@xfey/tutti 0.1.96 → 0.1.97
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.
- package/dist/collaboration-state/index.d.ts +5 -5
- package/dist/collaboration-state/index.js +4 -4
- package/dist/collaboration-state/scratchpad-source-state.d.ts +6 -1
- package/dist/collaboration-state/scratchpad-source-state.js +37 -6
- package/dist/collaboration-state/storage-types.d.ts +10 -0
- package/dist/collaboration-state/task-compile-context.d.ts +8 -3
- package/dist/collaboration-state/task-compile-context.js +91 -7
- package/dist/collaboration-state/types.d.ts +12 -0
- package/dist/collaboration-state/worklist.d.ts +2 -1
- package/dist/collaboration-state/worklist.js +6 -0
- package/dist/control-plane/clarification-commands.d.ts +12 -8
- package/dist/control-plane/clarification-commands.js +29 -10
- package/dist/control-plane/dispatch-intents.d.ts +104 -0
- package/dist/control-plane/dispatch-intents.js +276 -0
- package/dist/control-plane/follow-up-run.d.ts +1 -1
- package/dist/control-plane/follow-up-run.js +12 -4
- package/dist/control-plane/follow-up-start.d.ts +3 -1
- package/dist/control-plane/follow-up-start.js +7 -2
- package/dist/control-plane/formatters.d.ts +2 -0
- package/dist/control-plane/formatters.js +6 -0
- package/dist/control-plane/index.d.ts +9 -0
- package/dist/control-plane/index.js +260 -11
- package/dist/control-plane/intent-dispatcher.d.ts +52 -0
- package/dist/control-plane/intent-dispatcher.js +184 -0
- package/dist/control-plane/reference-summary-refresh.d.ts +4 -1
- package/dist/control-plane/reference-summary-refresh.js +39 -4
- package/dist/control-plane/run-dispatch-intents.d.ts +15 -0
- package/dist/control-plane/run-dispatch-intents.js +40 -0
- package/dist/control-plane/run-retry.d.ts +1 -1
- package/dist/control-plane/run-retry.js +14 -6
- package/dist/control-plane/run-scheduler.d.ts +1 -1
- package/dist/control-plane/run-scheduler.js +12 -4
- package/dist/control-plane/scratchpad-refresh-start.d.ts +10 -2
- package/dist/control-plane/scratchpad-refresh-start.js +57 -8
- package/dist/control-plane/startup-recovery.d.ts +2 -2
- package/dist/control-plane/startup-recovery.js +73 -3
- package/dist/control-plane/task-compile-continuation.d.ts +3 -1
- package/dist/control-plane/task-compile-continuation.js +14 -9
- package/dist/control-plane/task-compile-start.d.ts +33 -1
- package/dist/control-plane/task-compile-start.js +244 -26
- package/dist/control-plane/types.d.ts +4 -2
- package/migrations/0015_control_plane_intents.sql +115 -0
- package/migrations/README.md +2 -1
- package/node_modules/@tutti/shared/dist/ids/index.d.ts +3 -0
- package/node_modules/@tutti/shared/dist/ids/index.js +2 -0
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-BFo4r4nz.js → homepage-motion-scene-lpr-1FjJ.js} +1 -1
- package/web/assets/{index-CNIl4TF8.js → index-CQoq2PtU.js} +2 -2
- package/web/index.html +1 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createWorkflowInvocationRef, } from "@tutti/shared/ids";
|
|
2
|
-
import { captureTaskCompileContext, readActiveTaskCompileContext, readCurrentScratchpadSourceRefs, readScratchpadProjection, readWorklistProjection, rollbackTaskCompileContext, } from "../collaboration-state/index.js";
|
|
2
|
+
import { captureTaskCompileContext, readActiveTaskCompileContext, readCurrentScratchpadSourceRefs, readPendingTaskCompileSubmission, readScratchpadProjection, readScratchpadSourceState, readWorklistProjection, recordTaskCompileSubmission, rollbackTaskCompileContext, } from "../collaboration-state/index.js";
|
|
3
|
+
import { withHostStoreTransaction } from "../store/index.js";
|
|
3
4
|
import { initializeProjectDocs, readProjectDocsInitializationStatus, } from "../workspace-ops/index.js";
|
|
4
5
|
import { logProcedureExecutionError } from "./procedure-logging.js";
|
|
5
6
|
import { projectBriefShouldRefreshForUpdatedPaths, refreshProjectBriefProjection, } from "./project-brief-refresh.js";
|
|
6
7
|
import { applyProjectDocsInitializationResult, buildProjectDocsInitializationContext, tryProjectContextBootstrapDocuments, } from "./project-context-bootstrap.js";
|
|
7
8
|
import { readActiveScratchpadRefreshActivityRef, readControlPlaneScratchpadSourceBatch, runControlPlaneScratchpadRefreshProcedure, } from "./scratchpad-refresh-start.js";
|
|
8
9
|
import { applyTaskCompileProcedureOutput } from "./task-compile-output.js";
|
|
10
|
+
import { enqueueControlPlaneIntent } from "./dispatch-intents.js";
|
|
9
11
|
import { materializePromptSourceMessages } from "./task-source-context.js";
|
|
10
12
|
import { materializeRecentReferences, readPendingRecentReferenceSummaryPaths, readRecentReferenceSources, } from "./recent-references.js";
|
|
11
13
|
import { startReferenceSummaryRefreshProcedure } from "./reference-summary-refresh.js";
|
|
@@ -19,6 +21,15 @@ export function startControlPlaneTaskCompile(options) {
|
|
|
19
21
|
},
|
|
20
22
|
};
|
|
21
23
|
}
|
|
24
|
+
const pendingSubmission = readPendingTaskCompileSubmission(options.store.db);
|
|
25
|
+
if (pendingSubmission !== null) {
|
|
26
|
+
return {
|
|
27
|
+
disposition: {
|
|
28
|
+
kind: "already_running",
|
|
29
|
+
activity_ref: pendingSubmission.accepted_activity_ref,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
22
33
|
const scratchpad = readScratchpadProjection(options.store.db);
|
|
23
34
|
if (options.payload.expected_scratchpad_updated_at !== undefined &&
|
|
24
35
|
options.payload.expected_scratchpad_updated_at !== scratchpad.updated_at) {
|
|
@@ -43,7 +54,10 @@ export function startControlPlaneTaskCompile(options) {
|
|
|
43
54
|
};
|
|
44
55
|
}
|
|
45
56
|
const taskCompileWorkflowRef = createWorkflowInvocationRef();
|
|
46
|
-
const sourceBatch = readControlPlaneScratchpadSourceBatch(options.store
|
|
57
|
+
const sourceBatch = readControlPlaneScratchpadSourceBatch(options.store, {
|
|
58
|
+
...(options.projectContext === undefined ? {} : { projectContext: options.projectContext }),
|
|
59
|
+
now: options.now,
|
|
60
|
+
});
|
|
47
61
|
if (sourceBatch.source_message_count > 0) {
|
|
48
62
|
const activeScratchpadRefresh = readActiveScratchpadRefreshActivityRef({
|
|
49
63
|
engine: options.engine,
|
|
@@ -62,22 +76,70 @@ export function startControlPlaneTaskCompile(options) {
|
|
|
62
76
|
runner: runnerResolution.runner,
|
|
63
77
|
sourceBatch,
|
|
64
78
|
taskCompileWorkflowRef,
|
|
79
|
+
recordRecoveryIntent: false,
|
|
80
|
+
onAccepted: (activityRef) => recordTaskCompileSubmissionIntent({
|
|
81
|
+
store: options.store,
|
|
82
|
+
workflowRef: taskCompileWorkflowRef,
|
|
83
|
+
acceptedActivityRef: activityRef,
|
|
84
|
+
sourceBatch,
|
|
85
|
+
now: options.now,
|
|
86
|
+
}),
|
|
65
87
|
});
|
|
66
88
|
}
|
|
67
89
|
if (!scratchpadHasSubmittableContent(scratchpad)) {
|
|
68
90
|
return { disposition: { kind: "scratchpad_empty" } };
|
|
69
91
|
}
|
|
70
|
-
|
|
92
|
+
const started = startReferenceSummaryGateOrNextStage({
|
|
71
93
|
...options,
|
|
72
94
|
runner: runnerResolution.runner,
|
|
73
95
|
taskCompileWorkflowRef,
|
|
74
96
|
});
|
|
97
|
+
if (started.disposition.kind === "accepted") {
|
|
98
|
+
const activityRef = started.result?.activity_ref;
|
|
99
|
+
if (activityRef === undefined) {
|
|
100
|
+
throw new Error("Accepted Task Compile chain is missing activity identity");
|
|
101
|
+
}
|
|
102
|
+
recordTaskCompileSubmissionIntent({
|
|
103
|
+
store: options.store,
|
|
104
|
+
workflowRef: taskCompileWorkflowRef,
|
|
105
|
+
acceptedActivityRef: activityRef,
|
|
106
|
+
sourceBatch,
|
|
107
|
+
now: options.now,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return started;
|
|
111
|
+
}
|
|
112
|
+
function recordTaskCompileSubmissionIntent(options) {
|
|
113
|
+
return withHostStoreTransaction(options.store.db, (tx) => {
|
|
114
|
+
const sourceState = readScratchpadSourceState(tx);
|
|
115
|
+
const through = options.sourceBatch.cursor_update.refreshed_through_cursor ??
|
|
116
|
+
sourceState.refreshed_through_cursor;
|
|
117
|
+
recordTaskCompileSubmission(tx, {
|
|
118
|
+
workflow_ref: options.workflowRef,
|
|
119
|
+
accepted_activity_ref: options.acceptedActivityRef,
|
|
120
|
+
source_window: {
|
|
121
|
+
...(sourceState.cycle_start_cursor === undefined
|
|
122
|
+
? {}
|
|
123
|
+
: { after: sourceState.cycle_start_cursor }),
|
|
124
|
+
...(through === undefined ? {} : { through }),
|
|
125
|
+
},
|
|
126
|
+
now: options.now,
|
|
127
|
+
});
|
|
128
|
+
return enqueueControlPlaneIntent(tx, {
|
|
129
|
+
kind: "task_compile_start",
|
|
130
|
+
ownerKind: "task_compile",
|
|
131
|
+
ownerRef: options.workflowRef,
|
|
132
|
+
dedupeKey: `task_compile_start:${options.workflowRef}`,
|
|
133
|
+
lane: "foreground",
|
|
134
|
+
priority: 30,
|
|
135
|
+
now: options.now,
|
|
136
|
+
}).intent.intent_id;
|
|
137
|
+
});
|
|
75
138
|
}
|
|
76
139
|
function startControlPlaneTaskCompileActivity(options) {
|
|
77
140
|
const activeContext = readActiveTaskCompileContext(options.store.db, options.taskCompileWorkflowRef);
|
|
78
141
|
const scratchpad = readScratchpadProjection(options.store.db);
|
|
79
|
-
if (activeContext === null &&
|
|
80
|
-
!scratchpadHasSubmittableContent(scratchpad)) {
|
|
142
|
+
if (activeContext === null && !scratchpadHasSubmittableContent(scratchpad)) {
|
|
81
143
|
return { disposition: { kind: "scratchpad_empty" } };
|
|
82
144
|
}
|
|
83
145
|
const workflowRef = options.taskCompileWorkflowRef;
|
|
@@ -88,7 +150,14 @@ function startControlPlaneTaskCompileActivity(options) {
|
|
|
88
150
|
title: "Compile Worklist",
|
|
89
151
|
summary: "Compiling Scratchpad into Worklist tasks.",
|
|
90
152
|
now: options.now,
|
|
91
|
-
onTransition:
|
|
153
|
+
onTransition: (state) => {
|
|
154
|
+
options.publishProcedureTransition(state);
|
|
155
|
+
if (state.transition_kind === "terminal") {
|
|
156
|
+
options.onChainTerminal?.(state.activity.status === "failed"
|
|
157
|
+
? { state: "failed", errorCode: "procedure_failed" }
|
|
158
|
+
: undefined);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
92
161
|
onError: ({ context, error }) => logProcedureExecutionError({
|
|
93
162
|
logger: options.logger,
|
|
94
163
|
workflowKind: "task_compile",
|
|
@@ -126,6 +195,7 @@ function startControlPlaneTaskCompileActivity(options) {
|
|
|
126
195
|
}
|
|
127
196
|
function startScratchpadRefreshBeforeTaskCompileActivity(options) {
|
|
128
197
|
const workflowRef = createWorkflowInvocationRef();
|
|
198
|
+
let recoveryIntentId = options.recoveryIntentId;
|
|
129
199
|
const start = options.engine.startProcedure({
|
|
130
200
|
workflow_ref: workflowRef,
|
|
131
201
|
workflow_kind: "scratchpad_refresh",
|
|
@@ -135,7 +205,14 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
|
|
|
135
205
|
now: options.now,
|
|
136
206
|
onTransition: (state) => {
|
|
137
207
|
options.publishProcedureTransition(state);
|
|
138
|
-
if (state.transition_kind !== "terminal"
|
|
208
|
+
if (state.transition_kind !== "terminal") {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (state.activity.status !== "finished") {
|
|
212
|
+
options.onChainTerminal?.({
|
|
213
|
+
state: "failed",
|
|
214
|
+
errorCode: "scratchpad_refresh_failed",
|
|
215
|
+
});
|
|
139
216
|
return;
|
|
140
217
|
}
|
|
141
218
|
if (options.shouldContinue?.() === false) {
|
|
@@ -143,9 +220,19 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
|
|
|
143
220
|
}
|
|
144
221
|
const scratchpad = readScratchpadProjection(options.store.db);
|
|
145
222
|
if (!scratchpadHasSubmittableContent(scratchpad)) {
|
|
223
|
+
options.onChainTerminal?.({
|
|
224
|
+
state: "failed",
|
|
225
|
+
errorCode: "scratchpad_empty_after_refresh",
|
|
226
|
+
});
|
|
146
227
|
return;
|
|
147
228
|
}
|
|
148
|
-
|
|
229
|
+
const next = startReferenceSummaryGateOrNextStage(options);
|
|
230
|
+
if (next.disposition.kind !== "accepted" && next.disposition.kind !== "already_running") {
|
|
231
|
+
options.onChainTerminal?.({
|
|
232
|
+
state: "failed",
|
|
233
|
+
errorCode: "task_compile_stage_unavailable",
|
|
234
|
+
});
|
|
235
|
+
}
|
|
149
236
|
},
|
|
150
237
|
onError: ({ context, error }) => logProcedureExecutionError({
|
|
151
238
|
logger: options.logger,
|
|
@@ -161,6 +248,11 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
|
|
|
161
248
|
workflowRef,
|
|
162
249
|
activityRef: context.activity_ref,
|
|
163
250
|
sourceBatch: options.sourceBatch,
|
|
251
|
+
lane: "foreground",
|
|
252
|
+
...(options.recordRecoveryIntent === undefined
|
|
253
|
+
? {}
|
|
254
|
+
: { recordRecoveryIntent: options.recordRecoveryIntent }),
|
|
255
|
+
...(recoveryIntentId === undefined ? {} : { recoveryIntentId }),
|
|
164
256
|
now: options.now,
|
|
165
257
|
}),
|
|
166
258
|
});
|
|
@@ -172,6 +264,8 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
|
|
|
172
264
|
},
|
|
173
265
|
};
|
|
174
266
|
}
|
|
267
|
+
recoveryIntentId =
|
|
268
|
+
options.onAccepted?.(start.activity.activity_ref) ?? recoveryIntentId;
|
|
175
269
|
return {
|
|
176
270
|
disposition: { kind: "accepted" },
|
|
177
271
|
result: {
|
|
@@ -180,6 +274,116 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
|
|
|
180
274
|
},
|
|
181
275
|
};
|
|
182
276
|
}
|
|
277
|
+
function cursorIncludes(current, required) {
|
|
278
|
+
if (required === undefined) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
if (current === undefined) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
const timeOrder = current.created_at.localeCompare(required.created_at);
|
|
285
|
+
return timeOrder > 0 || (timeOrder === 0 && current.message_id >= required.message_id);
|
|
286
|
+
}
|
|
287
|
+
export function resumePendingTaskCompileSubmission(options) {
|
|
288
|
+
const submission = readPendingTaskCompileSubmission(options.store.db, options.taskCompileWorkflowRef);
|
|
289
|
+
if (submission === null) {
|
|
290
|
+
return { kind: "obsolete", errorCode: "task_compile_submission_absent" };
|
|
291
|
+
}
|
|
292
|
+
const runnerResolution = options.resolveWorkflowRunner();
|
|
293
|
+
if (runnerResolution.kind !== "ready") {
|
|
294
|
+
return {
|
|
295
|
+
kind: "not_ready",
|
|
296
|
+
errorCode: runnerResolution.kind === "not_configured"
|
|
297
|
+
? "provider_not_configured"
|
|
298
|
+
: "provider_unavailable",
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const sourceState = readScratchpadSourceState(options.store.db);
|
|
302
|
+
if (cursorIncludes(sourceState.refreshed_through_cursor, submission.source_window.through)) {
|
|
303
|
+
const result = startReferenceSummaryGateOrNextStage({
|
|
304
|
+
...options,
|
|
305
|
+
runner: runnerResolution.runner,
|
|
306
|
+
});
|
|
307
|
+
if (result.disposition.kind === "accepted") {
|
|
308
|
+
return { kind: "accepted" };
|
|
309
|
+
}
|
|
310
|
+
if (result.disposition.kind === "already_running") {
|
|
311
|
+
const activityRef = result.disposition.activity_ref;
|
|
312
|
+
const attached = options.engine
|
|
313
|
+
.getActiveProcedures()
|
|
314
|
+
.some((activity) => activity.activity_ref === activityRef &&
|
|
315
|
+
activity.workflow_kind === "reference_summary_refresh");
|
|
316
|
+
return attached ? { kind: "accepted" } : { kind: "busy" };
|
|
317
|
+
}
|
|
318
|
+
return { kind: "not_ready", errorCode: "task_compile_stage_unavailable" };
|
|
319
|
+
}
|
|
320
|
+
if (readActiveScratchpadRefreshActivityRef(options) !== undefined) {
|
|
321
|
+
return { kind: "busy" };
|
|
322
|
+
}
|
|
323
|
+
if (sourceState.backoff_until !== undefined &&
|
|
324
|
+
sourceState.backoff_until > options.now().toISOString()) {
|
|
325
|
+
return {
|
|
326
|
+
kind: "not_ready",
|
|
327
|
+
errorCode: "scratchpad_refresh_backoff",
|
|
328
|
+
availableAt: sourceState.backoff_until,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const sourceBatch = readControlPlaneScratchpadSourceBatch(options.store, {
|
|
332
|
+
...(options.projectContext === undefined ? {} : { projectContext: options.projectContext }),
|
|
333
|
+
sourceWindow: submission.source_window,
|
|
334
|
+
now: options.now,
|
|
335
|
+
});
|
|
336
|
+
if (sourceBatch.source_message_count === 0) {
|
|
337
|
+
return {
|
|
338
|
+
kind: "not_ready",
|
|
339
|
+
errorCode: "task_compile_source_boundary_unavailable",
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const result = startScratchpadRefreshBeforeTaskCompileActivity({
|
|
343
|
+
...options,
|
|
344
|
+
runner: runnerResolution.runner,
|
|
345
|
+
sourceBatch,
|
|
346
|
+
taskCompileWorkflowRef: submission.workflow_ref,
|
|
347
|
+
recordRecoveryIntent: false,
|
|
348
|
+
recoveryIntentId: options.recoveryIntentId,
|
|
349
|
+
});
|
|
350
|
+
return result.disposition.kind === "accepted"
|
|
351
|
+
? { kind: "accepted" }
|
|
352
|
+
: result.disposition.kind === "already_running"
|
|
353
|
+
? { kind: "busy" }
|
|
354
|
+
: { kind: "not_ready", errorCode: "scratchpad_refresh_unavailable" };
|
|
355
|
+
}
|
|
356
|
+
export function resumeControlPlaneTaskCompileChain(options) {
|
|
357
|
+
if (readActiveTaskCompileContext(options.store.db, options.taskCompileWorkflowRef) === null) {
|
|
358
|
+
return { kind: "obsolete", errorCode: "task_compile_context_absent" };
|
|
359
|
+
}
|
|
360
|
+
const runnerResolution = options.resolveWorkflowRunner();
|
|
361
|
+
if (runnerResolution.kind !== "ready") {
|
|
362
|
+
return { kind: "not_ready", errorCode: "workflow_runner_unavailable" };
|
|
363
|
+
}
|
|
364
|
+
const result = startReferenceSummaryGateOrNextStage({
|
|
365
|
+
...options,
|
|
366
|
+
runner: runnerResolution.runner,
|
|
367
|
+
});
|
|
368
|
+
if (result.disposition.kind === "accepted") {
|
|
369
|
+
return { kind: "accepted" };
|
|
370
|
+
}
|
|
371
|
+
if (result.disposition.kind === "already_running") {
|
|
372
|
+
const activityRef = result.disposition.activity_ref;
|
|
373
|
+
const attached = options.engine
|
|
374
|
+
.getActiveProcedures()
|
|
375
|
+
.some((activity) => activity.activity_ref === activityRef &&
|
|
376
|
+
activity.workflow_kind === "reference_summary_refresh");
|
|
377
|
+
return attached ? { kind: "accepted" } : { kind: "busy" };
|
|
378
|
+
}
|
|
379
|
+
if (result.disposition.kind === "provider_not_configured") {
|
|
380
|
+
return { kind: "not_ready", errorCode: "provider_not_configured" };
|
|
381
|
+
}
|
|
382
|
+
if (result.disposition.kind === "provider_unavailable") {
|
|
383
|
+
return { kind: "not_ready", errorCode: "provider_unavailable" };
|
|
384
|
+
}
|
|
385
|
+
return { kind: "not_ready", errorCode: "task_compile_stage_unavailable" };
|
|
386
|
+
}
|
|
183
387
|
function startReferenceSummaryGateOrNextStage(options) {
|
|
184
388
|
const sourceRefs = readActiveTaskCompileContext(options.store.db, options.taskCompileWorkflowRef)?.sources ??
|
|
185
389
|
readCurrentScratchpadSourceRefs(options.store.db);
|
|
@@ -194,8 +398,9 @@ function startReferenceSummaryGateOrNextStage(options) {
|
|
|
194
398
|
if (pendingPaths.length === 0) {
|
|
195
399
|
return startNextTaskCompileStage(options);
|
|
196
400
|
}
|
|
197
|
-
const activeReferenceSummary = options.engine
|
|
198
|
-
|
|
401
|
+
const activeReferenceSummary = options.engine
|
|
402
|
+
.getActiveProcedures()
|
|
403
|
+
.find((activity) => activity.lane === "foreground" && activity.workflow_kind === "reference_summary_refresh");
|
|
199
404
|
if (activeReferenceSummary !== undefined) {
|
|
200
405
|
readOrCaptureTaskCompileContext({
|
|
201
406
|
store: options.store,
|
|
@@ -218,10 +423,10 @@ function startReferenceSummaryGateOrNextStage(options) {
|
|
|
218
423
|
paths: pendingPaths,
|
|
219
424
|
resolveWorkflowRunner: () => ({ kind: "ready", runner: options.runner }),
|
|
220
425
|
readActiveActivity: () => {
|
|
221
|
-
const active = options.engine
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
426
|
+
const active = options.engine
|
|
427
|
+
.getActiveProcedures()
|
|
428
|
+
.find((activity) => activity.lane === "foreground");
|
|
429
|
+
return active === undefined ? null : { activity_ref: active.activity_ref };
|
|
225
430
|
},
|
|
226
431
|
projectContext: options.projectContext,
|
|
227
432
|
logger: options.logger,
|
|
@@ -266,6 +471,7 @@ function registerReferenceSummaryGateWaiter(options, activityRef) {
|
|
|
266
471
|
workflow_ref: options.taskCompileWorkflowRef,
|
|
267
472
|
now: options.now,
|
|
268
473
|
});
|
|
474
|
+
options.onChainTerminal?.({ state: "failed", errorCode: "reference_summary_failed" });
|
|
269
475
|
return;
|
|
270
476
|
}
|
|
271
477
|
void startReferenceSummaryGateOrNextStage(options);
|
|
@@ -301,7 +507,11 @@ function startProjectContextBootstrapActivity(options) {
|
|
|
301
507
|
now: options.now,
|
|
302
508
|
onTransition: (state) => {
|
|
303
509
|
options.publishProcedureTransition(state);
|
|
304
|
-
if (state.transition_kind !== "terminal"
|
|
510
|
+
if (state.transition_kind !== "terminal") {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (state.activity.status !== "finished") {
|
|
514
|
+
options.onChainTerminal?.({ state: "failed", errorCode: "project_context_failed" });
|
|
305
515
|
return;
|
|
306
516
|
}
|
|
307
517
|
if (options.shouldContinue?.() === false) {
|
|
@@ -434,15 +644,11 @@ export async function runControlPlaneTaskCompileProcedure(options) {
|
|
|
434
644
|
scratchpad: context.scratchpad_snapshot,
|
|
435
645
|
source_messages: materializePromptSourceMessages(options.store.db, context.sources),
|
|
436
646
|
worklist: readWorklistProjection(options.store.db),
|
|
437
|
-
...(recentReferences.length === 0
|
|
438
|
-
? {}
|
|
439
|
-
: { recent_references: recentReferences }),
|
|
647
|
+
...(recentReferences.length === 0 ? {} : { recent_references: recentReferences }),
|
|
440
648
|
}, {
|
|
441
649
|
workflow_ref: options.workflowRef,
|
|
442
650
|
activity_ref: options.activityRef,
|
|
443
|
-
...(options.reportProgress === undefined
|
|
444
|
-
? {}
|
|
445
|
-
: { reportProgress: options.reportProgress }),
|
|
651
|
+
...(options.reportProgress === undefined ? {} : { reportProgress: options.reportProgress }),
|
|
446
652
|
});
|
|
447
653
|
return applyTaskCompileProcedureOutput({
|
|
448
654
|
store: options.store,
|
|
@@ -463,12 +669,24 @@ export async function runControlPlaneTaskCompileProcedure(options) {
|
|
|
463
669
|
}
|
|
464
670
|
}
|
|
465
671
|
function readOrCaptureTaskCompileContext(input) {
|
|
466
|
-
return (
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
672
|
+
return withHostStoreTransaction(input.store.db, (tx) => {
|
|
673
|
+
const context = readActiveTaskCompileContext(tx, input.workflowRef) ??
|
|
674
|
+
captureTaskCompileContext(tx, {
|
|
675
|
+
workflow_ref: input.workflowRef,
|
|
676
|
+
captured_by_activity_ref: input.activityRef,
|
|
677
|
+
now: input.now,
|
|
678
|
+
});
|
|
679
|
+
enqueueControlPlaneIntent(tx, {
|
|
680
|
+
kind: "task_compile_start",
|
|
681
|
+
ownerKind: "task_compile",
|
|
682
|
+
ownerRef: input.workflowRef,
|
|
683
|
+
dedupeKey: `task_compile_start:${input.workflowRef}`,
|
|
684
|
+
lane: "foreground",
|
|
685
|
+
priority: 30,
|
|
470
686
|
now: input.now,
|
|
471
|
-
})
|
|
687
|
+
});
|
|
688
|
+
return context;
|
|
689
|
+
});
|
|
472
690
|
}
|
|
473
691
|
function scratchpadHasSubmittableContent(scratchpad) {
|
|
474
692
|
if (scratchpad.state !== "ready") {
|
|
@@ -52,6 +52,8 @@ export type StartupRecoveryResult = {
|
|
|
52
52
|
replayed_round_ids: ClarificationRoundRef[];
|
|
53
53
|
interrupted_command_count: number;
|
|
54
54
|
stale_replay_hint_count: number;
|
|
55
|
+
recovered_task_compile_count: number;
|
|
56
|
+
recovered_scratchpad_refresh_count: number;
|
|
55
57
|
};
|
|
56
58
|
export type RunDispatchTrigger = "auto_idle" | "task_arrival" | "manual_run_now" | "continuation" | "recovery";
|
|
57
59
|
export type RunDispatchContext = {
|
|
@@ -128,7 +130,7 @@ export type RunPipelineStageProgress = {
|
|
|
128
130
|
summary: string;
|
|
129
131
|
runtime?: ExecutionRuntimeSnapshot;
|
|
130
132
|
};
|
|
131
|
-
export type RunPipelineResult =
|
|
133
|
+
export type RunPipelineResult = {
|
|
132
134
|
result_kind: "needs_human";
|
|
133
135
|
summary: string;
|
|
134
136
|
clarification_request_payload: ClarificationRequestPayload;
|
|
@@ -152,7 +154,7 @@ export type RunPipelineResult = ({
|
|
|
152
154
|
retryable: boolean;
|
|
153
155
|
changed_paths?: string[];
|
|
154
156
|
promotion?: NonNullable<TaskDetailProjection["detail"]["result"]["promotion"]>;
|
|
155
|
-
}
|
|
157
|
+
};
|
|
156
158
|
export type RunPipelineRunner = {
|
|
157
159
|
runTask: (input: RunPipelineInput) => Promise<RunPipelineResult>;
|
|
158
160
|
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
CREATE TABLE control_plane_intents (
|
|
2
|
+
intent_id TEXT PRIMARY KEY CHECK (
|
|
3
|
+
intent_id GLOB 'intent_[0-7][0-9A-HJKMNP-TV-Z][0-9A-HJKMNP-TV-Z][0-9A-HJKMNP-TV-Z]*'
|
|
4
|
+
AND length(intent_id) = 33
|
|
5
|
+
),
|
|
6
|
+
kind TEXT NOT NULL CHECK (
|
|
7
|
+
kind IN (
|
|
8
|
+
'task_compile_start',
|
|
9
|
+
'task_compile_continuation',
|
|
10
|
+
'task_bound_follow_up',
|
|
11
|
+
'scratchpad_refresh',
|
|
12
|
+
'reference_summary_refresh',
|
|
13
|
+
'run_dispatch'
|
|
14
|
+
)
|
|
15
|
+
),
|
|
16
|
+
owner_kind TEXT NOT NULL CHECK (
|
|
17
|
+
owner_kind IN (
|
|
18
|
+
'task_compile',
|
|
19
|
+
'clarification_round',
|
|
20
|
+
'scratchpad',
|
|
21
|
+
'reference_summary',
|
|
22
|
+
'task_run'
|
|
23
|
+
)
|
|
24
|
+
),
|
|
25
|
+
owner_ref TEXT NOT NULL CHECK (length(owner_ref) BETWEEN 1 AND 512),
|
|
26
|
+
dedupe_key TEXT NOT NULL UNIQUE CHECK (length(dedupe_key) BETWEEN 1 AND 512),
|
|
27
|
+
lane TEXT NOT NULL CHECK (lane IN ('foreground', 'scratchpad_background', 'run')),
|
|
28
|
+
priority INTEGER NOT NULL DEFAULT 100 CHECK (priority >= 0),
|
|
29
|
+
state TEXT NOT NULL DEFAULT 'pending' CHECK (
|
|
30
|
+
state IN ('pending', 'claimed', 'running', 'completed', 'failed', 'cancelled')
|
|
31
|
+
),
|
|
32
|
+
available_at TEXT NOT NULL,
|
|
33
|
+
claim_token TEXT CHECK (claim_token IS NULL OR length(claim_token) BETWEEN 1 AND 256),
|
|
34
|
+
lease_expires_at TEXT,
|
|
35
|
+
attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
|
|
36
|
+
last_error_code TEXT CHECK (
|
|
37
|
+
last_error_code IS NULL OR length(last_error_code) BETWEEN 1 AND 128
|
|
38
|
+
),
|
|
39
|
+
created_at TEXT NOT NULL,
|
|
40
|
+
updated_at TEXT NOT NULL,
|
|
41
|
+
completed_at TEXT,
|
|
42
|
+
CHECK (
|
|
43
|
+
(
|
|
44
|
+
state = 'pending'
|
|
45
|
+
AND claim_token IS NULL
|
|
46
|
+
AND lease_expires_at IS NULL
|
|
47
|
+
AND completed_at IS NULL
|
|
48
|
+
)
|
|
49
|
+
OR (
|
|
50
|
+
state IN ('claimed', 'running')
|
|
51
|
+
AND claim_token IS NOT NULL
|
|
52
|
+
AND lease_expires_at IS NOT NULL
|
|
53
|
+
AND completed_at IS NULL
|
|
54
|
+
)
|
|
55
|
+
OR (
|
|
56
|
+
state IN ('completed', 'failed', 'cancelled')
|
|
57
|
+
AND claim_token IS NULL
|
|
58
|
+
AND lease_expires_at IS NULL
|
|
59
|
+
AND completed_at IS NOT NULL
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE INDEX idx_control_plane_intents_dispatch
|
|
65
|
+
ON control_plane_intents (state, available_at ASC, priority ASC, created_at ASC, intent_id ASC);
|
|
66
|
+
|
|
67
|
+
CREATE INDEX idx_control_plane_intents_expired_claim
|
|
68
|
+
ON control_plane_intents (state, lease_expires_at ASC)
|
|
69
|
+
WHERE state IN ('claimed', 'running');
|
|
70
|
+
|
|
71
|
+
CREATE INDEX idx_control_plane_intents_owner
|
|
72
|
+
ON control_plane_intents (owner_kind, owner_ref, created_at ASC);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE pending_task_compile_submissions (
|
|
75
|
+
id TEXT PRIMARY KEY CHECK (id = 'current'),
|
|
76
|
+
workflow_ref TEXT NOT NULL UNIQUE,
|
|
77
|
+
accepted_activity_ref TEXT NOT NULL,
|
|
78
|
+
requested_at TEXT NOT NULL,
|
|
79
|
+
source_window_after_created_at TEXT,
|
|
80
|
+
source_window_after_message_id TEXT,
|
|
81
|
+
source_window_through_created_at TEXT,
|
|
82
|
+
source_window_through_message_id TEXT,
|
|
83
|
+
CHECK (
|
|
84
|
+
(source_window_after_created_at IS NULL AND source_window_after_message_id IS NULL)
|
|
85
|
+
OR (
|
|
86
|
+
source_window_after_created_at IS NOT NULL
|
|
87
|
+
AND source_window_after_message_id IS NOT NULL
|
|
88
|
+
)
|
|
89
|
+
),
|
|
90
|
+
CHECK (
|
|
91
|
+
(source_window_through_created_at IS NULL AND source_window_through_message_id IS NULL)
|
|
92
|
+
OR (
|
|
93
|
+
source_window_through_created_at IS NOT NULL
|
|
94
|
+
AND source_window_through_message_id IS NOT NULL
|
|
95
|
+
)
|
|
96
|
+
),
|
|
97
|
+
CHECK (
|
|
98
|
+
source_window_after_created_at IS NULL
|
|
99
|
+
OR source_window_through_created_at IS NULL
|
|
100
|
+
OR source_window_through_created_at > source_window_after_created_at
|
|
101
|
+
OR (
|
|
102
|
+
source_window_through_created_at = source_window_after_created_at
|
|
103
|
+
AND source_window_through_message_id >= source_window_after_message_id
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
CREATE TRIGGER pending_task_compile_submissions_immutable
|
|
109
|
+
BEFORE UPDATE ON pending_task_compile_submissions
|
|
110
|
+
BEGIN
|
|
111
|
+
SELECT RAISE(ABORT, 'pending Task Compile submission is immutable');
|
|
112
|
+
END;
|
|
113
|
+
|
|
114
|
+
ALTER TABLE scratchpad_source_state
|
|
115
|
+
ADD COLUMN in_flight_intent_id TEXT REFERENCES control_plane_intents(intent_id);
|
package/migrations/README.md
CHANGED
|
@@ -27,10 +27,11 @@
|
|
|
27
27
|
- `0012_scratchpad_receipts.sql`:为当前 Scratchpad 增加 `submitted` 状态字段,并新增已提交 Scratchpad 小票历史表。
|
|
28
28
|
- `0013_scratchpad_receipt_sources.sql`:新增当前 Scratchpad snapshot 与 submitted receipt 的 source message id 引用表,供 Timeline Task Accepted 追溯原始聊天依据。
|
|
29
29
|
- `0014_scratchpad_task_compile_context.sql`:把 Scratchpad source state 收敛为纯主群聊周期 cursor,新增 durable active Task Compile Context 与完整 source refs,并把 Current Scratchpad / receipt sources 限定为主群聊。该开发阶段 migration 不迁移旧调试数据,已有本地开发数据库需要 reset / recreate。
|
|
30
|
+
- `0015_control_plane_intents.sql`:新增 Control Plane durable dispatch intent,保存 bounded kind / owner / dedupe / lane、pending-to-running claim lease 与 terminal 状态,为 Clarification、Task Compile、Scratchpad、Reference Summary 和 Run 提供可恢复的 runtime handoff;同时用 immutable pending Task Compile submission row 固化 context capture 前的 workflow / main-chat cursor boundary,不保存 prompt、消息正文、provider output、secret 或 host path。
|
|
30
31
|
|
|
31
32
|
## 当前实现状态
|
|
32
33
|
|
|
33
|
-
- `0001_init.sql` 至 `
|
|
34
|
+
- `0001_init.sql` 至 `0015_control_plane_intents.sql` 已存在,代表当前 host project SQLite schema contract。`0014` 明确采用开发数据库重建策略,不提供旧 Scratchpad source/cursor 数据兼容。
|
|
34
35
|
- SQLite adapter、migration runner、checksum 校验、store metadata 初始化、transaction helper 与 command recovery scan helper 已实现。
|
|
35
36
|
- 协作 repository 与 Control Plane 已实现第一批业务读写;Phase 7 首批已复用 `0001_init.sql` 中的 `tasks.latest_run_result_id`、`task_details.result_json` 与 `run_results` 表完成 Run result / checks / promotion 摘要持久化。Control Plane recovery resolver 与 task-bound continuation / retry 复用现有 schema;新 Run 的 accepted checkpoint 业务 truth 继续写入最终 Run result / task detail result,Git tag / commit trailer 承担 crash recovery evidence。Timeline 默认从 `tasks`、`run_results` 与历史主群聊 `refs.worklist_feedback` 投影任务级审计节点,当前 task terminal Chat feedback 写入 `refs.task_result` 消息;`project_timeline_events` 只保留缺少稳定 record 的轻量可观测性事件和历史 reconcile 兼容记录。project workspace、working diff 与 ignored runtime state 都不进入 SQLite truth。`provider_usage_events` 只保存 token 计数、调用来源、模型名、记录时间和可空 anchor,不保存 provider secret、prompt 或 raw response。store 基础设施本身已经可以创建并迁移 `tutti.sqlite`。
|
|
36
37
|
|
|
@@ -25,6 +25,7 @@ export declare const ID_PREFIXES: {
|
|
|
25
25
|
readonly hostConnection: "host_connection_";
|
|
26
26
|
readonly joinToken: "join_token_";
|
|
27
27
|
readonly notice: "notice_";
|
|
28
|
+
readonly controlPlaneIntent: "intent_";
|
|
28
29
|
};
|
|
29
30
|
export type IdPrefix = (typeof ID_PREFIXES)[keyof typeof ID_PREFIXES];
|
|
30
31
|
export type PrefixedId<TPrefix extends string> = `${TPrefix}${string}`;
|
|
@@ -53,6 +54,7 @@ export type RelayDesktopEntryContinuationRef = PrefixedId<typeof ID_PREFIXES.rel
|
|
|
53
54
|
export type HostConnectionRef = PrefixedId<typeof ID_PREFIXES.hostConnection>;
|
|
54
55
|
export type JoinTokenRef = PrefixedId<typeof ID_PREFIXES.joinToken>;
|
|
55
56
|
export type NoticeId = PrefixedId<typeof ID_PREFIXES.notice>;
|
|
57
|
+
export type ControlPlaneIntentId = PrefixedId<typeof ID_PREFIXES.controlPlaneIntent>;
|
|
56
58
|
export declare function createPrefixedId<const TPrefix extends IdPrefix>(prefix: TPrefix): PrefixedId<TPrefix>;
|
|
57
59
|
export declare function isUlidBody(value: string): boolean;
|
|
58
60
|
export declare function getPrefixedIdBody(value: string, prefix: IdPrefix): string | null;
|
|
@@ -85,4 +87,5 @@ export declare const createRelayDesktopEntryContinuationRef: () => RelayDesktopE
|
|
|
85
87
|
export declare const createHostConnectionRef: () => HostConnectionRef;
|
|
86
88
|
export declare const createJoinTokenRef: () => JoinTokenRef;
|
|
87
89
|
export declare const createNoticeId: () => NoticeId;
|
|
90
|
+
export declare const createControlPlaneIntentId: () => ControlPlaneIntentId;
|
|
88
91
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -27,6 +27,7 @@ export const ID_PREFIXES = {
|
|
|
27
27
|
hostConnection: "host_connection_",
|
|
28
28
|
joinToken: "join_token_",
|
|
29
29
|
notice: "notice_",
|
|
30
|
+
controlPlaneIntent: "intent_",
|
|
30
31
|
};
|
|
31
32
|
export function createPrefixedId(prefix) {
|
|
32
33
|
return `${prefix}${ulid()}`;
|
|
@@ -87,4 +88,5 @@ export const createRelayDesktopEntryContinuationRef = () => createPrefixedId(ID_
|
|
|
87
88
|
export const createHostConnectionRef = () => createPrefixedId(ID_PREFIXES.hostConnection);
|
|
88
89
|
export const createJoinTokenRef = () => createPrefixedId(ID_PREFIXES.joinToken);
|
|
89
90
|
export const createNoticeId = () => createPrefixedId(ID_PREFIXES.notice);
|
|
91
|
+
export const createControlPlaneIntentId = () => createPrefixedId(ID_PREFIXES.controlPlaneIntent);
|
|
90
92
|
//# sourceMappingURL=index.js.map
|