Files
zopu-code/packages/primitives/src/work.ts
T
-Puter cc47007fa9 Slice 1 polish: archive dormant code, merge work-os into primitives, add futures
- Archive dormant apps (daemon, desktop, native, tui) and superseded
  packages/server into repos/ for reference
- Archive 21 dead web components (chat page shell, work-os cluster, strays)
  into repos/web/; keep reused message-rendering primitives in components/chat
- Merge @code/work-os into @code/primitives; work.ts absorbed via ./work
  subpath and barrel export; update all four importers
- Add docs/futures.md tracking audio/video (blocked at Flue + provider),
  web layout cleanup, and message rendering quality
- Repoint dev:zopu script to @code/agents (the live Flue server)
- Note repos/ reference-code convention in AGENTS.md
2026-07-27 16:34:17 +05:30

232 lines
6.1 KiB
TypeScript

import { Array as EffectArray, Effect, Order, Schema } from "effect";
const MeaningfulString = Schema.String.check(
Schema.makeFilter((value) => value.trim().length > 0, {
expected: "a non-empty string",
})
);
export const WorkStatus = Schema.Literal("proposed");
export type WorkStatus = typeof WorkStatus.Type;
export const WorkDraft = Schema.Struct({
objective: MeaningfulString,
title: MeaningfulString,
});
export type WorkDraft = typeof WorkDraft.Type;
export const SignalProblem = Schema.Struct({
desiredOutcome: MeaningfulString,
summary: MeaningfulString,
title: MeaningfulString,
});
export const WorkCommandReason = Schema.Literals([
"InvalidInput",
"ProjectMismatch",
"TitleTooLong",
"ObjectiveTooLong",
]);
export type WorkCommandReason = typeof WorkCommandReason.Type;
export class WorkCommandError extends Schema.TaggedErrorClass<WorkCommandError>()(
"WorkCommandError",
{
message: Schema.String,
reason: WorkCommandReason,
}
) {}
export const workDraftFromSignal = Effect.fn("Work.draftFromSignal")(
function* workDraftFromSignal(input: unknown) {
const problem = yield* Schema.decodeUnknownEffect(SignalProblem)(
input
).pipe(
Effect.mapError(
() =>
new WorkCommandError({
message: "Signal is missing a valid problem statement",
reason: "InvalidInput",
})
)
);
const title = problem.title.trim();
const objective = problem.desiredOutcome.trim();
if (title.length > 160) {
return yield* new WorkCommandError({
message: "Work title must be 160 characters or fewer",
reason: "TitleTooLong",
});
}
if (objective.length > 2000) {
return yield* new WorkCommandError({
message: "Work objective must be 2000 characters or fewer",
reason: "ObjectiveTooLong",
});
}
return { objective, title } satisfies WorkDraft;
}
);
const WorkIdentity = Schema.Struct({
organizationId: MeaningfulString,
projectId: MeaningfulString,
workId: MeaningfulString,
});
const SignalIdentity = Schema.Struct({
organizationId: MeaningfulString,
projectId: MeaningfulString,
signalId: MeaningfulString,
title: MeaningfulString,
});
const WorkAttachmentInput = Schema.Struct({
signal: SignalIdentity,
work: WorkIdentity,
});
export const WorkEventKind = Schema.Literals([
"work.proposed",
"signal.attached",
]);
export type WorkEventKind = typeof WorkEventKind.Type;
export const WorkEventDraft = Schema.Struct({
data: Schema.Struct({
signalId: MeaningfulString,
title: MeaningfulString,
}),
idempotencyKey: MeaningfulString,
kind: WorkEventKind,
});
export type WorkEventDraft = typeof WorkEventDraft.Type;
export const workProposedEventFromSignal = Effect.fn(
"Work.proposedEventFromSignal"
)(function* workProposedEventFromSignal(input: unknown) {
const signal = yield* Schema.decodeUnknownEffect(SignalIdentity)(input).pipe(
Effect.mapError(
() =>
new WorkCommandError({
message: "Signal identity is invalid",
reason: "InvalidInput",
})
)
);
return {
data: { signalId: signal.signalId, title: signal.title.trim() },
idempotencyKey: `proposed:${signal.signalId}`,
kind: "work.proposed",
} satisfies WorkEventDraft;
});
export const signalAttachedEvent = Effect.fn("Work.signalAttachedEvent")(
function* signalAttachedEvent(input: unknown) {
const attachment = yield* Schema.decodeUnknownEffect(WorkAttachmentInput)(
input
).pipe(
Effect.mapError(
() =>
new WorkCommandError({
message: "Work attachment input is invalid",
reason: "InvalidInput",
})
)
);
if (
attachment.signal.organizationId !== attachment.work.organizationId ||
attachment.signal.projectId !== attachment.work.projectId
) {
return yield* new WorkCommandError({
message: "Signal and Work must belong to the same project",
reason: "ProjectMismatch",
});
}
return {
data: {
signalId: attachment.signal.signalId,
title: attachment.signal.title.trim(),
},
idempotencyKey: `signal:${attachment.signal.signalId}`,
kind: "signal.attached",
} satisfies WorkEventDraft;
}
);
const WorkNoticeInput = Schema.Array(
Schema.Struct({
_id: MeaningfulString,
events: Schema.Array(
Schema.Struct({
_id: MeaningfulString,
createdAt: Schema.Number,
kind: WorkEventKind,
})
),
signals: Schema.Array(
Schema.Struct({
sources: Schema.Array(
Schema.Struct({ messageId: MeaningfulString, rawText: Schema.String })
),
})
),
})
);
export interface WorkNotice {
readonly createdAt: number;
readonly eventId: string;
readonly sourceMessageIds: readonly string[];
readonly sourceTexts: readonly string[];
readonly workId: string;
}
const projectWorkNoticesEffect = Effect.fn("Work.projectNotices")(
function* projectWorkNoticesEffect(input: unknown) {
const works = yield* Schema.decodeUnknownEffect(WorkNoticeInput)(
input
).pipe(
Effect.mapError(
() =>
new WorkCommandError({
message: "Work notice data is invalid",
reason: "InvalidInput",
})
)
);
const notices = works.flatMap((work) => {
const sourceMessageIds = work.signals.flatMap((signal) =>
signal.sources.map((source) => source.messageId)
);
const sourceTexts = work.signals.flatMap((signal) =>
signal.sources.map((source) => source.rawText)
);
return work.events
.filter((event) => event.kind === "work.proposed")
.map(
(event): WorkNotice => ({
createdAt: event.createdAt,
eventId: event._id,
sourceMessageIds,
sourceTexts,
workId: work._id,
})
);
});
return EffectArray.sortWith(
notices,
(notice) => notice.createdAt,
Order.Number
);
}
);
export const projectWorkNotices = (input: unknown): readonly WorkNotice[] =>
Effect.runSync(projectWorkNoticesEffect(input));