@xfey/tutti 0.1.110 → 0.1.112

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 (81) hide show
  1. package/README.md +1 -1
  2. package/dist/approvals/decision-command.d.ts +2 -2
  3. package/dist/chat-assistant/index.d.ts +1 -1
  4. package/dist/chat-assistant/index.js +26 -6
  5. package/dist/collaboration-ingestion/clarification.d.ts +25 -0
  6. package/dist/collaboration-ingestion/clarification.js +63 -0
  7. package/dist/collaboration-ingestion/external-source.d.ts +2 -0
  8. package/dist/collaboration-ingestion/external-source.js +35 -0
  9. package/dist/collaboration-ingestion/index.d.ts +2 -0
  10. package/dist/collaboration-ingestion/index.js +2 -0
  11. package/dist/collaboration-ingestion/main-chat.js +9 -2
  12. package/dist/collaboration-ingestion/platform-clarification.d.ts +10 -0
  13. package/dist/collaboration-ingestion/platform-clarification.js +42 -0
  14. package/dist/collaboration-ingestion/types.d.ts +1 -0
  15. package/dist/collaboration-state/clarification-messages.js +3 -2
  16. package/dist/collaboration-state/clarification-records.d.ts +1 -1
  17. package/dist/collaboration-state/clarification-records.js +7 -5
  18. package/dist/collaboration-state/clarification-rounds.js +5 -5
  19. package/dist/collaboration-state/clarification-snapshot.d.ts +228 -0
  20. package/dist/collaboration-state/clarification-snapshot.js +29 -0
  21. package/dist/collaboration-state/clarification-successors.js +6 -6
  22. package/dist/collaboration-state/index.d.ts +1 -0
  23. package/dist/collaboration-state/index.js +1 -0
  24. package/dist/collaboration-state/run-recording.js +1 -1
  25. package/dist/collaboration-state/task-compile-context.d.ts +1 -1
  26. package/dist/collaboration-state/types.d.ts +1 -0
  27. package/dist/control-plane/clarification-commands.d.ts +3 -2
  28. package/dist/control-plane/clarification-commands.js +13 -4
  29. package/dist/control-plane/follow-up-context.js +8 -14
  30. package/dist/control-plane/index.d.ts +3 -2
  31. package/dist/control-plane/scratchpad-source-messages.js +9 -3
  32. package/dist/control-plane/task-compile-clarification-context.js +7 -2
  33. package/dist/control-plane/task-source-context-types.d.ts +1 -1
  34. package/dist/control-plane/task-source-context.js +4 -2
  35. package/dist/control-plane/types.d.ts +1 -0
  36. package/dist/control-plane/workflows/openai.js +5 -4
  37. package/dist/platform-outbound/repository.js +83 -4
  38. package/dist/server-shell/cli/host-lifecycle.js +1 -0
  39. package/dist/server-shell/cli/host-server-runtime.d.ts +2 -1
  40. package/dist/server-shell/cli/host-server-runtime.js +4 -2
  41. package/dist/server-shell/cli/relay-registration.js +2 -0
  42. package/dist/server-shell/http/host-tunnel.d.ts +2 -1
  43. package/dist/server-shell/http/host-tunnel.js +4 -1
  44. package/dist/server-shell/http/integration-clarification-dispatch.d.ts +6 -0
  45. package/dist/server-shell/http/integration-clarification-dispatch.js +43 -0
  46. package/dist/server-shell/http/routes/agent-context-reference-routes.js +1 -3
  47. package/dist/server-shell/http/routes/project-api/clarification-routes.js +16 -42
  48. package/dist/server-shell/http/routes/project-api/openapi-collaboration-routes.d.ts +2 -0
  49. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +1 -0
  50. package/dist/server-shell/http/routes/project-api/openapi.d.ts +3 -0
  51. package/dist/server-shell/http/routes/project-api/schemas.d.ts +1 -0
  52. package/migrations/0022_clarification_projection_cursors.sql +7 -0
  53. package/node_modules/@tutti/relay-client/dist/host-control.d.ts +2 -0
  54. package/node_modules/@tutti/relay-client/dist/host-control.js +6 -0
  55. package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +14 -1
  56. package/node_modules/@tutti/shared/dist/schemas/api/clarifications.js +9 -2
  57. package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +4 -0
  58. package/node_modules/@tutti/shared/dist/schemas/api/messages.js +1 -0
  59. package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +1 -0
  60. package/node_modules/@tutti/shared/dist/schemas/internal/index.d.ts +2 -0
  61. package/node_modules/@tutti/shared/dist/schemas/internal/index.js +2 -0
  62. package/node_modules/@tutti/shared/dist/schemas/internal/platform-clarification-content.d.ts +15 -0
  63. package/node_modules/@tutti/shared/dist/schemas/internal/platform-clarification-content.js +24 -0
  64. package/node_modules/@tutti/shared/dist/schemas/internal/platform-clarification.d.ts +354 -0
  65. package/node_modules/@tutti/shared/dist/schemas/internal/platform-clarification.js +94 -0
  66. package/node_modules/@tutti/shared/dist/schemas/internal/platform-integration.d.ts +132 -5
  67. package/node_modules/@tutti/shared/dist/schemas/internal/platform-integration.js +29 -5
  68. package/node_modules/@tutti/shared/dist/utils/clarification-participants.d.ts +4 -0
  69. package/node_modules/@tutti/shared/dist/utils/clarification-participants.js +11 -0
  70. package/node_modules/@tutti/shared/dist/utils/index.d.ts +1 -0
  71. package/node_modules/@tutti/shared/dist/utils/index.js +1 -0
  72. package/package.json +1 -1
  73. package/prompts/chat-assistant.md +1 -0
  74. package/prompts/procedures/follow-up-check.md +4 -4
  75. package/prompts/procedures/scratchpad-refresh.md +22 -2
  76. package/prompts/procedures/task-compile.md +1 -0
  77. package/prompts/runs/task-execute.md +2 -0
  78. package/web/assets/{homepage-motion-scene-rxYjg-zc.js → homepage-motion-scene-BwdH6Iss.js} +1 -1
  79. package/web/assets/index-B29RuNsd.js +69 -0
  80. package/web/index.html +1 -1
  81. package/web/assets/index-BrKWdtKZ.js +0 -69
