@xfey/tutti 0.1.59 → 0.1.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/collaboration-state/clarification-records.d.ts +0 -9
  2. package/dist/collaboration-state/clarification-records.js +0 -57
  3. package/dist/collaboration-state/index.d.ts +6 -5
  4. package/dist/collaboration-state/index.js +5 -4
  5. package/dist/collaboration-state/messages.d.ts +4 -6
  6. package/dist/collaboration-state/messages.js +22 -29
  7. package/dist/collaboration-state/scratchpad-source-state.d.ts +4 -12
  8. package/dist/collaboration-state/scratchpad-source-state.js +24 -58
  9. package/dist/collaboration-state/scratchpad.d.ts +1 -2
  10. package/dist/collaboration-state/scratchpad.js +0 -59
  11. package/dist/collaboration-state/storage-types.d.ts +25 -5
  12. package/dist/collaboration-state/task-compile-context.d.ts +40 -0
  13. package/dist/collaboration-state/task-compile-context.js +273 -0
  14. package/dist/collaboration-state/types.d.ts +39 -9
  15. package/dist/control-plane/clarification-commands.d.ts +0 -1
  16. package/dist/control-plane/clarification-commands.js +0 -1
  17. package/dist/control-plane/index.js +16 -3
  18. package/dist/control-plane/project-context-bootstrap.d.ts +4 -4
  19. package/dist/control-plane/project-context-bootstrap.js +7 -12
  20. package/dist/control-plane/scratchpad-auto-refresh.js +12 -1
  21. package/dist/control-plane/scratchpad-refresh-start.js +77 -94
  22. package/dist/control-plane/scratchpad-refresh.js +3 -1
  23. package/dist/control-plane/scratchpad-source-messages.d.ts +15 -7
  24. package/dist/control-plane/scratchpad-source-messages.js +53 -69
  25. package/dist/control-plane/task-compile-clarification-context.d.ts +9 -0
  26. package/dist/control-plane/task-compile-clarification-context.js +70 -0
  27. package/dist/control-plane/task-compile-continuation.js +28 -56
  28. package/dist/control-plane/task-compile-output.js +50 -14
  29. package/dist/control-plane/task-compile-start.js +119 -55
  30. package/dist/control-plane/workflows/openai.d.ts +2 -1
  31. package/dist/control-plane/workflows/openai.js +13 -27
  32. package/dist/control-plane/workflows/types.d.ts +27 -17
  33. package/migrations/0014_scratchpad_task_compile_context.sql +147 -0
  34. package/migrations/README.md +2 -1
  35. package/package.json +1 -1
  36. package/prompts/procedures/README.md +3 -3
  37. package/prompts/procedures/scratchpad-refresh.md +17 -9
  38. package/prompts/procedures/task-compile.md +6 -3
  39. package/prompts/prompt-flow-map.md +21 -14
  40. package/web/assets/{homepage-motion-scene-BJF0AKlJ.js → homepage-motion-scene-CY7o4hnR.js} +1 -1
  41. package/web/assets/index-B08r3x8o.js +69 -0
  42. package/web/assets/index-DpabiRgp.css +1 -0
  43. package/web/index.html +2 -2
  44. package/web/assets/index-8lX8GhfZ.css +0 -1
  45. package/web/assets/index-BHGh4UOg.js +0 -69
@@ -1,4 +1,5 @@
1
- import { applyTaskCompileProposal, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, readWorklistProjection, submitScratchpadProjection, } from "../collaboration-state/index.js";
1
+ import { applyTaskCompileProposal, completeTaskCompileContext, materializeClarificationSuccessor, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, readClarificationRoundProjection, readWorklistProjection, } from "../collaboration-state/index.js";
2
+ import { withHostStoreTransaction } from "../store/index.js";
2
3
  import { clarificationInvalidates, taskCompileInvalidates, } from "./invalidations.js";
3
4
  import { normalizeTaskCompileProposal } from "./task-proposal.js";
