- 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
232 lines
6.1 KiB
TypeScript
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));
|