@@ -0,0 +1,228 @@
1
+ import type { ClarificationRoundRef } from "@tutti/shared/ids";
2
+ import type { SqliteDatabase } from "../store/index.js";
3
+ /** Round messages are append-only; the eligible answer count is a stable CAS revision. */
4
+ export declare function readClarificationAnswerRevision(db: SqliteDatabase, roundId: ClarificationRoundRef): number;
5
+ /** Read the immutable submitted refs, not a first page or messages appended after sealing. */
6
+ export declare function readSubmittedClarificationMessages(db: SqliteDatabase, roundId: ClarificationRoundRef): {
7
+ clarification_card?: {
8
+ title?: string;
9
+ status: "writable" | "sealed";
10
+ round_id: string;
11
+ thread_id: string;
12
+ summary: string;
13
+ is_current_round: boolean;
14
+ };
15
+ refs?: {
16
+ task_id?: string;
17
+ activity_ref?: string;
18
+ artifact_feedback?: {
19
+ summary?: string;
20
+ kind: "artifact_feedback";
21
+ version: 1;
22
+ preview_generation: string;
23
+ annotations: {
24
+ document_url: string;
25
+ comment: string;
26
+ target: {
27
+ fallback_context?: {
28
+ snapshot: {
29
+ role?: string;
30
+ accessible_name?: string;
31
+ text_excerpt?: string;
32
+ state?: {
33
+ expanded?: boolean;
34
+ selected?: boolean;
35
+ checked?: boolean;
36
+ pressed?: boolean;
37
+ disabled?: boolean;
38
+ };
39
+ tag_name: string;
40
+ element_kind: "content" | "control" | "container";
41
+ context: {
42
+ role?: string;
43
+ accessible_name?: string;
44
+ tag_name: string;
45
+ }[];
46
+ };
47
+ anchor: {
48
+ explicit_anchor?: string;
49
+ stable_attributes?: {
50
+ id?: string;
51
+ test_id?: string;
52
+ };
53
+ version: 1;
54
+ semantic_path: {
55
+ role?: string;
56
+ accessible_name?: string;
57
+ }[];
58
+ selector_path: {
59
+ root: "document" | "open_shadow_root";
60
+ selector: string;
61
+ }[];
62
+ };
63
+ };
64
+ snapshot: {
65
+ role?: string;
66
+ accessible_name?: string;
67
+ text_excerpt?: string;
68
+ state?: {
69
+ expanded?: boolean;
70
+ selected?: boolean;
71
+ checked?: boolean;
72
+ pressed?: boolean;
73
+ disabled?: boolean;
74
+ };
75
+ tag_name: string;
76
+ element_kind: "content" | "control" | "container";
77
+ context: {
78
+ role?: string;
79
+ accessible_name?: string;
80
+ tag_name: string;
81
+ }[];
82
+ };
83
+ anchor: {
84
+ explicit_anchor?: string;
85
+ stable_attributes?: {
86
+ id?: string;
87
+ test_id?: string;
88
+ };
89
+ version: 1;
90
+ semantic_path: {
91
+ role?: string;
92
+ accessible_name?: string;
93
+ }[];
94
+ selector_path: {
95
+ root: "document" | "open_shadow_root";
96
+ selector: string;
97
+ }[];
98
+ };
99
+ };
100
+ }[];
101
+ artifact_title: string;
102
+ inspector_version: 1;
103
+ };
104
+ reference_file?: {
105
+ media_type?: string;
106
+ blob_oid?: string;
107
+ path: string;
108
+ name: string;
109
+ source: "human_upload" | "tutti_output";
110
+ category: "file" | "image";
111
+ size_bytes: number;
112
+ text_read: {
113
+ blocked_reason?: "unknown" | "binary" | "too_large" | "unsupported_encoding" | "restricted_path" | "secret_like";
114
+ can_read: boolean;
115
+ };
116
+ };
117
+ workflow_ref?: string;
118
+ clarification_round_ref?: string;
119
+ clarification_thread_ref?: string;
120
+ task_result?: {
121
+ error_code?: string;
122
+ activity_ref?: string;
123
+ generated_files?: {
124
+ media_type?: string;
125
+ group?: string;
126
+ path: string;
127
+ name: string;
128
+ size_bytes: number;
129
+ }[];
130
+ module_name?: string;
131
+ latest_run_result_id?: string;
132
+ retry_action?: "retryable" | "retried";
133
+ final_note?: string;
134
+ status: "completed" | "failed";
135
+ kind: "task_result";
136
+ task_id: string;
137
+ title: string;
138
+ summary: string;
139
+ task_title: string;
140
+ result_key: string;
141
+ };
142
+ worklist_compile?: {
143
+ kind: "worklist_compile";
144
+ title: string;
145
+ workflow_ref: string;
146
+ tasks: {
147
+ status: "failed" | "done" | "pending" | "todo" | "running";
148
+ task_id: string;
149
+ title: string;
150
+ summary: string;
151
+ module_id: string;
152
+ module_name: string;
153
+ }[];
154
+ task_count: number;
155
+ };
156
+ artifact_preview?: {
157
+ task_id?: string;
158
+ run_result_id?: string;
159
+ status: "available" | "updated";
160
+ kind: "artifact_preview";
161
+ title: string;
162
+ };
163
+ development_operator?: true;
164
+ external_reference_reads?: string[];
165
+ external_references?: {
166
+ name: string;
167
+ kind: "feishu_docx" | "feishu_drive_file" | "feishu_file" | "feishu_image";
168
+ created_at: string;
169
+ source_ref: string;
170
+ }[];
171
+ scratchpad_source?: "include" | "exclude";
172
+ worklist_feedback?: {
173
+ generated_files?: {
174
+ media_type?: string;
175
+ group?: string;
176
+ path: string;
177
+ name: string;
178
+ size_bytes: number;
179
+ }[];
180
+ final_note?: string;
181
+ primary_task_id?: string;
182
+ status: "completed" | "failed";
183
+ kind: "worklist_terminal";
184
+ items: {
185
+ module_name?: string;
186
+ latest_run_result_id?: string;
187
+ status: "failed" | "done";
188
+ task_id: string;
189
+ title: string;
190
+ summary: string;
191
+ }[];
192
+ title: string;
193
+ reason: "all_tasks_done" | "run_failed";
194
+ counters: {
195
+ completed: number;
196
+ failed: number;
197
+ remaining: number;
198
+ };
199
+ feedback_key: string;
200
+ };
201
+ };
202
+ scope: {
203
+ kind: "main_chat";
204
+ } | {
205
+ kind: "clarification_round";
206
+ clarification_round_ref: string;
207
+ };
208
+ id: string;
209
+ author: {
210
+ ref?: string;
211
+ display_name?: string;
212
+ avatar?: {
213
+ kind: "initials";
214
+ initials: string;
215
+ color: "neutral" | "green" | "blue" | "yellow";
216
+ } | {
217
+ kind: "image";
218
+ initials: string;
219
+ color: "neutral" | "green" | "blue" | "yellow";
220
+ image_url: string;
221
+ };
222
+ kind: "human" | "system" | "agent";
223
+ };
224
+ body: string;
225
+ created_at: string;
226
+ message_kind: "user_text" | "agent_text" | "system_notice" | "clarification_request" | "clarification_card";
227
+ }[];
228
+ //# sourceMappingURL=clarification-snapshot.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { mapMessageRow } from "./messages.js";
2
+ /** Round messages are append-only; the eligible answer count is a stable CAS revision. */
3
+ export function readClarificationAnswerRevision(db, roundId) {
4
+ return db
5
+ .prepare(`
6
+ SELECT COUNT(*) AS count FROM messages
7
+ WHERE scope_kind = 'clarification_round' AND clarification_round_id = ?
8
+ AND message_kind = 'user_text'
9
+ AND (author_kind = 'human' OR
10
+ (author_kind = 'system' AND json_extract(refs_json, '$.development_operator') = 1))
11
+ `)
12
+ .get(roundId).count;
13
+ }
14
+ /** Read the immutable submitted refs, not a first page or messages appended after sealing. */
15
+ export function readSubmittedClarificationMessages(db, roundId) {
16
+ return db
17
+ .prepare(`
18
+ SELECT messages.* FROM clarification_rounds AS rounds
19
+ JOIN json_each(rounds.submitted_message_refs_json) AS submitted
20
+ JOIN messages ON messages.id = submitted.value
21
+ WHERE rounds.id = ? AND rounds.status = 'sealed'
22
+ AND messages.scope_kind = 'clarification_round'
23
+ AND messages.clarification_round_id = rounds.id
24
+ ORDER BY CAST(submitted.key AS INTEGER)
25
+ `)
26
+ .all(roundId)
27
+ .map((row) => mapMessageRow(row));
28
+ }
29
+ //# sourceMappingURL=clarification-snapshot.js.map
@@ -15,7 +15,7 @@ export function submitClarificationRound(db, input) {
15
15
  return;
16
16
  }
