@xfey/tutti 0.1.67 → 0.1.69

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 (52) hide show
  1. package/dist/chat-assistant/index.d.ts +1 -1
  2. package/dist/chat-assistant/index.js +1 -1
  3. package/dist/collaboration-state/index.d.ts +1 -1
  4. package/dist/collaboration-state/index.js +1 -1
  5. package/dist/collaboration-state/task-compile-context.d.ts +2 -1
  6. package/dist/collaboration-state/task-compile-context.js +30 -8
  7. package/dist/control-plane/index.d.ts +9 -2
  8. package/dist/control-plane/index.js +82 -4
  9. package/dist/control-plane/recent-references.d.ts +26 -0
  10. package/dist/control-plane/recent-references.js +94 -0
  11. package/dist/control-plane/reference-context-types.d.ts +12 -0
  12. package/dist/control-plane/reference-context-types.js +2 -0
  13. package/dist/control-plane/reference-summary-refresh.d.ts +5 -1
  14. package/dist/control-plane/reference-summary-refresh.js +181 -113
  15. package/dist/control-plane/run-start.js +12 -0
  16. package/dist/control-plane/task-compile-continuation.d.ts +2 -0
  17. package/dist/control-plane/task-compile-continuation.js +11 -0
  18. package/dist/control-plane/task-compile-start.d.ts +2 -0
  19. package/dist/control-plane/task-compile-start.js +113 -16
  20. package/dist/control-plane/types.d.ts +2 -0
  21. package/dist/control-plane/workflows/openai.js +7 -0
  22. package/dist/control-plane/workflows/types.d.ts +3 -0
  23. package/dist/providers/openai/app-server/json-rpc.js +13 -1
  24. package/dist/providers/openai/app-server/linux-sandbox-readiness.js +6 -3
  25. package/dist/providers/openai/app-server/read-only-procedure.js +2 -2
  26. package/dist/providers/openai/app-server/smoke.js +2 -2
  27. package/dist/providers/openai/app-server/workspace-write-run.js +2 -2
  28. package/dist/providers/openai/codex-app-server.d.ts +10 -0
  29. package/dist/providers/openai/codex-app-server.js +22 -5
  30. package/dist/run-pipeline/task-run-invocation.js +3 -0
  31. package/dist/server-shell/cli/host-server-runtime.js +1 -1
  32. package/dist/server-shell/http/routes/project-api/messages-routes.js +1 -1
  33. package/dist/server-shell/http/routes/project-api/reference-files-routes.js +1 -1
  34. package/dist/server-shell/local-console/invocation-context.d.ts +2 -0
  35. package/dist/server-shell/local-console/invocation-context.js +10 -1
  36. package/dist/workspace-ops/index.d.ts +1 -1
  37. package/dist/workspace-ops/index.js +1 -1
  38. package/dist/workspace-ops/reference-files.js +98 -34
  39. package/dist/workspace-ops/reference-summaries.d.ts +5 -0
  40. package/dist/workspace-ops/reference-summaries.js +6 -5
  41. package/package.json +2 -2
  42. package/prompts/README.md +1 -1
  43. package/prompts/procedures/README.md +1 -1
  44. package/prompts/procedures/task-compile.md +2 -1
  45. package/prompts/prompt-flow-map.md +9 -2
  46. package/prompts/runs/README.md +1 -1
  47. package/prompts/runs/task-continuation.md +3 -0
  48. package/prompts/runs/task-retry.md +3 -0
  49. package/prompts/runs/task-run.md +3 -0
  50. package/web/assets/{homepage-motion-scene-CblEWSta.js → homepage-motion-scene-D2GZ8Nix.js} +1 -1
  51. package/web/assets/{index-CH2ZN4W4.js → index-NsxRK6_d.js} +2 -2
  52. package/web/index.html +1 -1
@@ -43,7 +43,7 @@ export type ReadOnlyChatAssistantOptions = {
43
43
  workspaceRoot: string;
44
44
  model: ReadOnlyChatAssistantModel;
45
45
  promptsRoot?: string;
46
- onScratchpadSourceChanged?: () => void;
46
+ onScratchpadSourceChanged?: (message: MessageProjection) => void;
47
47
  now?: () => Date;
48
48
  };
