@xfey/tutti 0.1.66 → 0.1.68

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 (38) hide show
  1. package/dist/collaboration-state/index.d.ts +1 -1
  2. package/dist/collaboration-state/index.js +1 -1
  3. package/dist/collaboration-state/task-compile-context.d.ts +2 -1
  4. package/dist/collaboration-state/task-compile-context.js +8 -3
  5. package/dist/control-plane/index.d.ts +7 -0
  6. package/dist/control-plane/index.js +77 -3
  7. package/dist/control-plane/recent-references.d.ts +26 -0
  8. package/dist/control-plane/recent-references.js +94 -0
  9. package/dist/control-plane/reference-context-types.d.ts +12 -0
  10. package/dist/control-plane/reference-context-types.js +2 -0
  11. package/dist/control-plane/reference-summary-refresh.d.ts +5 -1
  12. package/dist/control-plane/reference-summary-refresh.js +181 -113
  13. package/dist/control-plane/run-start.js +12 -0
  14. package/dist/control-plane/task-compile-continuation.d.ts +2 -0
  15. package/dist/control-plane/task-compile-continuation.js +11 -0
  16. package/dist/control-plane/task-compile-start.d.ts +2 -0
  17. package/dist/control-plane/task-compile-start.js +113 -16
  18. package/dist/control-plane/types.d.ts +2 -0
  19. package/dist/control-plane/workflows/openai.js +7 -0
  20. package/dist/control-plane/workflows/types.d.ts +3 -0
  21. package/dist/run-pipeline/task-run-invocation.js +3 -0
  22. package/dist/workspace-ops/index.d.ts +1 -1
  23. package/dist/workspace-ops/index.js +1 -1
  24. package/dist/workspace-ops/reference-files.js +98 -34
  25. package/dist/workspace-ops/reference-summaries.d.ts +5 -0
  26. package/dist/workspace-ops/reference-summaries.js +6 -5
  27. package/package.json +2 -2
  28. package/prompts/README.md +1 -1
  29. package/prompts/procedures/README.md +1 -1
  30. package/prompts/procedures/task-compile.md +2 -1
  31. package/prompts/prompt-flow-map.md +9 -2
  32. package/prompts/runs/README.md +1 -1
  33. package/prompts/runs/task-continuation.md +3 -0
  34. package/prompts/runs/task-retry.md +3 -0
  35. package/prompts/runs/task-run.md +3 -0
  36. package/web/assets/{homepage-motion-scene-Doj_Soje.js → homepage-motion-scene-D2GZ8Nix.js} +1 -1
  37. package/web/assets/{index-DuT1ti0p.js → index-NsxRK6_d.js} +2 -2
  38. package/web/index.html +1 -1
@@ -2,6 +2,8 @@ import { createWorkflowInvocationRef } from "@tutti/shared/ids";
2
2
  import { recordProjectTimelineEvent } from "../project-timeline/index.js";
3
3
  import { WorkspaceOpsError, readReferenceSummaryTargets, updateReferenceSummaries, } from "../workspace-ops/index.js";
4
4
  import { logProcedureExecutionError } from "./procedure-logging.js";