17
17
  if (row.status === "sealed") {
18
- round = mapClarificationRoundRow(row);
18
+ round = mapClarificationRoundRow(row, db);
19
19
  return;
20
20
  }
21
21
  const messageRows = db
@@ -64,7 +64,7 @@ export function materializeClarificationSuccessor(db, input) {
64
64
  return;
65
65
  }
66
66
  if (row.successor_status === "materialized") {
67
- round = mapClarificationRoundRow(row);
67
+ round = mapClarificationRoundRow(row, db);
68
68
  return;
69
69
  }
70
70
  db.prepare(`
@@ -120,7 +120,7 @@ export function materializeTaskBoundRunSuccessor(db, input) {
120
120
  result = {
121
121
  kind: "already_materialized",
122
122
  thread: mapClarificationThreadRow(thread),
123
- round: mapClarificationRoundRow(row),
123
+ round: mapClarificationRoundRow(row, db),
124
124
  task: readTaskProjection(db, thread.owner_task_id),
125
125
  };
126
126
  return;
@@ -182,7 +182,7 @@ export function materializeTaskBoundRunSuccessor(db, input) {
182
182
  result = {
183
183
  kind: "materialized",
184
184
  thread: mapClarificationThreadRow(updatedThread),
185
- round: mapClarificationRoundRow(updatedRound),
185
+ round: mapClarificationRoundRow(updatedRound, db),
186
186
  task: updatedTask,
187
187
  };
188
188
  })();
@@ -210,7 +210,7 @@ export function closeTaskBoundClarificationAsRetaskingRequired(db, input) {
210
210
  return {
211
211
  kind: "already_materialized",
212
212
  thread: mapClarificationThreadRow(thread),
213
- round: mapClarificationRoundRow(row),
213
+ round: mapClarificationRoundRow(row, db),
214
214
  task: readTaskProjection(tx, taskId),
215
215
  task_detail: readTaskDetailProjection(tx, taskId),
216
216
  };
@@ -308,7 +308,7 @@ export function closeTaskBoundClarificationAsRetaskingRequired(db, input) {
308
308
  return {
309
309
  kind: "closed",
310
310
  thread: mapClarificationThreadRow(updatedThread),
311
- round: mapClarificationRoundRow(updatedRound),
311
+ round: mapClarificationRoundRow(updatedRound, db),
312
312
  task,
313
313
  task_detail: taskDetail,
314
314
  message,
@@ -1,4 +1,5 @@
1
1
  export { CollaborationStateCursorError } from "./serialization.js";
2
+ export { readClarificationAnswerRevision, readSubmittedClarificationMessages, } from "./clarification-snapshot.js";
2
3
  export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
4
  export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, ensureInitialProjectWelcomeMessage, hasMainChatArtifactPreviewMessage, hasMainChatTaskResultMessage, hasMainChatWorklistCompileMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesBeforeCursor, readMainChatMessagesForScratchpadCycle, } from "./messages.js";
4
5
  export { createEmptyScratchpadProjection, readScratchpadReceiptSourceRefs, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
@@ -1,4 +1,5 @@
1
1
  export { CollaborationStateCursorError } from "./serialization.js";
2
+ export { readClarificationAnswerRevision, readSubmittedClarificationMessages, } from "./clarification-snapshot.js";
2
3
  export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
4
  export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, ensureInitialProjectWelcomeMessage, hasMainChatArtifactPreviewMessage, hasMainChatTaskResultMessage, hasMainChatWorklistCompileMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesBeforeCursor, readMainChatMessagesForScratchpadCycle, } from "./messages.js";
4
5
  export { createEmptyScratchpadProjection, readScratchpadReceiptSourceRefs, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
@@ -227,7 +227,7 @@ export function recordRunNeedsHumanResult(db, input) {
227
227
  task: updatedTask,
228
228
  task_detail: taskDetail,
229
229
  thread: mapClarificationThreadRow(thread),
230
- round: mapClarificationRoundRow(round),
230
+ round: mapClarificationRoundRow(round, db),
231
231
  card_message: mapMessageRow(cardMessage, tx),
232
232
  opening_message: mapMessageRow(openingMessage),
233
233
  };
@@ -15,8 +15,8 @@ export declare function completeTaskCompileContext(db: SqliteDatabase, input: Co
15
15
  updated_by_activity_ref?: string;
16
16
  submitted_by_activity_ref?: string;
17
17
  submitted_by_workflow_ref?: string;
18
- id: "current";
19
18
  state: "ready" | "empty" | "submitted";
19
+ id: "current";
20
20
  topic: string;
21
21
  background_summary: string;
22
22
  current_consensus: string[];
@@ -279,6 +279,7 @@ export type CreateClarificationRoundMessageInput = {
279
279
  round_id: ClarificationRoundRef;
280
280
  body: string;
281
281
  author: MessageAuthorInput;
282
+ refs?: MessageProjection["refs"];
282
283
  now?: () => Date;
283
284
  };
284
285
  export type CreateClarificationRoundAgentMessageInput = {
@@ -2,13 +2,14 @@ import type { ClarificationRoundRef, IdempotencyKey } from "@tutti/shared/ids";
2
2
  import type { SendClarificationRoundMessageDisposition, SendClarificationRoundMessagePayload, SendClarificationRoundMessageResult, SubmitClarificationRoundDisposition, SubmitClarificationRoundPayload, SubmitClarificationRoundResult } from "@tutti/shared/schemas/api";
3
3
  import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js";
4
4
  import { type HostProjectStore } from "../store/index.js";
5
- import type { ControlPlaneCommandResult, TrustedHumanAuthor } from "./types.js";
5
+ import type { ControlPlaneCommandResult } from "./types.js";
6
+ import type { TrustedCollaborationAuthor } from "../collaboration-ingestion/types.js";
6
7
  export declare function runSendClarificationRoundMessage(options: {
7
8
  store: HostProjectStore;
8
9
  events: WorkspaceEventBus;
9
10
  roundId: ClarificationRoundRef;
10
11
  payload: SendClarificationRoundMessagePayload;
11
- author: TrustedHumanAuthor;
12
+ author: TrustedCollaborationAuthor;
12
13
  now: () => Date;
13
14
  }): ControlPlaneCommandResult<SendClarificationRoundMessageDisposition, SendClarificationRoundMessageResult>;
14
15
  export declare function runSubmitClarificationRound(options: {
@@ -1,4 +1,4 @@
1
- import { createClarificationRoundMessage, readActiveClarificationProjection, submitClarificationRound, } from "../collaboration-state/index.js";
1
+ import { createClarificationRoundMessage, readActiveClarificationProjection, readClarificationAnswerRevision, submitClarificationRound, } from "../collaboration-state/index.js";
2
2
  import { withHostStoreTransaction } from "../store/index.js";
3
3
  import { enqueueControlPlaneIntent } from "./dispatch-intents.js";
4
4
  import { clarificationInvalidates } from "./invalidations.js";
@@ -19,11 +19,14 @@ export function runSendClarificationRoundMessage(options) {
19
19
  round_id: options.roundId,
20
20
  body: options.payload.body,
21
21
  author: {
22
- kind: "human",
22
+ kind: options.author.kind === "development_operator" ? "system" : "human",
23
23
  ref: options.author.account_ref,
24
24
  display_name: options.author.display_name,
25
25
  ...(options.author.avatar === undefined ? {} : { avatar: options.author.avatar }),
26
26
  },
27
+ ...(options.author.kind === "development_operator"
28
+ ? { refs: { development_operator: true } }
29
+ : {}),
27
30
  now: options.now,
28
31
  });
29
32
  options.events.publish({
@@ -41,12 +44,11 @@ export function runSendClarificationRoundMessage(options) {
41
44
  invalidates: clarificationInvalidates(options.roundId, active.thread.id),
42
45
  });
43
46
  return {
44
- disposition: { kind: "created" },
47
+ disposition: { kind: "created", message_id: message.id },
45
48
  result: { message },
46
49
  };
47
50
  }
48
51
  export function runSubmitClarificationRound(options) {
49
- void options.payload;
50
52
  const active = readActiveClarificationProjection(options.store.db);
51
53
  if (active.state === "none") {
52
54
  return { disposition: { kind: "chain_closed" } };
@@ -71,6 +73,13 @@ export function runSubmitClarificationRound(options) {
71
73
  result: { round: active.round },
72
74
  };
73
75
  }
76
+ const answerRevision = readClarificationAnswerRevision(options.store.db, options.roundId);
77
+ if (options.payload.expected_answer_revision !== undefined &&
78
+ options.payload.expected_answer_revision !== answerRevision) {
79
+ return { disposition: { kind: "answers_changed", current_answer_revision: answerRevision } };
80
+ }
81
+ if (answerRevision === 0)
82
+ return { disposition: { kind: "no_answers" } };
74
83
  const sealed = withHostStoreTransaction(options.store.db, (tx) => {
75
84
  const submitted = submitClarificationRound(tx, {
76
85
  round_id: options.roundId,
@@ -1,12 +1,10 @@
1
- import { readClarificationRoundMessagesWindow, readClarificationRoundProjection, readClarificationRoundsForThread, readTaskDetailProjection, readTaskRunResultsPage, } from "../collaboration-state/index.js";
1
+ import { clarificationAnswerRole } from "@tutti/shared/utils";
2
+ import { readSubmittedClarificationMessages, readClarificationRoundProjection, readClarificationRoundsForThread, readTaskDetailProjection, readTaskRunResultsPage, } from "../collaboration-state/index.js";
2
3
  import { toDomainRunLineage } from "./run-lineage.js";
3
4
  const CONTINUATION_CLARIFICATION_MESSAGE_LIMIT = 200;
4
5
  export function readFollowUpCheckInputFromStore(input) {
5
6
  const taskDetail = readTaskDetailProjection(input.db, input.task_id);
6
7
  const round = readClarificationRoundProjection(input.db, input.round_id);
7
- const messagesWindow = readClarificationRoundMessagesWindow(input.db, input.round_id, {
8
- anchor: "start",
9
- });
10
8
  const latestRunResult = readTaskRunResultsPage(input.db, input.task_id, {
11
9
  limit: 1,
12
10
  })?.items[0];
@@ -29,7 +27,7 @@ export function readFollowUpCheckInputFromStore(input) {
29
27
  clarification_round_ref: input.round_id,
30
28
  clarification_request_payload: round.request_payload,
31
29
  submitted_message_refs: (round.submitted_message_refs ?? []),
32
- round_messages: messagesWindow?.items ?? [],
30
+ round_messages: readSubmittedClarificationMessages(input.db, input.round_id),
33
31
  latest_run_result: latestRunResult,
34
32
  run_lineage: toDomainRunLineage(latestRunResult.run_lineage),
35
33
  },
@@ -51,18 +49,14 @@ export function readRunContinuationContextFromStore(input) {
51
49
  }
52
50
  let messagesTruncated = false;
53
51
  const resolvedRounds = rounds.map((round) => {
54
- const messagesWindow = readClarificationRoundMessagesWindow(input.db, round.id, { anchor: "start", limit: CONTINUATION_CLARIFICATION_MESSAGE_LIMIT });
55
- if (messagesWindow === null ||
56
- messagesWindow.window.has_older ||
57
- messagesWindow.window.has_newer) {
52
+ const submitted = readSubmittedClarificationMessages(input.db, round.id).filter((message) => clarificationAnswerRole(message) !== null);
53
+ if (submitted.length > CONTINUATION_CLARIFICATION_MESSAGE_LIMIT)
58
54
  messagesTruncated = true;
59
- }
60
- const submittedMessageRefs = new Set((round.submitted_message_refs ?? []));
61
- const answerMessages = (messagesWindow?.items ?? [])
62
- .filter((message) => message.author.kind === "human")
63
- .filter((message) => submittedMessageRefs.size === 0 || submittedMessageRefs.has(message.id))
55
+ const answerMessages = submitted
56
+ .slice(0, CONTINUATION_CLARIFICATION_MESSAGE_LIMIT)
64
57
  .map((message) => ({
65
58
  author: message.author,
59
+ role: clarificationAnswerRole(message),
66
60
  text: message.body,
67
61
  created_at: message.created_at,
68
62
  }));
@@ -1,7 +1,8 @@
1
1
  import { type ActivityRef, type ClarificationRoundRef, type IdempotencyKey, type TaskId, type WorkflowInvocationRef } from "@tutti/shared/ids";
2
+ import type { TrustedCollaborationAuthor } from "../collaboration-ingestion/types.js";
2
3
  import type { ExecutionStatusProjection, MessageProjection, ContextSyncDisposition, ContextSyncResult, RefreshScratchpadDisposition, RefreshScratchpadResult, RunSchedulerNowDisposition, RunSchedulerNowPayload, RetryTaskDisposition, RetryTaskPayload, RefreshReferenceSummariesDisposition, RefreshReferenceSummariesPayload, RefreshReferenceSummariesResult, SendClarificationRoundMessageDisposition, SendClarificationRoundMessagePayload, SendClarificationRoundMessageResult, SubmitClarificationRoundDisposition, SubmitClarificationRoundPayload, SubmitClarificationRoundResult, SubmitWorklistDisposition, SubmitWorklistPayload, SubmitWorklistResult, ProcedureLane } from "@tutti/shared/schemas/api";
3
4
  import type { ContextSyncWorkflowTrigger } from "./workflows/index.js";
4
- import type { ControlPlaneCommandResult, Phase5ControlPlaneOptions, StartupRecoveryOptions, StartupRecoveryResult, TrustedHumanAuthor } from "./types.js";
5
+ import type { ControlPlaneCommandResult, Phase5ControlPlaneOptions, StartupRecoveryOptions, StartupRecoveryResult } from "./types.js";
5
6
  import { type ProjectBriefRefreshReason, type RefreshProjectBriefProjectionResult } from "./project-brief-refresh.js";
6
7
  export type { ControlPlaneCommandResult, ControlPlaneLogger, Phase5ControlPlaneOptions, RunCorrectionContext, RunContinuationContext, RunDispatchContext, RunDispatchTrigger, RunPipelineInput, RunPipelineResolution, RunPipelineResolver, RunPipelineResult, RunPipelineRunner, StartupRecoveryOptions, StartupRecoveryResult, TrustedHumanAuthor, } from "./types.js";
7
8
  export declare class Phase5ControlPlane {
@@ -50,7 +51,7 @@ export declare class Phase5ControlPlane {
50
51
  startContextSyncAnalysis(trigger?: ContextSyncWorkflowTrigger): ControlPlaneCommandResult<ContextSyncDisposition, ContextSyncResult>;
51
52
  startReferenceSummaryRefresh(payload?: RefreshReferenceSummariesPayload): ControlPlaneCommandResult<RefreshReferenceSummariesDisposition, RefreshReferenceSummariesResult>;
52
53
  private tryStartPendingReferenceSummaryRefresh;
53
- sendClarificationRoundMessage(roundId: ClarificationRoundRef, payload: SendClarificationRoundMessagePayload, author: TrustedHumanAuthor): ControlPlaneCommandResult<SendClarificationRoundMessageDisposition, SendClarificationRoundMessageResult>;
54
+ sendClarificationRoundMessage(roundId: ClarificationRoundRef, payload: SendClarificationRoundMessagePayload, author: TrustedCollaborationAuthor): ControlPlaneCommandResult<SendClarificationRoundMessageDisposition, SendClarificationRoundMessageResult>;
54
55
  submitClarificationRound(roundId: ClarificationRoundRef, payload: SubmitClarificationRoundPayload, commandId: IdempotencyKey): ControlPlaneCommandResult<SubmitClarificationRoundDisposition, SubmitClarificationRoundResult>;
55
56
  waitForIdle(): Promise<void>;
56
57
  private trackAsyncOwner;
@@ -19,7 +19,9 @@ export function shouldUseMessageAsScratchpadSource(message) {
19
19
  if (message.scope.kind !== "main_chat") {
20
20
  return false;
21
21
  }
22
- if (message.message_kind === "user_text" && message.author.kind === "human") {
22
+ if (message.message_kind === "user_text" &&
23
+ (message.author.kind === "human" ||
24
+ (message.author.kind === "system" && message.refs?.development_operator === true))) {
23
25
  return true;
24
26
  }
25
27
  if (message.message_kind === "agent_text" && message.author.kind === "agent") {
@@ -31,7 +33,11 @@ export function filterScratchpadSourceMessages(messages) {
31
33
  return messages.filter(shouldUseMessageAsScratchpadSource);
32
34
  }
33
35
  function compactRole(message) {
34
- return message.author.kind === "agent" ? "tutti" : "human";
36
+ return message.refs?.development_operator === true
37
+ ? "operator"
38
+ : message.author.kind === "agent"
39
+ ? "tutti"
40
+ : "human";
35
41
  }
36
42
  export function truncateTextMiddle(text, maxChars) {
37
43
  const trimmed = text.trim();
@@ -54,7 +60,7 @@ function sourceText(message) {
54
60
  return `Reference file (${reference.category}:${reference.name})\n${message.body}`;
55
61
  }
56
62
  function sourceAuthorDisplayName(message) {
57
- if (message.author.kind !== "human") {
63
+ if (message.author.kind !== "human" && message.refs?.development_operator !== true) {
58
64
  return undefined;
59
65
  }
60
66
  const displayName = message.author.display_name?.trim();
@@ -1,3 +1,4 @@
1
+ import { clarificationAnswerRole } from "@tutti/shared/utils";
1
2
  import { readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readMessageProjectionById, readTaskCompileClarificationRoundsByWorkflowRef, } from "../collaboration-state/index.js";
2
3
  function answerMessage(store, messageId, roundId) {
3
4
  const message = readMessageProjectionById(store.db, messageId);
@@ -7,11 +8,15 @@ function answerMessage(store, messageId, roundId) {
7
8
  (message.message_kind !== "user_text" && message.message_kind !== "agent_text")) {
8
9
  return null;
9
10
  }
10
- const role = message.author.kind === "agent" ? "tutti" : "human";
11
+ const role = message.author.kind === "agent" && message.message_kind === "agent_text"
12
+ ? "tutti"
13
+ : clarificationAnswerRole(message);
14
+ if (role === null)
15
+ return null;
11
16
  const displayName = message.author.display_name?.trim();
12
17
  return {
13
18
  role,
14
- ...(role === "human" && displayName !== undefined && displayName !== ""
19
+ ...(role !== "tutti" && displayName !== undefined && displayName !== ""
15
20
  ? { author_display_name: displayName }
16
21
  : {}),
17
22
  text: message.body,
@@ -1,6 +1,6 @@
1
1
  import type { ClarificationRequestPayload, ExternalReferenceDescriptor } from "@tutti/shared/schemas/api";
2
2
  export type PromptSourceMessage = {
3
- role: "human" | "tutti";
3
+ role: "human" | "tutti" | "operator";
4
4
  author_display_name?: string;
5
5
  text: string;
6
6
  external_references?: ExternalReferenceDescriptor[];
@@ -8,10 +8,12 @@ function promptSourceMessage(db, source) {
8
8
  if (message.scope.kind !== "main_chat") {
9
9
  throw new Error("Task source message is outside main chat");
10
10
  }
11
- if (message.message_kind === "user_text" && message.author.kind === "human") {
11
+ if (message.message_kind === "user_text" &&
12
+ (message.author.kind === "human" ||
13
+ (message.author.kind === "system" && message.refs?.development_operator === true))) {
12
14
  const displayName = message.author.display_name?.trim();
13
15
  return {
14
- role: "human",
16
+ role: message.refs?.development_operator === true ? "operator" : "human",
15
17
  ...(displayName === undefined || displayName === ""
16
18
  ? {}
17
19
  : { author_display_name: displayName }),
@@ -75,6 +75,7 @@ export type RunContinuationContext = {
75
75
  request_payload: ClarificationRequestPayload;
76
76
  answer_messages: Array<{
77
77
  author: MessageProjection["author"];
78
+ role?: "human" | "operator";
78
79
  text: string;
79
80
  created_at: string;
80
81
  }>;
@@ -2,6 +2,7 @@ import { copyFileSync, mkdtempSync } from "node:fs";
2
2
  import { rm } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { extname, isAbsolute, join, relative, resolve } from "node:path";
5
+ import { clarificationAnswerRole } from "@tutti/shared/utils";
5
6
  import { renderPromptTemplate } from "../../prompt-templates/index.js";
6
7
  import { createOpenAiStructuredOutputClient, runOpenAiStructuredOutput, } from "../../providers/openai/sdk-procedure-runner.js";
7
8
  import { readOpenAiApiKeyCredential } from "../../providers/openai/credential-store.js";
@@ -162,10 +163,10 @@ function referenceFileSummaryWorkflowOutput(output, originalPath) {
162
163
  function followUpCheckPromptInput(input) {
163
164
  const submittedMessageRefs = new Set(input.submitted_message_refs);
164
165
  const answers = input.round_messages
165
- .filter((message) => message.author.kind === "human")
166
- .filter((message) => submittedMessageRefs.size === 0 || submittedMessageRefs.has(message.id))
167
- .map((message) => message.body.trim())
168
- .filter((text) => text.length > 0);
166
+ .filter((message) => clarificationAnswerRole(message) !== null)
167
+ .filter((message) => submittedMessageRefs.has(message.id))
168
+ .filter((message) => message.body.trim().length > 0)
169
+ .map((message) => ({ role: clarificationAnswerRole(message), text: message.body.trim() }));
169
170
  return {
170
171
  title: input.task_detail.task.title,
171
172
  goal: input.task_detail.detail.contract.goal,