49
49
  export declare function isChatAssistantOutput(value: unknown): value is ChatAssistantOutput;
@@ -206,7 +206,7 @@ export class ReadOnlyChatAssistant {
206
206
  invalidates: mainChatMessageInvalidates(),
207
207
  });
208
208
  if (refs?.scratchpad_source !== "exclude") {
209
- this.options.onScratchpadSourceChanged?.();
209
+ this.options.onScratchpadSourceChanged?.(message);
210
210
  }
211
211
  }
212
212
  appendClarificationRoundAssistantMessage(input) {
@@ -3,7 +3,7 @@ export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
3
  export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, ensureInitialProjectWelcomeMessage, hasMainChatArtifactPreviewMessage, hasMainChatTaskResultMessage, hasMainChatWorklistCompileMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesBeforeCursor, readMainChatMessagesForScratchpadCycle, } from "./messages.js";
4
4
  export { createEmptyScratchpadProjection, readScratchpadReceiptSourceRefs, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
5
5
  export { markScratchpadRefreshFailed, markScratchpadRefreshStarted, markScratchpadRefreshSucceeded, markScratchpadSourceDirty, readScratchpadSourceState, } from "./scratchpad-source-state.js";
6
- export { captureTaskCompileContext, completeTaskCompileContext, readActiveTaskCompileContext, rollbackTaskCompileContext, taskCompileContextSnapshot, } from "./task-compile-context.js";
6
+ export { captureTaskCompileContext, completeTaskCompileContext, readActiveTaskCompileContext, readCurrentScratchpadSourceRefs, rollbackTaskCompileContext, taskCompileContextSnapshot, } from "./task-compile-context.js";
7
7
  export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readSealedClarificationRoundsWithoutSuccessor, } from "./clarification-records.js";
8
8
  export { createClarificationRoundAgentMessage, createClarificationRoundMessage, readClarificationRoundMessagesWindow, } from "./clarification-messages.js";
9
9
  export { openNextTaskBoundClarificationRound, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, } from "./clarification-rounds.js";
@@ -3,7 +3,7 @@ export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
3
  export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, ensureInitialProjectWelcomeMessage, hasMainChatArtifactPreviewMessage, hasMainChatTaskResultMessage, hasMainChatWorklistCompileMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesBeforeCursor, readMainChatMessagesForScratchpadCycle, } from "./messages.js";
4
4
  export { createEmptyScratchpadProjection, readScratchpadReceiptSourceRefs, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
5
5
  export { markScratchpadRefreshFailed, markScratchpadRefreshStarted, markScratchpadRefreshSucceeded, markScratchpadSourceDirty, readScratchpadSourceState, } from "./scratchpad-source-state.js";
6
- export { captureTaskCompileContext, completeTaskCompileContext, readActiveTaskCompileContext, rollbackTaskCompileContext, taskCompileContextSnapshot, } from "./task-compile-context.js";
6
+ export { captureTaskCompileContext, completeTaskCompileContext, readActiveTaskCompileContext, readCurrentScratchpadSourceRefs, rollbackTaskCompileContext, taskCompileContextSnapshot, } from "./task-compile-context.js";
7
7
  export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readSealedClarificationRoundsWithoutSuccessor, } from "./clarification-records.js";
8
8
  export { createClarificationRoundAgentMessage, createClarificationRoundMessage, readClarificationRoundMessagesWindow, } from "./clarification-messages.js";
9
9
  export { openNextTaskBoundClarificationRound, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, } from "./clarification-rounds.js";
@@ -1,7 +1,8 @@
1
1
  import type { WorkflowInvocationRef } from "@tutti/shared/ids";
2
2
  import { type SqliteDatabase } from "../store/index.js";
3
- import type { CaptureTaskCompileContextInput, CompleteTaskCompileContextInput, RollbackTaskCompileContextInput, ScratchpadContentSnapshot, TaskCompileContextRecord } from "./types.js";
3
+ import type { CaptureTaskCompileContextInput, CompleteTaskCompileContextInput, RollbackTaskCompileContextInput, ScratchpadContentSnapshot, ScratchpadSourceRefInput, TaskCompileContextRecord } from "./types.js";
4
4
  export declare function readActiveTaskCompileContext(db: SqliteDatabase, workflowRef?: WorkflowInvocationRef): TaskCompileContextRecord | null;