5
+ const REFERENCE_SUMMARY_CONCURRENCY = 2;
6
+ const REFERENCE_SUMMARY_MAX_ATTEMPTS = 2;
5
7
  function publishReferenceSummaryTimelineChanged(input) {
6
8
  input.events.publish({
7
9
  event_type: "query.invalidate",
@@ -62,111 +64,104 @@ function logNotStarted(input) {
62
64
  }, "Reference summary refresh not started");
63
65
  }
64
66
  async function runReferenceSummaryRefresh(options) {
65
- const summaries = [];
67
+ let targets = options.targets;
68
+ const processedTargets = new Set();
69
+ let targetCount = 0;
70
+ let updatedCount = 0;
66
71
  let failedCount = 0;
67
72
  let adapterUnavailableCount = 0;
68
- for (const target of options.targets) {
73
+ let lastCommitOid;
74
+ while (targets.length !== 0) {
75
+ const batch = targets.filter((target) => !processedTargets.has(`${target.path}\u0000${target.blob_oid}`));
76
+ if (batch.length === 0) {
77
+ break;
78
+ }
79
+ for (const target of batch) {
80
+ processedTargets.add(`${target.path}\u0000${target.blob_oid}`);
81
+ }
82
+ targetCount += batch.length;
83
+ options.reportProgress?.({
84
+ phase: "reference_summary_batch",
85
+ summary: `Summarizing ${batch.length} reference file${batch.length === 1 ? "" : "s"}.`,
86
+ });
87
+ let completedInBatch = 0;
88
+ const batchResults = await mapWithConcurrency(batch, REFERENCE_SUMMARY_CONCURRENCY, async (target) => {
89
+ const result = await summarizeReferenceTarget({
90
+ target,
91
+ runner: options.runner,
92
+ workflowRef: options.workflowRef,
93
+ activityRef: options.activityRef,
94
+ logger: options.logger,
95
+ now: options.now,
96
+ });
97
+ completedInBatch += 1;
98
+ options.reportProgress?.({
99
+ phase: "reference_summary_batch",
100
+ summary: `Processed ${completedInBatch} of ${batch.length} reference files.`,
101
+ });
102
+ return result;
103
+ });
104
+ const summaries = batchResults.map((entry) => entry.summary);
105
+ failedCount += batchResults.filter((entry) => entry.failed).length;
106
+ adapterUnavailableCount += batchResults.filter((entry) => entry.adapterUnavailable).length;
107
+ let result;
69
108
  try {
70
- const output = await options.runner.runReferenceFileSummary?.({
71
- workflow_ref: options.workflowRef,
72
- activity_ref: options.activityRef,
73
- file: {
74
- path: target.path,
75
- name: target.name,
76
- category: target.category,
77
- size_bytes: target.size_bytes,
78
- blob_oid: target.blob_oid,
79
- ...(target.media_type === undefined ? {} : { media_type: target.media_type }),
80
- },
81
- }, {
82
- workflow_ref: options.workflowRef,
83
- activity_ref: options.activityRef,
84
- ...(options.reportProgress === undefined
85
- ? {}
86
- : { reportProgress: options.reportProgress }),
109
+ result = updateReferenceSummaries({
110
+ workspaceRoot: options.projectContext.workspaceRoot,
111
+ summaries,
112
+ now: options.now,
87
113
  });
88
- if (output === undefined) {
89
- adapterUnavailableCount += 1;
90
- summaries.push({
91
- path: target.path,
92
- status: "unavailable",
93
- generated_at: options.now().toISOString(),
94
- source_blob_oid: target.blob_oid,
95
- unavailable_reason: "summary_adapter_unavailable",
96
- });
97
- continue;
98
- }
99
- summaries.push(summaryRecordFromOutput({
100
- target,
101
- output,
102
- generatedAt: options.now().toISOString(),
103
- }));
104
114
  }
105
115
  catch (error) {
106
- failedCount += 1;
107
- options.logger?.info({
108
- workflow_ref: options.workflowRef,
116
+ recordProjectTimelineEvent(options.store.db, {
117
+ event_kind: "reference_summary",
118
+ status: "failed",
119
+ summary: "Reference summary write failed.",
109
120
  activity_ref: options.activityRef,
110
- path: target.path,
111
- error_name: error instanceof Error ? error.name : "UnknownError",
112
- }, "Reference summary target failed");
113
- summaries.push({
114
- path: target.path,
115
- status: "unavailable",
116
- generated_at: options.now().toISOString(),
117
- source_blob_oid: target.blob_oid,
118
- unavailable_reason: "summary_failed",
121
+ workflow_ref: options.workflowRef,
122
+ meta: [
123
+ `target count: ${targetCount}`,
124
+ `error: ${error instanceof Error ? error.name : "UnknownError"}`,
125
+ ],
126
+ now: options.now,
127
+ });
128
+ publishReferenceSummaryTimelineChanged({
129
+ events: options.events,
130
+ message: "Reference summary timeline updated.",
119
131
  });
132
+ throw error;
120
133
  }
121
- }
122
- let result;
123
- try {
124
- result = updateReferenceSummaries({
125
- workspaceRoot: options.projectContext.workspaceRoot,
126
- summaries,
127
- now: options.now,
128
- });
129
- }
130
- catch (error) {
131
- recordProjectTimelineEvent(options.store.db, {
132
- event_kind: "reference_summary",
133
- status: "failed",
134
- summary: "Reference summary write failed.",
135
- activity_ref: options.activityRef,
136
- workflow_ref: options.workflowRef,
137
- meta: [
138
- `target count: ${options.targets.length}`,
139
- `error: ${error instanceof Error ? error.name : "UnknownError"}`,
140
- ],
134
+ updatedCount += result.updated_paths.length;
135
+ if (result.commit_oid !== undefined) {
136
+ lastCommitOid = result.commit_oid;
137
+ }
138
+ targets = readTargets({
139
+ projectContext: options.projectContext,
141
140
  now: options.now,
142
- });
143
- publishReferenceSummaryTimelineChanged({
144
- events: options.events,
145
- message: "Reference summary timeline updated.",
146
- });
147
- throw error;
141
+ }).filter((target) => (target.summary.status === "missing" || target.summary.status === "stale") &&
142
+ !processedTargets.has(`${target.path}\u0000${target.blob_oid}`));
148
143
  }
149
144
  options.logger?.info({
150
145
  workflow_ref: options.workflowRef,
151
146
  activity_ref: options.activityRef,
152
- target_count: options.targets.length,
153
- updated_count: result.updated_paths.length,
147
+ target_count: targetCount,
148
+ updated_count: updatedCount,
154
149
  failed_count: failedCount,
155
150
  adapter_unavailable_count: adapterUnavailableCount,
156
- ...(result.commit_oid === undefined ? {} : { commit_oid: result.commit_oid }),
151
+ ...(lastCommitOid === undefined ? {} : { commit_oid: lastCommitOid }),
157
152
  }, "Reference summaries refreshed");
158
153
  recordProjectTimelineEvent(options.store.db, {
159
154
  event_kind: "reference_summary",
160
155
  status: "completed",
161
- summary: result.updated_paths.length === 0
156
+ summary: updatedCount === 0
162
157
  ? "Reference summaries already up to date."
163
- : `Updated ${result.updated_paths.length} reference summaries.`,
158
+ : `Updated ${updatedCount} reference summaries.`,
164
159
  activity_ref: options.activityRef,
165
160
  workflow_ref: options.workflowRef,
166
- ...(result.commit_oid === undefined ? {} : { commit_oid: result.commit_oid }),
161
+ ...(lastCommitOid === undefined ? {} : { commit_oid: lastCommitOid }),
167
162
  meta: [
168
- `target count: ${options.targets.length}`,
169
- `updated paths: ${result.updated_paths.length}`,
163
+ `target count: ${targetCount}`,
164
+ `updated paths: ${updatedCount}`,
170
165
  `failed targets: ${failedCount}`,
171
166
  `adapter unavailable: ${adapterUnavailableCount}`,
172
167
  ],
@@ -175,7 +170,7 @@ async function runReferenceSummaryRefresh(options) {
175
170
  publishReferenceSummaryTimelineChanged({
176
171
  events: options.events,
177
172
  message: "Reference summary timeline updated.",
178
- ...(result.updated_paths.length !== 0
173
+ ...(updatedCount !== 0
179
174
  ? {
180
175
  invalidates: [
181
176
  { kind: "viewer", path: "docs/reference" },
@@ -188,12 +183,96 @@ async function runReferenceSummaryRefresh(options) {
188
183
  });
189
184
  return {
190
185
  kind: "completed",
191
- summary: result.updated_paths.length === 0
186
+ summary: updatedCount === 0
192
187
  ? "Reference summaries already up to date."
193
- : `Updated ${result.updated_paths.length} reference summaries.`,
188
+ : `Updated ${updatedCount} reference summaries.`,
194
189
  result_anchor: { kind: "workflow", workflow_ref: options.workflowRef },
195
190
  };
196
191
  }
192
+ async function mapWithConcurrency(items, concurrency, worker) {
193
+ const results = new Array(items.length);
194
+ let nextIndex = 0;
195
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
196
+ while (nextIndex < items.length) {
197
+ const index = nextIndex;
198
+ nextIndex += 1;
199
+ const item = items[index];
200
+ if (item !== undefined) {
201
+ results[index] = await worker(item);
202
+ }
203
+ }
204
+ });
205
+ await Promise.all(workers);
206
+ return results;
207
+ }
208
+ async function summarizeReferenceTarget(options) {
209
+ for (let attempt = 1; attempt <= REFERENCE_SUMMARY_MAX_ATTEMPTS; attempt += 1) {
210
+ try {
211
+ const output = await options.runner.runReferenceFileSummary?.({
212
+ workflow_ref: options.workflowRef,
213
+ activity_ref: options.activityRef,
214
+ file: {
215
+ path: options.target.path,
216
+ name: options.target.name,
217
+ category: options.target.category,
218
+ size_bytes: options.target.size_bytes,
219
+ blob_oid: options.target.blob_oid,
220
+ ...(options.target.media_type === undefined
221
+ ? {}
222
+ : { media_type: options.target.media_type }),
223
+ },
224
+ });
225
+ if (output === undefined) {
226
+ return {
227
+ summary: {
228
+ path: options.target.path,
229
+ status: "unavailable",
230
+ generated_at: options.now().toISOString(),
231
+ source_blob_oid: options.target.blob_oid,
232
+ unavailable_reason: "summary_adapter_unavailable",
233
+ },
234
+ failed: false,
235
+ adapterUnavailable: true,
236
+ };
237
+ }
238
+ return {
239
+ summary: summaryRecordFromOutput({
240
+ target: options.target,
241
+ output,
242
+ generatedAt: options.now().toISOString(),
243
+ }),
244
+ failed: false,
245
+ adapterUnavailable: false,
246
+ };
247
+ }
248
+ catch (error) {
249
+ options.logger?.info({
250
+ workflow_ref: options.workflowRef,
251
+ activity_ref: options.activityRef,
252
+ path: options.target.path,
253
+ attempt,
254
+ retrying: attempt < REFERENCE_SUMMARY_MAX_ATTEMPTS,
255
+ error_name: error instanceof Error ? error.name : "UnknownError",
256
+ }, attempt < REFERENCE_SUMMARY_MAX_ATTEMPTS
257
+ ? "Reference summary target retrying"
258
+ : "Reference summary target failed");
259
+ if (attempt === REFERENCE_SUMMARY_MAX_ATTEMPTS) {
260
+ return {
261
+ summary: {
262
+ path: options.target.path,
263
+ status: "unavailable",
264
+ generated_at: options.now().toISOString(),
265
+ source_blob_oid: options.target.blob_oid,
266
+ unavailable_reason: "summary_failed",
267
+ },
268
+ failed: true,
269
+ adapterUnavailable: false,
270
+ };
271
+ }
272
+ }
273
+ }
274
+ throw new Error("Reference summary retry loop ended unexpectedly");
275
+ }
197
276
  export function startReferenceSummaryRefreshProcedure(input) {
198
277
  const activeActivity = input.readActiveActivity();
199
278
  const requestedPathCount = input.paths?.length ?? 0;
@@ -268,23 +347,6 @@ export function startReferenceSummaryRefreshProcedure(input) {
268
347
  },
269
348
  };
270
349
  }
271
- if (runnerResolution.runner.runReferenceFileSummary === undefined) {
272
- logNotStarted({
273
- logger: input.logger,
274
- disposition: "provider_unavailable",
275
- requestedPathCount,
276
- targetCount: targets.length,
277
- reason: "provider_model_unavailable",
278
- retryable: false,
279
- });
280
- return {
281
- disposition: {
282
- kind: "provider_unavailable",
283
- reason: "provider_model_unavailable",
284
- retryable: false,
285
- },
286
- };
287
- }
288
350
  const workflowRef = createWorkflowInvocationRef();
289
351
  const projectContext = input.projectContext;
290
352
  const start = input.engine.startProcedure({
@@ -302,18 +364,24 @@ export function startReferenceSummaryRefreshProcedure(input) {
302
364
  activityRef: context.activity_ref,
303
365
  error,
304
366
  }),
305
- execute: async (context) => runReferenceSummaryRefresh({
306
- store: input.store,
307
- runner: runnerResolution.runner,
308
- workflowRef,
309
- activityRef: context.activity_ref,
310
- targets,
311
- projectContext,
312
- events: input.events,
313
- logger: input.logger,
314
- reportProgress: context.reportProgress,
315
- now: input.now,
316
- }),
367
+ execute: async (context) => {
368
+ input.onExecuteStart?.({
369
+ activityRef: context.activity_ref,
370
+ workflowRef,
371
+ });
372
+ return runReferenceSummaryRefresh({
373
+ store: input.store,
374
+ runner: runnerResolution.runner,
375
+ workflowRef,
376
+ activityRef: context.activity_ref,
377
+ targets,
378
+ projectContext,
379
+ events: input.events,
380
+ logger: input.logger,
381
+ reportProgress: context.reportProgress,
382
+ now: input.now,
383
+ });
384
+ },
317
385
  });
318
386
  if (start.kind === "already_running") {
319
387
  logNotStarted({
@@ -1,3 +1,4 @@
1
+ import { materializeWorkflowRecentReferences } from "./recent-references.js";
1
2
  import { recordRunPipelineFailure, recordRunPipelineResult, } from "./run-result-recording.js";
2
3
  import { projectBriefShouldRefreshForUpdatedPaths, refreshProjectBriefProjection, } from "./project-brief-refresh.js";
3
4
  function runLineageFields(lineage) {
@@ -79,6 +80,14 @@ export function startControlPlaneRunForTask(input) {
79
80
  const pipelineStartedAt = input.now();
80
81
  input.logger?.info(lifecycleFields, "Run pipeline started");
81
82
  try {
83
+ const recentReferences = input.projectContext === undefined
84
+ ? []
85
+ : materializeWorkflowRecentReferences({
86
+ db: input.store.db,
87
+ workspaceRoot: input.projectContext.workspaceRoot,
88
+ workflowRef: input.run.task.created_from_workflow_ref,
89
+ now: input.now,
90
+ });
82
91
  const pipelineResult = await input.run.runner.runTask({
83
92
  activity_ref: context.activity_ref,
84
93
  task: input.run.task,
@@ -88,6 +97,9 @@ export function startControlPlaneRunForTask(input) {
88
97
  ...(input.run.continuation_context === undefined
89
98
  ? {}
90
99
  : { continuation_context: input.run.continuation_context }),
100
+ ...(recentReferences.length === 0
101
+ ? {}
102
+ : { recent_references: recentReferences }),
91
103
  on_stage: (progress) => context.reportProgress({
92
104
  phase: progress.stage,
93
105
  summary: runStageSummary(input.run, progress),
@@ -3,6 +3,7 @@ import type { ProcedureEngine, ProcedureEngineState } from "../procedure-engine/
3
3
  import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js";
4
4
  import type { HostProjectStore } from "../store/index.js";
5
5
  import type { ControlPlaneLogger } from "./types.js";
6
+ import type { Phase5ControlPlaneOptions } from "./types.js";
6
7
  import type { ProcedureWorkflowRunnerResolver } from "./workflows/index.js";
7
8
  export declare function startControlPlaneTaskCompileContinuation(options: {
8
9
  engine: ProcedureEngine;
@@ -17,6 +18,7 @@ export declare function startControlPlaneTaskCompileContinuation(options: {
17
18
  };
18
19
  roundId: ClarificationRoundRef;
19
20
  resolveWorkflowRunner: ProcedureWorkflowRunnerResolver;
21
+ projectContext: Phase5ControlPlaneOptions["projectContext"] | undefined;
20
22
  logger?: ControlPlaneLogger | undefined;
21
23
  now: () => Date;
22
24
  publishProcedureTransition: (state: ProcedureEngineState) => void;
@@ -4,6 +4,7 @@ import { clarificationInvalidates } from "./invalidations.js";
4
4
  import { applyTaskCompileProcedureOutput } from "./task-compile-output.js";
5
5
  import { readTaskCompileClarificationRounds } from "./task-compile-clarification-context.js";
6
6
  import { logProcedureExecutionError } from "./procedure-logging.js";
7
+ import { materializeRecentReferences, readRecentReferenceSources, } from "./recent-references.js";
7
8
  export function startControlPlaneTaskCompileContinuation(options) {
8
9
  if (options.owner.kind !== "task_compile") {
9
10
  return false;
@@ -43,10 +44,20 @@ export function startControlPlaneTaskCompileContinuation(options) {
43
44
  workflowRef,
44
45
  throughRoundId: options.roundId,
45
46
  });
47
+ const recentReferences = options.projectContext === undefined
48
+ ? []
49
+ : materializeRecentReferences({
50
+ workspaceRoot: options.projectContext.workspaceRoot,
51
+ sources: readRecentReferenceSources(options.store.db, taskCompileContext.sources),
52
+ now: options.now,
53
+ });
46
54
  const output = await runnerResolution.runner.runTaskCompile({
47
55
  workflow_ref: workflowRef,
48
56
  scratchpad: taskCompileContext.scratchpad_snapshot,
49
57
  worklist: readWorklistProjection(options.store.db),
58
+ ...(recentReferences.length === 0
59
+ ? {}
60
+ : { recent_references: recentReferences }),
50
61
  clarification_rounds: clarificationRounds,
51
62
  }, {
52
63
  workflow_ref: workflowRef,
@@ -15,6 +15,7 @@ export declare function startControlPlaneTaskCompile(options: {
15
15
  logger: ControlPlaneLogger | undefined;
16
16
  now: () => Date;
17
17
  publishProcedureTransition: (state: ProcedureEngineState) => void;
18
+ registerReferenceSummaryWaiter: (activityRef: ActivityRef, waiter: (state: ProcedureEngineState) => void) => void;
18
19
  onTasksAdded: () => void;
19
20
  }): ControlPlaneCommandResult<SubmitWorklistDisposition, SubmitWorklistResult>;
20
21
  export declare function runControlPlaneTaskCompileProcedure(options: {
@@ -23,6 +24,7 @@ export declare function runControlPlaneTaskCompileProcedure(options: {
23
24
  activityRef: ActivityRef;
24
25
  store: HostProjectStore;
25
26
  events: WorkspaceEventBus;
27
+ projectContext: Phase5ControlPlaneOptions["projectContext"] | undefined;
26
28
  now: () => Date;
27
29
  onTasksAdded: () => void;
28
30
  reportProgress?: (progress: ProcedureProgressUpdate) => void;
@@ -1,11 +1,13 @@
1
1
  import { createWorkflowInvocationRef, } from "@tutti/shared/ids";
2
- import { captureTaskCompileContext, readActiveTaskCompileContext, readScratchpadProjection, readWorklistProjection, rollbackTaskCompileContext, } from "../collaboration-state/index.js";
2
+ import { captureTaskCompileContext, readActiveTaskCompileContext, readCurrentScratchpadSourceRefs, 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";
6
6
  import { applyProjectDocsInitializationResult, buildProjectDocsInitializationContext, tryProjectContextBootstrapDocuments, } from "./project-context-bootstrap.js";
7
7
  import { readActiveScratchpadRefreshActivityRef, readControlPlaneScratchpadSourceBatch, runControlPlaneScratchpadRefreshProcedure, } from "./scratchpad-refresh-start.js";
8
8
  import { applyTaskCompileProcedureOutput } from "./task-compile-output.js";
9
+ import { materializeRecentReferences, readPendingRecentReferenceSummaryPaths, readRecentReferenceSources, } from "./recent-references.js";
10
+ import { startReferenceSummaryRefreshProcedure } from "./reference-summary-refresh.js";
9
11
  export function startControlPlaneTaskCompile(options) {
10
12
  const activeContext = readActiveTaskCompileContext(options.store.db);
11
13
  if (activeContext !== null) {
@@ -26,7 +28,6 @@ export function startControlPlaneTaskCompile(options) {
26
28
  },
27
29
  };
28
30
  }
29
- const shouldInitializeProjectContext = shouldInitializeControlPlaneProjectContext(options);
30
31
  const runnerResolution = options.resolveWorkflowRunner();
31
32
  if (runnerResolution.kind === "not_configured") {
32
33
  return { disposition: { kind: "provider_not_configured" } };
@@ -65,14 +66,7 @@ export function startControlPlaneTaskCompile(options) {
65
66
  if (!scratchpadHasSubmittableContent(scratchpad)) {
66
67
  return { disposition: { kind: "scratchpad_empty" } };
67
68
  }
68
- if (shouldInitializeProjectContext) {
69
- return startProjectContextBootstrapActivity({
70
- ...options,
71
- runner: runnerResolution.runner,
72
- taskCompileWorkflowRef,
73
- });
74
- }
75
- return startControlPlaneTaskCompileActivity({
69
+ return startReferenceSummaryGateOrNextStage({
76
70
  ...options,
77
71
  runner: runnerResolution.runner,
78
72
  taskCompileWorkflowRef,
@@ -105,6 +99,7 @@ function startControlPlaneTaskCompileActivity(options) {
105
99
  runner: options.runner,
106
100
  store: options.store,
107
101
  events: options.events,
102
+ projectContext: options.projectContext,
108
103
  workflowRef,
109
104
  activityRef: context.activity_ref,
110
105
  reportProgress: context.reportProgress,
@@ -146,12 +141,7 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
146
141
  if (!scratchpadHasSubmittableContent(scratchpad)) {
147
142
  return;
148
143
  }
149
- if (shouldInitializeControlPlaneProjectContext(options)) {
150
- void startProjectContextBootstrapActivity(options);
151
- }
152
- else {
153
- void startControlPlaneTaskCompileActivity(options);
154
- }
144
+ void startReferenceSummaryGateOrNextStage(options);
155
145
  },
156
146
  onError: ({ context, error }) => logProcedureExecutionError({
157
147
  logger: options.logger,
@@ -186,6 +176,103 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
186
176
  },
187
177
  };
188
178
  }
179
+ function startReferenceSummaryGateOrNextStage(options) {
180
+ const sourceRefs = readActiveTaskCompileContext(options.store.db, options.taskCompileWorkflowRef)?.sources ??
181
+ readCurrentScratchpadSourceRefs(options.store.db);
182
+ const referenceSources = readRecentReferenceSources(options.store.db, sourceRefs);
183
+ const pendingPaths = options.projectContext === undefined
184
+ ? []
185
+ : readPendingRecentReferenceSummaryPaths({
186
+ workspaceRoot: options.projectContext.workspaceRoot,
187
+ sources: referenceSources,
188
+ now: options.now,
189
+ });
190
+ if (pendingPaths.length === 0) {
191
+ return startNextTaskCompileStage(options);
192
+ }
193
+ const activeReferenceSummary = options.engine.getActiveProcedures().find((activity) => activity.lane === "foreground" &&
194
+ activity.workflow_kind === "reference_summary_refresh");
195
+ if (activeReferenceSummary !== undefined) {
196
+ readOrCaptureTaskCompileContext({
197
+ store: options.store,
198
+ workflowRef: options.taskCompileWorkflowRef,
199
+ activityRef: activeReferenceSummary.activity_ref,
200
+ now: options.now,
201
+ });
202
+ registerReferenceSummaryGateWaiter(options, activeReferenceSummary.activity_ref);
203
+ return {
204
+ disposition: {
205
+ kind: "already_running",
206
+ activity_ref: activeReferenceSummary.activity_ref,
207
+ },
208
+ };
209
+ }
210
+ const started = startReferenceSummaryRefreshProcedure({
211
+ engine: options.engine,
212
+ store: options.store,
213
+ events: options.events,
214
+ paths: pendingPaths,
215
+ resolveWorkflowRunner: () => ({ kind: "ready", runner: options.runner }),
216
+ readActiveActivity: () => {
217
+ const active = options.engine.getActiveProcedures().find((activity) => activity.lane === "foreground");
218
+ return active === undefined
219
+ ? null
220
+ : { activity_ref: active.activity_ref };
221
+ },
222
+ projectContext: options.projectContext,
223
+ logger: options.logger,
224
+ now: options.now,
225
+ onExecuteStart: ({ activityRef }) => {
226
+ readOrCaptureTaskCompileContext({
227
+ store: options.store,
228
+ workflowRef: options.taskCompileWorkflowRef,
229
+ activityRef,
230
+ now: options.now,
231
+ });
232
+ },
233
+ publishProcedureTransition: options.publishProcedureTransition,
234
+ });
235
+ if (started.disposition.kind === "accepted") {
236
+ const activityRef = started.result?.activity_ref;
237
+ const workflowRef = started.result?.workflow_ref;
238
+ if (activityRef === undefined || workflowRef === undefined) {
239
+ throw new Error("Accepted reference summary gate is missing activity identity");
240
+ }
241
+ registerReferenceSummaryGateWaiter(options, activityRef);
242
+ return {
243
+ disposition: { kind: "accepted" },
244
+ result: {
245
+ activity_ref: activityRef,
246
+ workflow_ref: workflowRef,
247
+ },
248
+ };
249
+ }
250
+ if (started.disposition.kind === "already_running") {
251
+ return { disposition: started.disposition };
252
+ }
253
+ if (started.disposition.kind === "no_targets") {
254
+ return startNextTaskCompileStage(options);
255
+ }
256
+ return { disposition: started.disposition };
257
+ }
258
+ function registerReferenceSummaryGateWaiter(options, activityRef) {
259
+ options.registerReferenceSummaryWaiter(activityRef, (state) => {
260
+ if (state.activity.status !== "finished") {
261
+ rollbackTaskCompileContext(options.store.db, {
262
+ workflow_ref: options.taskCompileWorkflowRef,
263
+ now: options.now,
264
+ });
265
+ return;
266
+ }
267
+ void startReferenceSummaryGateOrNextStage(options);
268
+ });
269
+ }
270
+ function startNextTaskCompileStage(options) {
271
+ if (shouldInitializeControlPlaneProjectContext(options)) {
272
+ return startProjectContextBootstrapActivity(options);
273
+ }
274
+ return startControlPlaneTaskCompileActivity(options);
275
+ }
189
276
  function shouldInitializeControlPlaneProjectContext(options) {
190
277
  if (options.projectContext === undefined) {
191
278
  return false;
@@ -324,10 +411,20 @@ export async function runControlPlaneTaskCompileProcedure(options) {
324
411
  };
325
412
  }
326
413
  try {
414
+ const recentReferences = options.projectContext === undefined
415
+ ? []
416
+ : materializeRecentReferences({
417
+ workspaceRoot: options.projectContext.workspaceRoot,
418
+ sources: readRecentReferenceSources(options.store.db, context.sources),
419
+ now: options.now,
420
+ });
327
421
  const output = await options.runner.runTaskCompile({
328
422
  workflow_ref: options.workflowRef,
329
423
  scratchpad: context.scratchpad_snapshot,
330
424
  worklist: readWorklistProjection(options.store.db),
425
+ ...(recentReferences.length === 0
426
+ ? {}
427
+ : { recent_references: recentReferences }),
331
428
  }, {
332
429
  workflow_ref: options.workflowRef,
333
430
  activity_ref: options.activityRef,
@@ -4,6 +4,7 @@ import type { ActivityRef, ClarificationRoundRef, TaskId, TaskModuleId } from "@
4
4
  import type { ActiveApprovalProjection, ClarificationRequestPayload, ExecutionRuntimeSnapshot, MessageProjection, RecoverySummaryProjection, TaskDetailProjection, TaskProjection } from "@tutti/shared/schemas/api";
5
5
  import type { HostProjectStore } from "../store/index.js";
6
6
  import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js";
7
+ import type { RecentReferenceContext } from "./reference-context-types.js";
7
8
  import type { ProcedureWorkflowRunnerResolver } from "./workflows/index.js";
8
9
  export type Phase5ControlPlaneOptions = {
9
10
  store: HostProjectStore;
@@ -104,6 +105,7 @@ export type RunPipelineInput = {
104
105
  dispatch_context: RunDispatchContext;
105
106
  continuation_context?: RunContinuationContext;
106
107
  correction_context?: RunCorrectionContext;
108
+ recent_references?: RecentReferenceContext[];
107
109
  on_stage?: (progress: RunPipelineStageProgress) => void;
108
110
  };
109
111
  export type RunPipelineStageProgress = {
@@ -160,6 +160,13 @@ export function buildTaskCompilePromptInput(input) {
160
160
  })),
161
161
  },
162
162
  };
163
+ if (input.recent_references !== undefined && input.recent_references.length !== 0) {
164
+ promptInput.recent_references = input.recent_references.map((reference) => ({
165
+ path: reference.path,
166
+ name: reference.name,
167
+ summary: { ...reference.summary },
168
+ }));
169
+ }
163
170
  if (input.clarification_rounds !== undefined) {
164
171
  promptInput.clarification_rounds = input.clarification_rounds.map((round) => ({
165
172
  request: {