4
5
  const EMPTY_INITIAL_WORKLIST_CLARIFICATION = {
@@ -72,25 +73,45 @@ export function applyTaskCompileProcedureOutput(input) {
72
73
  }
73
74
  const currentWorklist = readWorklistProjection(input.store.db);
74
75
  const proposal = normalizeTaskCompileProposal(input.output.proposal);
75
- if (proposal.tasks.length === 0 && countWorklistTasks(currentWorklist) === 0) {
76
+ if (input.continueFromRoundId === undefined &&
77
+ proposal.tasks.length === 0 &&
78
+ countWorklistTasks(currentWorklist) === 0) {
76
79
  return openTaskCompileNeedsHuman({
77
80
  ...input,
78
81
  requestPayload: EMPTY_INITIAL_WORKLIST_CLARIFICATION,
79
82
  });
80
83
  }
81
- const applied = applyTaskCompileProposal(input.store.db, {
82
- workflow_ref: input.workflowRef,
83
- proposal,
84
- now: input.now,
84
+ const continuationRound = input.continueFromRoundId === undefined
85
+ ? null
86
+ : readClarificationRoundProjection(input.store.db, input.continueFromRoundId);
87
+ const committed = withHostStoreTransaction(input.store.db, (tx) => {
88
+ const applied = applyTaskCompileProposal(tx, {
89
+ workflow_ref: input.workflowRef,
90
+ proposal,
91
+ now: input.now,
92
+ });
93
+ const scratchpad = completeTaskCompileContext(tx, {
94
+ workflow_ref: input.workflowRef,
95
+ activity_ref: input.activityRef,
96
+ now: input.now,
97
+ });
98
+ if (input.continueFromRoundId !== undefined) {
99
+ materializeClarificationSuccessor(tx, {
100
+ round_id: input.continueFromRoundId,
101
+ successor_ref: {
102
+ kind: "proposal_applied",
103
+ workflow_ref: input.workflowRef,
104
+ },
105
+ close_thread: true,
106
+ now: input.now,
107
+ });
108
+ }
109
+ return { applied, scratchpad };
85
110
  });
111
+ const { applied } = committed;
86
112
  if (applied.changed_task_ids.length > 0) {
87
113
  input.onTasksAdded?.();
88
114
  }
89
- const submittedScratchpad = submitScratchpadProjection(input.store.db, {
90
- workflow_ref: input.workflowRef,
91
- activity_ref: input.activityRef,
92
- now: input.now,
93
- });
94
115
  input.events.publish({
95
116
  event_type: "worklist.changed",
96
117
  payload: {
@@ -105,21 +126,36 @@ export function applyTaskCompileProcedureOutput(input) {
105
126
  input.events.publish({
106
127
  event_type: "scratchpad.updated",
107
128
  payload: {
108
- scratchpad: submittedScratchpad,
129
+ scratchpad: committed.scratchpad,
109
130
  activity_ref: input.activityRef,
110
131
  },
111
132
  invalidates: taskCompileInvalidates(),
112
133
  });
134
+ if (input.continueFromRoundId !== undefined && continuationRound !== null) {
135
+ input.events.publish({
136
+ event_type: "clarification.changed",
137
+ payload: {
138
+ reason: "successor_materialized",
139
+ thread_id: continuationRound.thread_id,
140
+ round_id: input.continueFromRoundId,
141
+ successor_ref: {
142
+ kind: "proposal_applied",
143
+ workflow_ref: input.workflowRef,
144
+ },
145
+ },
146
+ invalidates: clarificationInvalidates(input.continueFromRoundId, continuationRound.thread_id),
147
+ });
148
+ }
113
149
  if (input.continueFromRoundId !== undefined) {
114
150
  return {
115
151
  kind: "completed",
116
- summary: "Worklist compiled.",
152
+ summary: proposal.tasks.length === 0 ? "No new task created." : "Worklist compiled.",
117
153
  result_anchor: { kind: "workflow", workflow_ref: input.workflowRef },
118
154
  };
119
155
  }
120
156
  return {
121
157
  kind: "completed",
122
- summary: "Worklist compiled.",
158
+ summary: proposal.tasks.length === 0 ? "No new task created." : "Worklist compiled.",
123
159
  result_anchor: { kind: "workflow", workflow_ref: input.workflowRef },
124
160
  };
125
161
  }
@@ -1,5 +1,5 @@
1
1
  import { createWorkflowInvocationRef, } from "@tutti/shared/ids";
2
- import { readScratchpadProjection, readWorklistProjection } from "../collaboration-state/index.js";
2
+ import { captureTaskCompileContext, readActiveTaskCompileContext, readScratchpadProjection, readWorklistProjection, rollbackTaskCompileContext, } from "../collaboration-state/index.js";
3
3
  import { initializeProjectDocs, readProjectDocsInitializationStatus, } from "../workspace-ops/index.js";
4
4
  import { logProcedureExecutionError } from "./procedure-logging.js";
5
5
  import { projectBriefShouldRefreshForUpdatedPaths, refreshProjectBriefProjection, } from "./project-brief-refresh.js";
@@ -7,6 +7,15 @@ import { applyProjectDocsInitializationResult, buildProjectDocsInitializationCon
7
7
  import { readActiveScratchpadRefreshActivityRef, readControlPlaneScratchpadSourceBatch, runControlPlaneScratchpadRefreshProcedure, } from "./scratchpad-refresh-start.js";
8
8
  import { applyTaskCompileProcedureOutput } from "./task-compile-output.js";
9
9
  export function startControlPlaneTaskCompile(options) {
10
+ const activeContext = readActiveTaskCompileContext(options.store.db);
11
+ if (activeContext !== null) {
12
+ return {
13
+ disposition: {
14
+ kind: "already_running",
15
+ activity_ref: activeContext.captured_by_activity_ref,
16
+ },
17
+ };
18
+ }
10
19
  const scratchpad = readScratchpadProjection(options.store.db);
11
20
  if (options.payload.expected_scratchpad_updated_at !== undefined &&
12
21
  options.payload.expected_scratchpad_updated_at !== scratchpad.updated_at) {
@@ -31,6 +40,7 @@ export function startControlPlaneTaskCompile(options) {
31
40
  },
32
41
  };
33
42
  }
43
+ const taskCompileWorkflowRef = createWorkflowInvocationRef();
34
44
  const sourceBatch = readControlPlaneScratchpadSourceBatch(options.store);
35
45
  if (sourceBatch.source_message_count > 0) {
36
46
  const activeScratchpadRefresh = readActiveScratchpadRefreshActivityRef({
@@ -49,6 +59,7 @@ export function startControlPlaneTaskCompile(options) {
49
59
  ...options,
50
60
  runner: runnerResolution.runner,
51
61
  sourceBatch,
62
+ taskCompileWorkflowRef,
52
63
  });
53
64
  }
54
65
  if (!scratchpadHasSubmittableContent(scratchpad)) {
@@ -58,19 +69,23 @@ export function startControlPlaneTaskCompile(options) {
58
69
  return startProjectContextBootstrapActivity({
59
70
  ...options,
60
71
  runner: runnerResolution.runner,
72
+ taskCompileWorkflowRef,
61
73
  });
62
74
  }
63
75
  return startControlPlaneTaskCompileActivity({
64
76
  ...options,
65
77
  runner: runnerResolution.runner,
78
+ taskCompileWorkflowRef,
66
79
  });
67
80
  }
68
81
  function startControlPlaneTaskCompileActivity(options) {
82
+ const activeContext = readActiveTaskCompileContext(options.store.db, options.taskCompileWorkflowRef);
69
83
  const scratchpad = readScratchpadProjection(options.store.db);
70
- if (!scratchpadHasSubmittableContent(scratchpad)) {
84
+ if (activeContext === null &&
85
+ !scratchpadHasSubmittableContent(scratchpad)) {
71
86
  return { disposition: { kind: "scratchpad_empty" } };
72
87
  }
73
- const workflowRef = createWorkflowInvocationRef();
88
+ const workflowRef = options.taskCompileWorkflowRef;
74
89
  const start = options.engine.startProcedure({
75
90
  workflow_ref: workflowRef,
76
91
  workflow_kind: "task_compile",
@@ -201,8 +216,7 @@ function startProjectContextBootstrapActivity(options) {
201
216
  if (shouldInitializeControlPlaneProjectContext(options)) {
202
217
  return;
203
218
  }
204
- const scratchpad = readScratchpadProjection(options.store.db);
205
- if (!scratchpadHasSubmittableContent(scratchpad)) {
219
+ if (readActiveTaskCompileContext(options.store.db, options.taskCompileWorkflowRef) === null) {
206
220
  return;
207
221
  }
208
222
  void startControlPlaneTaskCompileActivity(options);
@@ -221,44 +235,59 @@ function startProjectContextBootstrapActivity(options) {
221
235
  summary: "Project context is unavailable.",
222
236
  };
223
237
  }
224
- const initializationContext = buildProjectDocsInitializationContext(readScratchpadProjection(options.store.db));
225
- const modelDocuments = await tryProjectContextBootstrapDocuments({
226
- runner: options.runner,
227
- projectContext: options.projectContext,
228
- scratchpad: readScratchpadProjection(options.store.db),
229
- projectName: options.projectContext.projectName,
230
- workflowRef,
238
+ const taskCompileContext = readOrCaptureTaskCompileContext({
239
+ store: options.store,
240
+ workflowRef: options.taskCompileWorkflowRef,
231
241
  activityRef: context.activity_ref,
232
- logger: options.logger,
233
- reportProgress: context.reportProgress,
234
- now: options.now,
235
- });
236
- const initialized = initializeProjectDocs({
237
- workspaceRoot: options.projectContext.workspaceRoot,
238
- projectName: options.projectContext.projectName,
239
- context: initializationContext,
240
- ...(modelDocuments === undefined ? {} : { documents: modelDocuments }),
241
242
  now: options.now,
242
243
  });
243
- if (projectBriefShouldRefreshForUpdatedPaths(initialized.created_paths)) {
244
- await refreshProjectBriefProjection({
245
- store: options.store,
246
- projectContext: options.projectContext,
247
- reason: "project_context_bootstrap",
244
+ try {
245
+ const initializationContext = buildProjectDocsInitializationContext(taskCompileContext.scratchpad_snapshot);
246
+ const modelDocuments = await tryProjectContextBootstrapDocuments({
248
247
  runner: options.runner,
248
+ projectContext: options.projectContext,
249
+ scratchpad: taskCompileContext.scratchpad_snapshot,
250
+ projectName: options.projectContext.projectName,
249
251
  workflowRef,
250
252
  activityRef: context.activity_ref,
251
253
  logger: options.logger,
254
+ reportProgress: context.reportProgress,
255
+ now: options.now,
256
+ });
257
+ const initialized = initializeProjectDocs({
258
+ workspaceRoot: options.projectContext.workspaceRoot,
259
+ projectName: options.projectContext.projectName,
260
+ context: initializationContext,
261
+ ...(modelDocuments === undefined ? {} : { documents: modelDocuments }),
262
+ now: options.now,
263
+ });
264
+ if (projectBriefShouldRefreshForUpdatedPaths(initialized.created_paths)) {
265
+ await refreshProjectBriefProjection({
266
+ store: options.store,
267
+ projectContext: options.projectContext,
268
+ reason: "project_context_bootstrap",
269
+ runner: options.runner,
270
+ workflowRef,
271
+ activityRef: context.activity_ref,
272
+ logger: options.logger,
273
+ now: options.now,
274
+ });
275
+ }
276
+ return applyProjectDocsInitializationResult({
277
+ events: options.events,
278
+ logger: options.logger,
279
+ result: initialized,
280
+ workflowRef,
281
+ activityRef: context.activity_ref,
282
+ });
283
+ }
284
+ catch (error) {
285
+ rollbackTaskCompileContext(options.store.db, {
286
+ workflow_ref: options.taskCompileWorkflowRef,
252
287
  now: options.now,
253
288
  });
289
+ throw error;
254
290
  }
255
- return applyProjectDocsInitializationResult({
256
- events: options.events,
257
- logger: options.logger,
258
- result: initialized,
259
- workflowRef,
260
- activityRef: context.activity_ref,
261
- });
262
291
  },
263
292
  });
264
293
  if (start.kind === "already_running") {
@@ -278,33 +307,59 @@ function startProjectContextBootstrapActivity(options) {
278
307
  };
279
308
  }
280
309
  export async function runControlPlaneTaskCompileProcedure(options) {
281
- const scratchpad = readScratchpadProjection(options.store.db);
282
- if (!scratchpadHasSubmittableContent(scratchpad)) {
283
- return {
284
- kind: "failed",
285
- summary: "Scratchpad is empty. Discuss more before running.",
286
- };
287
- }
288
- const output = await options.runner.runTaskCompile({
289
- workflow_ref: options.workflowRef,
290
- scratchpad,
291
- worklist: readWorklistProjection(options.store.db),
292
- }, {
293
- workflow_ref: options.workflowRef,
294
- activity_ref: options.activityRef,
295
- ...(options.reportProgress === undefined
296
- ? {}
297
- : { reportProgress: options.reportProgress }),
298
- });
299
- return applyTaskCompileProcedureOutput({
310
+ const context = readOrCaptureTaskCompileContext({
300
311
  store: options.store,
301
- events: options.events,
302
- output,
303
312
  workflowRef: options.workflowRef,
304
313
  activityRef: options.activityRef,
305
314
  now: options.now,
306
- onTasksAdded: options.onTasksAdded,
307
315
  });
316
+ if (!scratchpadSnapshotHasSubmittableContent(context.scratchpad_snapshot)) {
317
+ rollbackTaskCompileContext(options.store.db, {
318
+ workflow_ref: options.workflowRef,
319
+ now: options.now,
320
+ });
321
+ return {
322
+ kind: "failed",
323
+ summary: "Scratchpad is empty. Discuss more before running.",
324
+ };
325
+ }
326
+ try {
327
+ const output = await options.runner.runTaskCompile({
328
+ workflow_ref: options.workflowRef,
329
+ scratchpad: context.scratchpad_snapshot,
330
+ worklist: readWorklistProjection(options.store.db),
331
+ }, {
332
+ workflow_ref: options.workflowRef,
333
+ activity_ref: options.activityRef,
334
+ ...(options.reportProgress === undefined
335
+ ? {}
336
+ : { reportProgress: options.reportProgress }),
337
+ });
338
+ return applyTaskCompileProcedureOutput({
339
+ store: options.store,
340
+ events: options.events,
341
+ output,
342
+ workflowRef: options.workflowRef,
343
+ activityRef: options.activityRef,
344
+ now: options.now,
345
+ onTasksAdded: options.onTasksAdded,
346
+ });
347
+ }
348
+ catch (error) {
349
+ rollbackTaskCompileContext(options.store.db, {
350
+ workflow_ref: options.workflowRef,
351
+ now: options.now,
352
+ });
353
+ throw error;
354
+ }
355
+ }
356
+ function readOrCaptureTaskCompileContext(input) {
357
+ return (readActiveTaskCompileContext(input.store.db, input.workflowRef) ??
358
+ captureTaskCompileContext(input.store.db, {
359
+ workflow_ref: input.workflowRef,
360
+ captured_by_activity_ref: input.activityRef,
361
+ now: input.now,
362
+ }));
308
363
  }
309
364
  function scratchpadHasSubmittableContent(scratchpad) {
310
365
  if (scratchpad.state !== "ready") {
@@ -318,4 +373,13 @@ function scratchpadHasSubmittableContent(scratchpad) {
318
373
  ...scratchpad.task_changes,
319
374
  ].some((item) => item.trim().length > 0);
320
375
  }
376
+ function scratchpadSnapshotHasSubmittableContent(scratchpad) {
377
+ return [
378
+ scratchpad.topic,
379
+ scratchpad.background_summary,
380
+ ...scratchpad.current_consensus,
381
+ ...scratchpad.open_questions,
382
+ ...scratchpad.task_changes,
383
+ ].some((item) => item.trim().length > 0);
384
+ }
321
385
  //# sourceMappingURL=task-compile-start.js.map
@@ -2,7 +2,7 @@ import type { ProjectId } from "@tutti/shared/ids";
2
2
  import type { ProviderUsageRecorder } from "../../provider-usage/index.js";
3
3
  import { type OpenAiStructuredOutputClient } from "../../providers/openai/sdk-procedure-runner.js";
4
4
  import { type OpenAiProviderConfig } from "../../providers/openai/provider-config.js";
5
- import type { ProcedureWorkflowRunner, ProcedureWorkflowRunnerResolution } from "./types.js";
5
+ import type { ProcedureWorkflowRunner, ProcedureWorkflowRunnerResolution, TaskCompilePromptInput, TaskCompileWorkflowInput } from "./types.js";
6
6
  import type { CodexAppServerAgentContextRuntime } from "../../providers/openai/app-server/skills.js";
7
7
  export type OpenAiProcedureWorkflowRunnerOptions = {
8
8
  config: OpenAiProviderConfig;
@@ -30,6 +30,7 @@ export type ProjectOpenAiProcedureWorkflowRunnerOptions = {
30
30
  agentContext?: CodexAppServerAgentContextRuntime;
31
31
  recordUsage?: ProviderUsageRecorder;
32
32
  };
33
+ export declare function buildTaskCompilePromptInput(input: TaskCompileWorkflowInput): TaskCompilePromptInput;
33
34
  export declare function createOpenAiProcedureWorkflowRunner(options: OpenAiProcedureWorkflowRunnerOptions): ProcedureWorkflowRunner;
34
35
  export declare function resolveProjectOpenAiProcedureWorkflowRunner(options: ProjectOpenAiProcedureWorkflowRunnerOptions): ProcedureWorkflowRunnerResolution;
35
36
  //# sourceMappingURL=openai.d.ts.map
@@ -137,7 +137,7 @@ function followUpCheckWorkflowOutput(output) {
137
137
  summary: output.result.requires_retasking.summary,
138
138
  };
139
139
  }
140
- function taskCompilePromptInput(input) {
140
+ export function buildTaskCompilePromptInput(input) {
141
141
  const promptInput = {
142
142
  scratchpad: {
143
143
  topic: input.scratchpad.topic,
@@ -160,34 +160,20 @@ function taskCompilePromptInput(input) {
160
160
  })),
161
161
  },
162
162
  };
163
- if (input.clarification !== undefined) {
164
- const request = {
165
- ...(input.clarification.request_payload.title === undefined
166
- ? {}
167
- : { title: input.clarification.request_payload.title }),
168
- summary: input.clarification.request_payload.summary,
169
- request: input.clarification.request_payload.request,
170
- };
171
- promptInput.clarification = {
172
- request,
173
- answer_messages: input.clarification.messages
174
- .filter((message) => message.message_kind === "user_text" || message.message_kind === "agent_text")
175
- .map(taskCompileClarificationMessage),
176
- };
163
+ if (input.clarification_rounds !== undefined) {
164
+ promptInput.clarification_rounds = input.clarification_rounds.map((round) => ({
165
+ request: {
166
+ ...(round.request_payload.title === undefined
167
+ ? {}
168
+ : { title: round.request_payload.title }),
169
+ summary: round.request_payload.summary,
170
+ request: round.request_payload.request,
171
+ },
172
+ answer_messages: round.answer_messages,
173
+ }));
177
174
  }
178
175
  return promptInput;
179
176
  }
180
- function taskCompileClarificationMessage(message) {
181
- const role = message.author.kind === "agent" ? "tutti" : "human";
182
- const authorDisplayName = message.author.display_name?.trim();
183
- return {
184
- role,
185
- ...(role === "human" && authorDisplayName !== undefined && authorDisplayName !== ""
186
- ? { author_display_name: authorDisplayName }
187
- : {}),
188
- text: message.body.trim(),
189
- };
190
- }
191
177
  function taskCompileWorkflowOutput(output) {
192
178
  if ("clarification_request_payload" in output.result) {
193
179
  return {
@@ -280,7 +266,7 @@ export function createOpenAiProcedureWorkflowRunner(options) {
280
266
  templateKey: "procedures.task_compile",
281
267
  ...(options.promptsRoot === undefined ? {} : { promptsRoot: options.promptsRoot }),
282
268
  variables: {
283
- workflow_input_json: taskCompilePromptInput(input),
269
+ workflow_input_json: buildTaskCompilePromptInput(input),
284
270
  },
285
271
  });
286
272
  const result = await runCodexAppServerReadOnlyProcedure({
@@ -1,4 +1,4 @@
1
- import type { ExecutionRuntimeSnapshot, MessageProjection, RunResultProjection, ScratchpadProjection, TaskDetailProjection, WorklistProjection } from "@tutti/shared/schemas/api";
1
+ import type { ExecutionRuntimeSnapshot, MessageProjection, RunResultProjection, TaskDetailProjection, WorklistProjection } from "@tutti/shared/schemas/api";
2
2
  import type { ClarificationRequestPayload, TaskContractProjection } from "@tutti/shared/schemas/api";
3
3
  import type { ProjectDocKey } from "@tutti/shared/schemas/api";
4
4
  import type { ActivityRef, ClarificationRoundRef, ClarificationThreadRef, MessageId, TaskId, TaskModuleId, WorkflowInvocationRef } from "@tutti/shared/ids";
@@ -6,21 +6,31 @@ import type { RunLineage } from "@tutti/shared/domain";
6
6
  import type { ProjectBriefSnapshot, ProjectBriefSourceDocument } from "../../project-brief/index.js";
7
7
  export type ScratchpadRefreshWorkflowInput = {
8
8
  project_brief?: ProjectBriefSnapshot;
9
- previous: {
10
- topic?: string;
11
- background?: string;
12
- consensus?: string[];
13
- questions?: string[];
14
- changes?: string[];
15
- };
16
- recent_messages: ScratchpadRefreshSourceMessageInput[];
17
- new_messages: ScratchpadRefreshSourceMessageInput[];
9
+ worklist: ScratchpadRefreshWorklistInput;
10
+ active_task_compile?: ScratchpadWorkflowSnapshot;
11
+ messages: ScratchpadRefreshSourceMessageInput[];
12
+ };
13
+ export type ScratchpadWorkflowSnapshot = {
14
+ topic: string;
15
+ background_summary: string;
16
+ current_consensus: string[];
17
+ open_questions: string[];
18
+ task_changes: string[];
19
+ };
20
+ export type ScratchpadRefreshWorklistInput = {
21
+ modules: Array<{
22
+ name: string;
23
+ tasks: Array<{
24
+ title: string;
25
+ summary?: string;
26
+ status: WorklistProjection["modules"][number]["tasks"][number]["status"];
27
+ }>;
28
+ }>;
18
29
  };
19
30
  export type ScratchpadRefreshSourceMessageInput = {
20
31
  role: "human" | "tutti";
21
32
  author_display_name?: string;
22
33
  text: string;
23
- ref?: string;
24
34
  };
25
35
  export type ScratchpadRefreshWorkflowContext = {
26
36
  workflow_ref: WorkflowInvocationRef;
@@ -68,12 +78,12 @@ export type TaskCompileProposalTask = {
68
78
  };
69
79
  export type TaskCompileWorkflowInput = {
70
80
  workflow_ref: WorkflowInvocationRef;
71
- scratchpad: ScratchpadProjection;
81
+ scratchpad: ScratchpadWorkflowSnapshot;
72
82
  worklist: WorklistProjection;
73
- clarification?: {
83
+ clarification_rounds?: Array<{
74
84
  request_payload: ClarificationRequestPayload;
75
- messages: MessageProjection[];
76
- };
85
+ answer_messages: TaskCompilePromptClarificationMessage[];
86
+ }>;
77
87
  };
78
88
  export type TaskCompileWorkflowOutput = {
79
89
  decision: "ready";
@@ -103,14 +113,14 @@ export type TaskCompilePromptInput = {
103
113
  }>;
104
114
  }>;
105
115
  };
106
- clarification?: {
116
+ clarification_rounds?: Array<{
107
117
  request: {
108
118
  title?: string;
109
119
  summary: string;
110
120
  request: string;
111
121
  };
112
122
  answer_messages: TaskCompilePromptClarificationMessage[];
113
- };
123
+ }>;
114
124
  };
115
125
  export type TaskCompilePromptClarificationMessage = {
116
126
  role: "human" | "tutti";
@@ -0,0 +1,147 @@
1
+ DROP TABLE IF EXISTS scratchpad_receipt_sources;
2
+ DROP TABLE IF EXISTS scratchpad_source_refs;
3
+ DROP TABLE IF EXISTS scratchpad_source_state;
4
+
5
+ CREATE TABLE scratchpad_source_state (
6
+ id TEXT PRIMARY KEY CHECK (id = 'current'),
7
+ cycle_start_cursor_created_at TEXT,
8
+ cycle_start_cursor_message_id TEXT,
9
+ refreshed_through_cursor_created_at TEXT,
10
+ refreshed_through_cursor_message_id TEXT,
11
+ dirty_since TEXT,
12
+ in_flight_activity_ref TEXT,
13
+ in_flight_started_at TEXT,
14
+ last_completed_at TEXT,
15
+ last_failed_at TEXT,
16
+ backoff_until TEXT,
17
+ updated_at TEXT NOT NULL,
18
+ CHECK (
19
+ (cycle_start_cursor_created_at IS NULL AND cycle_start_cursor_message_id IS NULL)
20
+ OR (
21
+ cycle_start_cursor_created_at IS NOT NULL
22
+ AND cycle_start_cursor_message_id IS NOT NULL
23
+ )
24
+ ),
25
+ CHECK (
26
+ (
27
+ refreshed_through_cursor_created_at IS NULL
28
+ AND refreshed_through_cursor_message_id IS NULL
29
+ )
30
+ OR (
31
+ refreshed_through_cursor_created_at IS NOT NULL
32
+ AND refreshed_through_cursor_message_id IS NOT NULL
33
+ )
34
+ ),
35
+ CHECK (
36
+ (in_flight_activity_ref IS NULL AND in_flight_started_at IS NULL)
37
+ OR (in_flight_activity_ref IS NOT NULL AND in_flight_started_at IS NOT NULL)
38
+ )
39
+ );
40
+
41
+ CREATE TABLE scratchpad_source_refs (
42
+ scratchpad_id TEXT NOT NULL DEFAULT 'current' CHECK (scratchpad_id = 'current'),
43
+ message_id TEXT NOT NULL,
44
+ source_scope TEXT NOT NULL DEFAULT 'main_chat' CHECK (source_scope = 'main_chat'),
45
+ created_at TEXT NOT NULL,
46
+ PRIMARY KEY (scratchpad_id, message_id),
47
+ FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE
48
+ );
49
+
50
+ CREATE INDEX idx_scratchpad_source_refs_created_at
51
+ ON scratchpad_source_refs (scratchpad_id, created_at ASC, message_id ASC);
52
+
53
+ CREATE TABLE scratchpad_receipt_sources (
54
+ workflow_ref TEXT NOT NULL,
55
+ message_id TEXT NOT NULL,
56
+ source_scope TEXT NOT NULL DEFAULT 'main_chat' CHECK (source_scope = 'main_chat'),
57
+ created_at TEXT NOT NULL,
58
+ PRIMARY KEY (workflow_ref, message_id),
59
+ FOREIGN KEY (workflow_ref) REFERENCES scratchpad_receipts(workflow_ref) ON DELETE CASCADE,
60
+ FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE
61
+ );
62
+
63
+ CREATE INDEX idx_scratchpad_receipt_sources_created_at
64
+ ON scratchpad_receipt_sources (workflow_ref, created_at ASC, message_id ASC);
65
+
66
+ CREATE TABLE active_task_compile_context (
67
+ id TEXT PRIMARY KEY CHECK (id = 'current'),
68
+ workflow_ref TEXT NOT NULL UNIQUE,
69
+ captured_by_activity_ref TEXT NOT NULL,
70
+ submitted_at TEXT NOT NULL,
71
+ topic TEXT NOT NULL,
72
+ background_summary TEXT NOT NULL,
73
+ current_consensus_json TEXT NOT NULL CHECK (
74
+ json_valid(current_consensus_json) AND json_type(current_consensus_json) = 'array'
75
+ ),
76
+ open_questions_json TEXT NOT NULL CHECK (
77
+ json_valid(open_questions_json) AND json_type(open_questions_json) = 'array'
78
+ ),
79
+ task_changes_json TEXT NOT NULL CHECK (
80
+ json_valid(task_changes_json) AND json_type(task_changes_json) = 'array'
81
+ ),
82
+ scratchpad_updated_at TEXT NOT NULL,
83
+ source_window_after_created_at TEXT,
84
+ source_window_after_message_id TEXT,
85
+ source_window_through_created_at TEXT,
86
+ source_window_through_message_id TEXT,
87
+ CHECK (
88
+ (source_window_after_created_at IS NULL AND source_window_after_message_id IS NULL)
89
+ OR (
90
+ source_window_after_created_at IS NOT NULL
91
+ AND source_window_after_message_id IS NOT NULL
92
+ )
93
+ ),
94
+ CHECK (
95
+ (source_window_through_created_at IS NULL AND source_window_through_message_id IS NULL)
96
+ OR (
97
+ source_window_through_created_at IS NOT NULL
98
+ AND source_window_through_message_id IS NOT NULL
99
+ )
100
+ ),
101
+ CHECK (
102
+ source_window_after_created_at IS NULL
103
+ OR source_window_through_created_at IS NULL
104
+ OR source_window_through_created_at > source_window_after_created_at
105
+ OR (
106
+ source_window_through_created_at = source_window_after_created_at
107
+ AND source_window_through_message_id >= source_window_after_message_id
108
+ )
109
+ )
110
+ );
111
+
112
+ CREATE TRIGGER active_task_compile_context_immutable
113
+ BEFORE UPDATE ON active_task_compile_context
114
+ BEGIN
115
+ SELECT RAISE(ABORT, 'active Task Compile Context is immutable');
116
+ END;
117
+
118
+ CREATE TABLE active_task_compile_context_sources (
119
+ workflow_ref TEXT NOT NULL,
120
+ message_id TEXT NOT NULL,
121
+ created_at TEXT NOT NULL,
122
+ PRIMARY KEY (workflow_ref, message_id),
123
+ FOREIGN KEY (workflow_ref) REFERENCES active_task_compile_context(workflow_ref)
124
+ ON DELETE CASCADE,
125
+ FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE
126
+ );
127
+
128
+ CREATE INDEX idx_active_task_compile_context_sources_order
129
+ ON active_task_compile_context_sources (workflow_ref, created_at ASC, message_id ASC);
130
+
131
+ CREATE TRIGGER active_task_compile_context_sources_main_chat_only
132
+ BEFORE INSERT ON active_task_compile_context_sources
133
+ WHEN NOT EXISTS (
134
+ SELECT 1
135
+ FROM messages
136
+ WHERE messages.id = NEW.message_id
137
+ AND messages.scope_kind = 'main_chat'
138
+ )
139
+ BEGIN
140
+ SELECT RAISE(ABORT, 'Task Compile Context source must be a main-chat message');
141
+ END;
142
+
143
+ CREATE TRIGGER active_task_compile_context_sources_immutable
144
+ BEFORE UPDATE ON active_task_compile_context_sources
145
+ BEGIN
146
+ SELECT RAISE(ABORT, 'Task Compile Context sources are immutable');
147
+ END;