5
+ export declare function readCurrentScratchpadSourceRefs(db: SqliteDatabase): ScratchpadSourceRefInput[];
5
6
  export declare function captureTaskCompileContext(db: SqliteDatabase, input: CaptureTaskCompileContextInput): TaskCompileContextRecord;
6
7
  export declare function completeTaskCompileContext(db: SqliteDatabase, input: CompleteTaskCompileContextInput): {
7
8
  updated_at?: string;
@@ -2,6 +2,16 @@ import { withHostStoreTransaction } from "../store/index.js";
2
2
  import { readScratchpadProjection } from "./scratchpad.js";
3
3
  import { readScratchpadSourceState } from "./scratchpad-source-state.js";
4
4
  import { nowIso, stringifyJson } from "./serialization.js";
5
+ function cursorIsAfter(left, right) {
6
+ if (left === undefined) {
7
+ return false;
8
+ }
9
+ if (right === undefined) {
10
+ return true;
11
+ }
12
+ const timeOrder = left.created_at.localeCompare(right.created_at);
13
+ return timeOrder > 0 || (timeOrder === 0 && left.message_id > right.message_id);
14
+ }
5
15
  function parseStringArray(value, field) {
6
16
  let parsed;
7
17
  try {
@@ -89,7 +99,7 @@ export function readActiveTaskCompileContext(db, workflowRef) {
89
99
  }
90
100
  return mapContextRow(row, readContextSources(db, row.workflow_ref));
91
101
  }
92
- function currentScratchpadSources(db) {
102
+ export function readCurrentScratchpadSourceRefs(db) {
93
103
  return db
94
104
  .prepare(`
95
105
  SELECT message_id, created_at
@@ -98,7 +108,12 @@ function currentScratchpadSources(db) {
98
108
  AND source_scope = 'main_chat'
99
109
  ORDER BY created_at ASC, message_id ASC
100
110
  `)
101
- .all();
111
+ .all()
112
+ .map((source) => ({
113
+ message_id: source.message_id,
114
+ source_scope: "main_chat",
115
+ created_at: source.created_at,
116
+ }));
102
117
  }
103
118
  export function captureTaskCompileContext(db, input) {
104
119
  return withHostStoreTransaction(db, (tx) => {
@@ -114,7 +129,7 @@ export function captureTaskCompileContext(db, input) {
114
129
  throw new Error("A ready Scratchpad is required to capture Task Compile Context");
115
130
  }
116
131
  const sourceState = readScratchpadSourceState(tx);
117
- const sources = currentScratchpadSources(tx);
132
+ const sources = readCurrentScratchpadSourceRefs(tx);
118
133
  const lastSource = sources.at(-1);
119
134
  const through = sourceState.refreshed_through_cursor ??
120
135
  (lastSource === undefined
@@ -233,11 +248,18 @@ export function rollbackTaskCompileContext(db, input) {
233
248
  }
234
249
  const rolledBackAt = nowIso(input.now);
235
250
  const sourceState = readScratchpadSourceState(tx);
236
- const dirtySince = sourceState.in_flight !== undefined &&
237
- new Date(rolledBackAt).getTime() <= new Date(sourceState.in_flight.started_at).getTime()
238
- ? new Date(new Date(sourceState.in_flight.started_at).getTime() + 1).toISOString()
239
- : rolledBackAt;
240
251
  const after = context.source_window.after;
252
+ const through = context.source_window.through;
253
+ const hasBoundaryAfterSourceChanges = sourceState.dirty_since !== undefined ||
254
+ sourceState.in_flight !== undefined ||
255
+ cursorIsAfter(sourceState.refreshed_through_cursor, through);
256
+ const dirtySince = hasBoundaryAfterSourceChanges
257
+ ? sourceState.in_flight !== undefined &&
258
+ new Date(rolledBackAt).getTime() <= new Date(sourceState.in_flight.started_at).getTime()
259
+ ? new Date(new Date(sourceState.in_flight.started_at).getTime() + 1).toISOString()
260
+ : rolledBackAt
261
+ : null;
262
+ const restoredThrough = hasBoundaryAfterSourceChanges ? after : through;
241
263
  tx.prepare(`
242
264
  INSERT INTO scratchpad_source_state (
243
265
  id,
@@ -256,7 +278,7 @@ export function rollbackTaskCompileContext(db, input) {
256
278
  refreshed_through_cursor_message_id = excluded.refreshed_through_cursor_message_id,
257
279
  dirty_since = excluded.dirty_since,
258
280
  updated_at = excluded.updated_at
259
- `).run(after?.created_at ?? null, after?.message_id ?? null, after?.created_at ?? null, after?.message_id ?? null, dirtySince, rolledBackAt);
281
+ `).run(after?.created_at ?? null, after?.message_id ?? null, restoredThrough?.created_at ?? null, restoredThrough?.message_id ?? null, dirtySince, rolledBackAt);
260
282
  tx.prepare("DELETE FROM active_task_compile_context WHERE workflow_ref = ?").run(input.workflow_ref);
261
283
  return true;
262
284
  });
@@ -1,5 +1,5 @@
1
1
  import { type ActivityRef, type ClarificationRoundRef, type IdempotencyKey, type WorkflowInvocationRef } from "@tutti/shared/ids";
2
- import type { ExecutionStatusProjection, ContextSyncDisposition, ContextSyncResult, RefreshScratchpadDisposition, RefreshScratchpadResult, RunSchedulerNowDisposition, RunSchedulerNowPayload, RefreshReferenceSummariesDisposition, RefreshReferenceSummariesPayload, RefreshReferenceSummariesResult, SendClarificationRoundMessageDisposition, SendClarificationRoundMessagePayload, SendClarificationRoundMessageResult, SubmitClarificationRoundDisposition, SubmitClarificationRoundPayload, SubmitClarificationRoundResult, SubmitWorklistDisposition, SubmitWorklistPayload, SubmitWorklistResult, ProcedureLane } from "@tutti/shared/schemas/api";
2
+ import type { ExecutionStatusProjection, MessageProjection, ContextSyncDisposition, ContextSyncResult, RefreshScratchpadDisposition, RefreshScratchpadResult, RunSchedulerNowDisposition, RunSchedulerNowPayload, RefreshReferenceSummariesDisposition, RefreshReferenceSummariesPayload, RefreshReferenceSummariesResult, SendClarificationRoundMessageDisposition, SendClarificationRoundMessagePayload, SendClarificationRoundMessageResult, SubmitClarificationRoundDisposition, SubmitClarificationRoundPayload, SubmitClarificationRoundResult, SubmitWorklistDisposition, SubmitWorklistPayload, SubmitWorklistResult, ProcedureLane } from "@tutti/shared/schemas/api";
3
3
  import type { ContextSyncWorkflowTrigger } from "./workflows/index.js";
4
4
  import type { ControlPlaneCommandResult, Phase5ControlPlaneOptions, StartupRecoveryOptions, StartupRecoveryResult, TrustedHumanAuthor } from "./types.js";
5
5
  import { type ProjectBriefRefreshReason, type RefreshProjectBriefProjectionResult } from "./project-brief-refresh.js";
@@ -18,6 +18,10 @@ export declare class Phase5ControlPlane {
18
18
  private readonly runLifecycle;
19
19
  private readonly now;
20
20
  private readonly scratchpadAutoRefreshScheduler;
21
+ private readonly pendingReferenceSummaryPaths;
22
+ private readonly referenceSummaryWaiters;
23
+ private pendingAllReferenceSummaries;
24
+ private referenceSummaryStartScheduled;
21
25
  private lastRecoveryConclusion;
22
26
  constructor(options: Phase5ControlPlaneOptions);
23
27
  getExecutionStatus(): ExecutionStatusProjection;
@@ -26,7 +30,7 @@ export declare class Phase5ControlPlane {
26
30
  startScratchpadRefresh(options?: {
27
31
  lane?: ProcedureLane;
28
32
  }): ControlPlaneCommandResult<RefreshScratchpadDisposition, RefreshScratchpadResult>;
29
- notifyScratchpadSourceChanged(): void;
33
+ notifyScratchpadSourceChanged(message: MessageProjection): void;
30
34
  refreshProjectBrief(options: {
31
35
  reason: ProjectBriefRefreshReason;
32
36
  workflowRef?: WorkflowInvocationRef;
@@ -36,9 +40,12 @@ export declare class Phase5ControlPlane {
36
40
  startTaskCompile(payload: SubmitWorklistPayload): ControlPlaneCommandResult<SubmitWorklistDisposition, SubmitWorklistResult>;
37
41
  startContextSyncAnalysis(trigger?: ContextSyncWorkflowTrigger): ControlPlaneCommandResult<ContextSyncDisposition, ContextSyncResult>;
38
42
  startReferenceSummaryRefresh(payload?: RefreshReferenceSummariesPayload): ControlPlaneCommandResult<RefreshReferenceSummariesDisposition, RefreshReferenceSummariesResult>;
43
+ private tryStartPendingReferenceSummaryRefresh;
39
44
  sendClarificationRoundMessage(roundId: ClarificationRoundRef, payload: SendClarificationRoundMessagePayload, author: TrustedHumanAuthor): ControlPlaneCommandResult<SendClarificationRoundMessageDisposition, SendClarificationRoundMessageResult>;
40
45
  submitClarificationRound(roundId: ClarificationRoundRef, payload: SubmitClarificationRoundPayload, commandId: IdempotencyKey): ControlPlaneCommandResult<SubmitClarificationRoundDisposition, SubmitClarificationRoundResult>;
41
46
  waitForIdle(): Promise<void>;
47
+ private hasPendingReferenceSummaryRefresh;
48
+ private schedulePendingReferenceSummaryRefresh;
42
49
  private startRunForTask;
43
50
  private readActiveRun;
44
51
  private readActiveProcedures;
@@ -9,6 +9,7 @@ import { runControlPlaneStartupRecovery } from "./startup-recovery.js";
9
9
  import { readControlPlaneExecutionStatus } from "./execution-status.js";
10
10
  import { ScratchpadAutoRefreshScheduler } from "./scratchpad-auto-refresh.js";
11
11
  import { startControlPlaneScratchpadRefresh } from "./scratchpad-refresh-start.js";
12
+ import { shouldUseMessageAsScratchpadSource } from "./scratchpad-source-messages.js";
12
13
  import { startControlPlaneTaskCompile } from "./task-compile-start.js";
13
14
  import { startControlPlaneTaskCompileContinuation } from "./task-compile-continuation.js";
14
15
  import { startControlPlaneTaskBoundFollowUpCheck } from "./follow-up-start.js";
@@ -30,6 +31,10 @@ export class Phase5ControlPlane {
30
31
  runLifecycle;
31
32
  now;
32
33
  scratchpadAutoRefreshScheduler;
34
+ pendingReferenceSummaryPaths = new Set();
35
+ referenceSummaryWaiters = new Map();
36
+ pendingAllReferenceSummaries = false;
37
+ referenceSummaryStartScheduled = false;
33
38
  lastRecoveryConclusion = null;
34
39
  constructor(options) {
35
40
  this.store = options.store;
@@ -102,7 +107,10 @@ export class Phase5ControlPlane {
102
107
  publishProcedureTransition: (state) => this.publishProcedureTransition(state),
103
108
  });
104
109
  }
105
- notifyScratchpadSourceChanged() {
110
+ notifyScratchpadSourceChanged(message) {
111
+ if (!shouldUseMessageAsScratchpadSource(message)) {
112
+ return;
113
+ }
106
114
  this.scratchpadAutoRefreshScheduler.notifySourceChanged();
107
115
  }
108
116
  refreshProjectBrief(options) {
@@ -139,6 +147,11 @@ export class Phase5ControlPlane {
139
147
  this.tryRunTaskArrivalScheduler();
140
148
  }
141
149
  },
150
+ registerReferenceSummaryWaiter: (activityRef, waiter) => {
151
+ const existing = this.referenceSummaryWaiters.get(activityRef) ?? [];
152
+ existing.push(waiter);
153
+ this.referenceSummaryWaiters.set(activityRef, existing);
154
+ },
142
155
  onTasksAdded: () => {
143
156
  tasksAdded = true;
144
157
  },
@@ -159,11 +172,25 @@ export class Phase5ControlPlane {
159
172
  });
160
173
  }
161
174
  startReferenceSummaryRefresh(payload = {}) {
162
- return startReferenceSummaryRefreshProcedure({
175
+ if (payload.paths === undefined) {
176
+ this.pendingAllReferenceSummaries = true;
177
+ this.pendingReferenceSummaryPaths.clear();
178
+ }
179
+ else {
180
+ for (const path of payload.paths) {
181
+ this.pendingReferenceSummaryPaths.add(path);
182
+ }
183
+ }
184
+ return this.tryStartPendingReferenceSummaryRefresh();
185
+ }
186
+ tryStartPendingReferenceSummaryRefresh() {
187
+ const refreshAll = this.pendingAllReferenceSummaries;
188
+ const paths = refreshAll ? undefined : [...this.pendingReferenceSummaryPaths];
189
+ const result = startReferenceSummaryRefreshProcedure({
163
190
  engine: this.engine,
164
191
  store: this.store,
165
192
  events: this.events,
166
- ...(payload.paths === undefined ? {} : { paths: payload.paths }),
193
+ ...(paths === undefined ? {} : { paths }),
167
194
  resolveWorkflowRunner: this.resolveWorkflowRunner,
168
195
  readActiveActivity: () => this.readSchedulerBlocker(),
169
196
  projectContext: this.projectContext,
@@ -171,6 +198,18 @@ export class Phase5ControlPlane {
171
198
  now: this.now,
172
199
  publishProcedureTransition: (state) => this.publishProcedureTransition(state),
173
200
  });
201
+ if (result.disposition.kind !== "already_running") {
202
+ if (refreshAll) {
203
+ this.pendingAllReferenceSummaries = false;
204
+ this.pendingReferenceSummaryPaths.clear();
205
+ }
206
+ else {
207
+ for (const path of paths ?? []) {
208
+ this.pendingReferenceSummaryPaths.delete(path);
209
+ }
210
+ }
211
+ }
212
+ return result;
174
213
  }
175
214
  sendClarificationRoundMessage(roundId, payload, author) {
176
215
  return runSendClarificationRoundMessage({
@@ -198,11 +237,29 @@ export class Phase5ControlPlane {
198
237
  while (true) {
199
238
  await this.engine.waitForIdle();
200
239
  await this.runEngine.waitForIdle();
201
- if (this.readAnyActiveActivity() === null) {
240
+ await Promise.resolve();
241
+ if (this.readAnyActiveActivity() === null &&
242
+ !this.hasPendingReferenceSummaryRefresh()) {
202
243
  return;
203
244
  }
245
+ this.schedulePendingReferenceSummaryRefresh();
204
246
  }
205
247
  }
248
+ hasPendingReferenceSummaryRefresh() {
249
+ return this.pendingAllReferenceSummaries || this.pendingReferenceSummaryPaths.size !== 0;
250
+ }
251
+ schedulePendingReferenceSummaryRefresh() {
252
+ if (!this.hasPendingReferenceSummaryRefresh() || this.referenceSummaryStartScheduled) {
253
+ return;
254
+ }
255
+ this.referenceSummaryStartScheduled = true;
256
+ queueMicrotask(() => {
257
+ this.referenceSummaryStartScheduled = false;
258
+ if (this.hasPendingReferenceSummaryRefresh()) {
259
+ void this.tryStartPendingReferenceSummaryRefresh();
260
+ }
261
+ });
262
+ }
206
263
  startRunForTask(input) {
207
264
  const result = startControlPlaneRunForTask({
208
265
  runEngine: this.runEngine,
@@ -304,6 +361,9 @@ export class Phase5ControlPlane {
304
361
  onTerminal: (terminalState) => this.handleRunTerminalTransition(terminalState),
305
362
  });
306
363
  this.scratchpadAutoRefreshScheduler.notifyActivityChanged();
364
+ if (state.transition_kind === "terminal") {
365
+ this.schedulePendingReferenceSummaryRefresh();
366
+ }
307
367
  }
308
368
  handleRunTerminalTransition(state) {
309
369
  if (state.activity.status === "finished") {
@@ -377,6 +437,7 @@ export class Phase5ControlPlane {
377
437
  owner,
378
438
  roundId,
379
439
  resolveWorkflowRunner: this.resolveWorkflowRunner,
440
+ projectContext: this.projectContext,
380
441
  logger: this.logger,
381
442
  now: this.now,
382
443
  publishProcedureTransition: (state) => {
@@ -421,6 +482,23 @@ export class Phase5ControlPlane {
421
482
  getExecutionStatus: () => this.getExecutionStatus(),
422
483
  });
423
484
  this.scratchpadAutoRefreshScheduler.notifyActivityChanged();
485
+ if (state.transition_kind === "terminal") {
486
+ const activityRef = state.activity.activity_ref;
487
+ const waiters = this.referenceSummaryWaiters.get(activityRef) ?? [];
488
+ this.referenceSummaryWaiters.delete(activityRef);
489
+ for (const waiter of waiters) {
490
+ try {
491
+ waiter(state);
492
+ }
493
+ catch (error) {
494
+ this.logger?.info({
495
+ activity_ref: activityRef,
496
+ error_name: error instanceof Error ? error.name : "UnknownError",
497
+ }, "Reference summary continuation failed");
498
+ }
499
+ }
500
+ this.schedulePendingReferenceSummaryRefresh();
501
+ }
424
502
  }
425
503
  logProcedureTransition(state) {
426
504
  const activity = state.activity;
@@ -0,0 +1,26 @@
1
+ import type { MessageId } from "@tutti/shared/ids";
2
+ import type { MessageReferenceFileRef } from "@tutti/shared/schemas/api";
3
+ import type { ScratchpadSourceRefInput } from "../collaboration-state/types.js";
4
+ import type { SqliteDatabase } from "../store/index.js";
5
+ import type { RecentReferenceContext } from "./reference-context-types.js";
6
+ export type RecentReferenceSource = MessageReferenceFileRef & {
7
+ message_id: MessageId;
8
+ };
9
+ export declare function readRecentReferenceSources(db: SqliteDatabase, sourceRefs: Array<Pick<ScratchpadSourceRefInput, "message_id">>): RecentReferenceSource[];
10
+ export declare function readPendingRecentReferenceSummaryPaths(options: {
11
+ workspaceRoot: string;
12
+ sources: RecentReferenceSource[];
13
+ now: () => Date;
14
+ }): string[];
15
+ export declare function materializeRecentReferences(options: {
16
+ workspaceRoot: string;
17
+ sources: RecentReferenceSource[];
18
+ now: () => Date;
19
+ }): RecentReferenceContext[];
20
+ export declare function materializeWorkflowRecentReferences(options: {
21
+ db: SqliteDatabase;
22
+ workspaceRoot: string;
23
+ workflowRef: string;
24
+ now: () => Date;
25
+ }): RecentReferenceContext[];
26
+ //# sourceMappingURL=recent-references.d.ts.map
@@ -0,0 +1,94 @@
1
+ import { readMessageProjectionById, readScratchpadReceiptSourceRefs, } from "../collaboration-state/index.js";
2
+ import { readReferenceSummaryEntries } from "../workspace-ops/index.js";
3
+ export function readRecentReferenceSources(db, sourceRefs) {
4
+ const sources = [];
5
+ const seen = new Set();
6
+ for (const sourceRef of sourceRefs) {
7
+ const message = readMessageProjectionById(db, sourceRef.message_id);
8
+ const reference = message?.refs?.reference_file;
9
+ if (message?.author.kind !== "human" ||
10
+ reference === undefined ||
11
+ reference.source !== "human_upload") {
12
+ continue;
13
+ }
14
+ const identity = `${reference.path}\u0000${reference.blob_oid ?? ""}`;
15
+ if (seen.has(identity)) {
16
+ continue;
17
+ }
18
+ seen.add(identity);
19
+ sources.push({ message_id: sourceRef.message_id, ...reference });
20
+ }
21
+ return sources;
22
+ }
23
+ export function readPendingRecentReferenceSummaryPaths(options) {
24
+ const paths = [...new Set(options.sources.map((source) => source.path))];
25
+ if (paths.length === 0) {
26
+ return [];
27
+ }
28
+ const entries = new Map(readReferenceSummaryEntries({
29
+ workspaceRoot: options.workspaceRoot,
30
+ paths,
31
+ now: options.now,
32
+ }).map((entry) => [entry.path, entry]));
33
+ const pending = new Set();
34
+ for (const source of options.sources) {
35
+ const entry = entries.get(source.path);
36
+ if (source.blob_oid === undefined ||
37
+ entry === undefined ||
38
+ entry.blob_oid !== source.blob_oid) {
39
+ continue;
40
+ }
41
+ if (entry.summary.status === "missing" || entry.summary.status === "stale") {
42
+ pending.add(source.path);
43
+ }
44
+ }
45
+ return [...pending];
46
+ }
47
+ export function materializeRecentReferences(options) {
48
+ const paths = [...new Set(options.sources.map((source) => source.path))];
49
+ const entries = new Map(paths.length === 0
50
+ ? []
51
+ : readReferenceSummaryEntries({
52
+ workspaceRoot: options.workspaceRoot,
53
+ paths,
54
+ now: options.now,
55
+ }).map((entry) => [entry.path, entry]));
56
+ return options.sources.map((source) => {
57
+ const entry = entries.get(source.path);
58
+ if (source.blob_oid === undefined) {
59
+ return unavailableRecentReference(source, "source_blob_unavailable");
60
+ }
61
+ if (entry === undefined) {
62
+ return unavailableRecentReference(source, "reference_unavailable");
63
+ }
64
+ if (entry.blob_oid !== source.blob_oid) {
65
+ return unavailableRecentReference(source, "reference_source_changed");
66
+ }
67
+ if (entry.summary.status === "available" && entry.summary.text !== undefined) {
68
+ return {
69
+ path: source.path,
70
+ name: source.name,
71
+ summary: { status: "available", text: entry.summary.text },
72
+ };
73
+ }
74
+ if (entry.summary.status === "unavailable") {
75
+ return unavailableRecentReference(source, entry.summary.unavailable_reason ?? "summary_unavailable");
76
+ }
77
+ return unavailableRecentReference(source, "summary_not_ready");
78
+ });
79
+ }
80
+ export function materializeWorkflowRecentReferences(options) {
81
+ return materializeRecentReferences({
82
+ workspaceRoot: options.workspaceRoot,
83
+ sources: readRecentReferenceSources(options.db, readScratchpadReceiptSourceRefs(options.db, options.workflowRef)),
84
+ now: options.now,
85
+ });
86
+ }
87
+ function unavailableRecentReference(source, reason) {
88
+ return {
89
+ path: source.path,
90
+ name: source.name,
91
+ summary: { status: "unavailable", reason },
92
+ };
93
+ }
94
+ //# sourceMappingURL=recent-references.js.map
@@ -0,0 +1,12 @@
1
+ export type RecentReferenceContext = {
2
+ path: string;
3
+ name: string;
4
+ summary: {
5
+ status: "available";
6
+ text: string;
7
+ } | {
8
+ status: "unavailable";
9
+ reason: string;
10
+ };
11
+ };
12
+ //# sourceMappingURL=reference-context-types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reference-context-types.js.map
@@ -1,4 +1,4 @@
1
- import type { ActivityRef } from "@tutti/shared/ids";
1
+ import type { ActivityRef, WorkflowInvocationRef } from "@tutti/shared/ids";
2
2
  import type { RefreshReferenceSummariesDisposition, RefreshReferenceSummariesResult } from "@tutti/shared/schemas/api";
3
3
  import type { ProcedureEngine, ProcedureEngineState } from "../procedure-engine/index.js";
4
4
  import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js";
@@ -17,6 +17,10 @@ export declare function startReferenceSummaryRefreshProcedure(input: {
17
17
  projectContext: Phase5ControlPlaneOptions["projectContext"];
18
18
  logger?: ControlPlaneLogger | undefined;
19
19
  now: () => Date;
20
+ onExecuteStart?: (input: {
21
+ activityRef: ActivityRef;
22
+ workflowRef: WorkflowInvocationRef;
23
+ }) => void;
20
24
  publishProcedureTransition: (state: ProcedureEngineState) => void;
21
25
  }): ControlPlaneCommandResult<RefreshReferenceSummariesDisposition, RefreshReferenceSummariesResult>;
22
26
  //# sourceMappingURL=reference-summary-refresh.d.ts.map