@xfey/tutti 0.1.127 → 0.1.129

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 (85) hide show
  1. package/dist/approvals/decision-command.d.ts +2 -2
  2. package/dist/chat-assistant/diagnostics.d.ts +17 -3
  3. package/dist/chat-assistant/diagnostics.js +5 -2
  4. package/dist/chat-assistant/failure-messages.d.ts +4 -0
  5. package/dist/chat-assistant/failure-messages.js +57 -0
  6. package/dist/chat-assistant/failures.d.ts +27 -0
  7. package/dist/chat-assistant/failures.js +46 -0
  8. package/dist/chat-assistant/index.d.ts +18 -4
  9. package/dist/chat-assistant/index.js +254 -51
  10. package/dist/chat-assistant/language-context.d.ts +2 -0
  11. package/dist/chat-assistant/language-context.js +17 -14
  12. package/dist/chat-assistant/requests.d.ts +34 -0
  13. package/dist/chat-assistant/requests.js +64 -0
  14. package/dist/collaboration-ingestion/clarification.js +3 -1
  15. package/dist/collaboration-ingestion/main-chat.js +1 -0
  16. package/dist/collaboration-ingestion/types.d.ts +2 -0
  17. package/dist/collaboration-state/clarification-snapshot.d.ts +5 -5
  18. package/dist/collaboration-state/context-sync-attempts.d.ts +9 -0
  19. package/dist/collaboration-state/context-sync-attempts.js +13 -0
  20. package/dist/collaboration-state/task-compile-context.d.ts +1 -1
  21. package/dist/collaboration-state/task-compile-context.js +11 -1
  22. package/dist/collaboration-state/task-compile-requests.d.ts +6 -0
  23. package/dist/collaboration-state/task-compile-requests.js +16 -0
  24. package/dist/communication-language/notice-locale.d.ts +4 -0
  25. package/dist/communication-language/notice-locale.js +14 -0
  26. package/dist/control-plane/context-sync-completion.d.ts +7 -0
  27. package/dist/control-plane/context-sync-completion.js +27 -0
  28. package/dist/control-plane/context-sync.d.ts +1 -0
  29. package/dist/control-plane/context-sync.js +23 -0
  30. package/dist/control-plane/index.d.ts +13 -4
  31. package/dist/control-plane/index.js +30 -23
  32. package/dist/control-plane/task-compile-continuation.js +2 -0
  33. package/dist/control-plane/task-compile-request-status.d.ts +6 -0
  34. package/dist/control-plane/task-compile-request-status.js +70 -0
  35. package/dist/control-plane/task-compile-start.js +40 -2
  36. package/dist/control-plane/types.d.ts +2 -0
  37. package/dist/providers/failure-classification.d.ts +23 -0
  38. package/dist/providers/failure-classification.js +43 -0
  39. package/dist/providers/invocation-progress.d.ts +6 -0
  40. package/dist/providers/invocation-progress.js +24 -0
  41. package/dist/providers/openai/app-server/read-only-procedure.js +1 -0
  42. package/dist/providers/openai/chat-assistant.js +2 -1
  43. package/dist/reference-access/read-evidence.d.ts +2 -0
  44. package/dist/reference-access/read-evidence.js +5 -0
  45. package/dist/reference-access/service.d.ts +2 -0
  46. package/dist/reference-access/service.js +2 -0
  47. package/dist/runtime-failures.js +15 -1
  48. package/dist/server-shell/cli/external-reference-runtime.js +6 -3
  49. package/dist/server-shell/cli/host-server-runtime.js +8 -2
  50. package/dist/server-shell/cli/relay-registration.js +1 -0
  51. package/dist/server-shell/http/routes/project-api/messages-routes.js +22 -1
  52. package/dist/server-shell/http/routes/project-api/openapi-collaboration-routes.d.ts +140 -5
  53. package/dist/server-shell/http/routes/project-api/openapi-collaboration-routes.js +29 -2
  54. package/dist/server-shell/http/routes/project-api/openapi-skills-routes.d.ts +2 -0
  55. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +6 -0
  56. package/dist/server-shell/http/routes/project-api/openapi.d.ts +148 -5
  57. package/dist/server-shell/http/routes/project-api/schemas.d.ts +6 -0
  58. package/dist/server-shell/http/routes/project-api/schemas.js +1 -0
  59. package/dist/server-shell/http/routes/project-api/types.d.ts +1 -0
  60. package/dist/server-shell/http/routes/project-api/workflow-routes.js +39 -3
  61. package/dist/server-shell/http/routes/project-api/workspace-projections.js +1 -0
  62. package/dist/server-shell/http/validation.js +8 -2
  63. package/dist/server-shell/local-console/managed-project-service.d.ts +1 -1
  64. package/dist/server-shell/managed-access/transport.d.ts +1 -1
  65. package/migrations/0024_chat_requests.sql +19 -0
  66. package/migrations/0025_task_compile_requests.sql +50 -0
  67. package/migrations/0026_context_sync_and_chat_locale.sql +12 -0
  68. package/migrations/README.md +3 -1
  69. package/node_modules/@tutti/relay-client/dist/host-control.d.ts +1 -0
  70. package/node_modules/@tutti/relay-client/dist/host-control.js +1 -0
  71. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -0
  72. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -0
  73. package/node_modules/@tutti/shared/dist/schemas/api/operation-status.d.ts +174 -0
  74. package/node_modules/@tutti/shared/dist/schemas/api/operation-status.js +75 -0
  75. package/node_modules/@tutti/shared/dist/schemas/api/primitives.d.ts +1 -0
  76. package/node_modules/@tutti/shared/dist/schemas/api/primitives.js +1 -0
  77. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.d.ts +1 -0
  78. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.js +2 -1
  79. package/package.json +1 -1
  80. package/web/assets/{homepage-motion-scene-DyEmWwvt.js → homepage-motion-scene-BHhw4CfM.js} +1 -1
  81. package/web/assets/index-5COc1Yku.js +70 -0
  82. package/web/assets/index-BJvKuUiT.css +1 -0
  83. package/web/index.html +2 -2
  84. package/web/assets/index-By3idT4U.js +0 -70
  85. package/web/assets/index-DSBjOZmJ.css +0 -1
@@ -13,8 +13,9 @@ export declare function readSubmittedClarificationMessages(db: SqliteDatabase, r
13
13
  is_current_round: boolean;
14
14
  };
15
15
  refs?: {
16
- task_id?: string;
17
16
  activity_ref?: string;
17
+ workflow_ref?: string;
18
+ task_id?: string;
18
19
  artifact_feedback?: {
19
20
  summary?: string;
20
21
  kind: "artifact_feedback";
@@ -106,15 +107,14 @@ export declare function readSubmittedClarificationMessages(db: SqliteDatabase, r
106
107
  blob_oid?: string;
107
108
  path: string;
108
109
  name: string;
109
- source: "human_upload" | "tutti_output";
110
110
  category: "file" | "image";
111
+ source: "human_upload" | "tutti_output";
111
112
  size_bytes: number;
112
113
  text_read: {
113
114
  blocked_reason?: "unknown" | "binary" | "too_large" | "unsupported_encoding" | "restricted_path" | "secret_like";
114
115
  can_read: boolean;
115
116
  };
116
117
  };
117
- workflow_ref?: string;
118
118
  clarification_round_ref?: string;
119
119
  clarification_thread_ref?: string;
120
120
  task_result?: {
@@ -141,10 +141,10 @@ export declare function readSubmittedClarificationMessages(db: SqliteDatabase, r
141
141
  };
142
142
  worklist_compile?: {
143
143
  kind: "worklist_compile";
144
- title: string;
145
144
  workflow_ref: string;
145
+ title: string;
146
146
  tasks: {
147
- status: "failed" | "pending" | "done" | "todo" | "running";
147
+ status: "failed" | "pending" | "running" | "done" | "todo";
148
148
  task_id: string;
149
149
  title: string;
150
150
  summary: string;
@@ -0,0 +1,9 @@
1
+ import type { SqliteDatabase } from "../store/index.js";
2
+ export declare function hasContextSyncCompletionAttempt(db: SqliteDatabase, batchKey: string): boolean;
3
+ /** Persist before invoking the provider; failure/unknown outcome is not a retry permit. */
4
+ export declare function recordContextSyncCompletionAttempt(db: SqliteDatabase, input: {
5
+ batchKey: string;
6
+ workflowRef: string;
7
+ startedAt: string;
8
+ }): boolean;
9
+ //# sourceMappingURL=context-sync-attempts.d.ts.map
@@ -0,0 +1,13 @@
1
+ export function hasContextSyncCompletionAttempt(db, batchKey) {
2
+ return (db
3
+ .prepare("SELECT 1 AS present FROM context_sync_completion_attempts WHERE batch_key = ?")
4
+ .get(batchKey) !== undefined);
5
+ }
6
+ /** Persist before invoking the provider; failure/unknown outcome is not a retry permit. */
7
+ export function recordContextSyncCompletionAttempt(db, input) {
8
+ return (db
9
+ .prepare(`INSERT OR IGNORE INTO context_sync_completion_attempts(batch_key, workflow_ref, started_at)
10
+ VALUES (?, ?, ?)`)
11
+ .run(input.batchKey, input.workflowRef, input.startedAt).changes === 1);
12
+ }
13
+ //# sourceMappingURL=context-sync-attempts.js.map
@@ -24,7 +24,7 @@ export declare function completeTaskCompileContext(db: SqliteDatabase, input: Co
24
24
  task_changes: string[];
25
25
  receipts: {
26
26
  compiled_tasks?: {
27
- status: "failed" | "pending" | "done" | "todo" | "running";
27
+ status: "failed" | "pending" | "running" | "done" | "todo";
28
28
  task_id: string;
29
29
  title: string;
30
30
  summary: string;
@@ -3,6 +3,7 @@ import { readScratchpadProjection } from "./scratchpad.js";
3
3
  import { readCommunicationLanguage, copyCommunicationLanguage } from "./communication-language.js";
4
4
  import { readScratchpadSourceState } from "./scratchpad-source-state.js";
5
5
  import { nowIso, stringifyJson } from "./serialization.js";
6
+ import { registerTaskCompileRequest, closeFailedTaskCompileRequest, } from "./task-compile-requests.js";
6
7
  function cursorIsAfter(left, right) {
7
8
  if (left === undefined) {
8
9
  return false;
@@ -145,6 +146,7 @@ export function recordTaskCompileSubmission(db, input) {
145
146
  if (readActiveTaskCompileContext(db) !== null) {
146
147
  throw new Error("An active Task Compile Context already exists");
147
148
  }
149
+ registerTaskCompileRequest(db, input.workflow_ref, input.accepted_activity_ref, nowIso(input.now));
148
150
  db.prepare(`
149
151
  INSERT INTO pending_task_compile_submissions (
150
152
  id,
@@ -226,6 +228,7 @@ export function captureTaskCompileContext(db, input) {
226
228
  : { created_at: lastSource.created_at, message_id: lastSource.message_id });
227
229
  const after = submission?.source_window.after ?? sourceState.cycle_start_cursor;
228
230
  const submittedAt = nowIso(input.now);
231
+ registerTaskCompileRequest(tx, input.workflow_ref, input.captured_by_activity_ref, submittedAt);
229
232
  tx.prepare(`
230
233
  INSERT INTO active_task_compile_context (
231
234
  id,
@@ -337,9 +340,16 @@ export function rollbackTaskCompileContext(db, input) {
337
340
  return withHostStoreTransaction(db, (tx) => {
338
341
  const context = readActiveTaskCompileContext(tx, input.workflow_ref);
339
342
  if (context === null) {
340
- return false;
343
+ const pending = readPendingTaskCompileSubmission(tx, input.workflow_ref);
344
+ if (pending === null)
345
+ return false;
346
+ // Before capture the source cursor has not moved: release only this admission.
347
+ closeFailedTaskCompileRequest(tx, input.workflow_ref, nowIso(input.now));
348
+ tx.prepare("DELETE FROM pending_task_compile_submissions WHERE workflow_ref = ?").run(input.workflow_ref);
349
+ return true;
341
350
  }
342
351
  const rolledBackAt = nowIso(input.now);
352
+ closeFailedTaskCompileRequest(tx, input.workflow_ref, rolledBackAt);
343
353
  const sourceState = readScratchpadSourceState(tx);
344
354
  const after = context.source_window.after;
345
355
  const through = context.source_window.through;
@@ -0,0 +1,6 @@
1
+ import type { SqliteDatabase } from "../store/index.js";
2
+ /** Persistence only: callers own acceptance, stage selection and rollback. */
3
+ export declare function registerTaskCompileRequest(db: SqliteDatabase, workflowRef: string, activityRef: string, createdAt: string): void;
4
+ export declare function linkTaskCompileActivity(db: SqliteDatabase, workflowRef: string, activityRef: string): void;
5
+ export declare function closeFailedTaskCompileRequest(db: SqliteDatabase, workflowRef: string, now: string, code?: "task_compile_failed" | "scratchpad_empty_after_refresh"): void;
6
+ //# sourceMappingURL=task-compile-requests.d.ts.map
@@ -0,0 +1,16 @@
1
+ /** Persistence only: callers own acceptance, stage selection and rollback. */
2
+ export function registerTaskCompileRequest(db, workflowRef, activityRef, createdAt) {
3
+ db.prepare(`INSERT OR IGNORE INTO task_compile_requests
4
+ (workflow_ref, accepted_activity_ref, current_activity_ref, created_at)
5
+ VALUES (?, ?, ?, ?)`).run(workflowRef, activityRef, activityRef, createdAt);
6
+ }
7
+ export function linkTaskCompileActivity(db, workflowRef, activityRef) {
8
+ db.prepare(`UPDATE task_compile_requests SET current_activity_ref = ?
9
+ WHERE workflow_ref = ? AND closed_at IS NULL`).run(activityRef, workflowRef);
10
+ }
11
+ export function closeFailedTaskCompileRequest(db, workflowRef, now, code = "task_compile_failed") {
12
+ db.prepare(`UPDATE task_compile_requests SET failure_code = ?, closed_at = ?
13
+ WHERE workflow_ref = ? AND closed_at IS NULL
14
+ AND NOT EXISTS (SELECT 1 FROM scratchpad_receipts WHERE workflow_ref = ?)`).run(code, now, workflowRef, workflowRef);
15
+ }
16
+ //# sourceMappingURL=task-compile-requests.js.map
@@ -0,0 +1,4 @@
1
+ /** Small static notice catalog, not language detection or a UI locale setting. */
2
+ export type NoticeLocale = "en" | "zh-Hans" | "zh-Hant";
3
+ export declare function noticeLocale(locale?: string | null): NoticeLocale;
4
+ //# sourceMappingURL=notice-locale.d.ts.map
@@ -0,0 +1,14 @@
1
+ export function noticeLocale(locale) {
2
+ if (locale === undefined || locale === null)
3
+ return "en";
4
+ try {
5
+ const parsed = new Intl.Locale(locale);
6
+ if (parsed.language !== "zh")
7
+ return "en";
8
+ return parsed.maximize().script === "Hant" ? "zh-Hant" : "zh-Hans";
9
+ }
10
+ catch {
11
+ return "en";
12
+ }
13
+ }
14
+ //# sourceMappingURL=notice-locale.js.map
@@ -0,0 +1,7 @@
1
+ import type { SqliteDatabase } from "../store/index.js";
2
+ import type { ContextSyncWorkflowTrigger } from "./workflows/index.js";
3
+ export declare function pendingContextSyncCompletion(db: SqliteDatabase): {
4
+ key: string;
5
+ trigger: ContextSyncWorkflowTrigger;
6
+ } | undefined;
7
+ //# sourceMappingURL=context-sync-completion.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { createHash } from "node:crypto";
2
+ import { hasContextSyncCompletionAttempt } from "../collaboration-state/context-sync-attempts.js";
3
+ import { readWorklistProjection } from "../collaboration-state/index.js";
4
+ export function pendingContextSyncCompletion(db) {
5
+ const worklist = readWorklistProjection(db);
6
+ const tasks = worklist.modules
7
+ .flatMap((entry) => entry.tasks)
8
+ .sort((a, b) => (a.id < b.id ? -1 : 1));
9
+ if (tasks.length === 0 || tasks.some((task) => task.status !== "done"))
10
+ return undefined;
11
+ // A changed result on the same task is a new completion. Source messages,
12
+ // ordering, labels and context_sync's own Git commit are deliberately absent.
13
+ const key = createHash("sha256")
14
+ .update(JSON.stringify(tasks.map((task) => [task.id, task.latest_run_result_id])))
15
+ .digest("hex");
16
+ if (hasContextSyncCompletionAttempt(db, key))
17
+ return undefined;
18
+ return {
19
+ key,
20
+ trigger: {
21
+ kind: "worklist_all_tasks_completed",
22
+ completed_task_count: tasks.length,
23
+ module_count: worklist.modules.length,
24
+ },
25
+ };
26
+ }
27
+ //# sourceMappingURL=context-sync-completion.js.map
@@ -10,6 +10,7 @@ export declare function startContextSyncAnalysisProcedure(input: {
10
10
  store: HostProjectStore;
11
11
  events: WorkspaceEventBus;
12
12
  trigger: ContextSyncWorkflowTrigger;
13
+ completionBatchKey?: string;
13
14
  resolveWorkflowRunner: ProcedureWorkflowRunnerResolver;
14
15
  readActiveActivity: () => {
15
16
  activity_ref: ActivityRef;
@@ -3,6 +3,7 @@ import { projectLanguageContext, logLanguageContext } from "./communication-lang
3
3
  import { recordProjectTimelineEvent } from "../project-timeline/index.js";
4
4
  import { syncProjectDocs } from "../workspace-ops/index.js";
5
5
  import { logProcedureExecutionError } from "./procedure-logging.js";
6
+ import { recordContextSyncCompletionAttempt } from "../collaboration-state/context-sync-attempts.js";
6
7
  import { projectBriefShouldRefreshForUpdatedPaths, refreshProjectBriefProjection, } from "./project-brief-refresh.js";
7
8
  function publishContextSyncTimelineChanged(input) {
8
9
  input.events.publish({
@@ -63,6 +64,28 @@ export function startContextSyncAnalysisProcedure(input) {
63
64
  error,
64
65
  }),
65
66
  execute: async (context) => {
67
+ if (input.completionBatchKey !== undefined) {
68
+ const recorded = recordContextSyncCompletionAttempt(input.store.db, {
69
+ batchKey: input.completionBatchKey,
70
+ workflowRef,
71
+ startedAt: input.now().toISOString(),
72
+ });
73
+ if (!recorded)
74
+ return {
75
+ kind: "completed",
76
+ summary: "These task results were already checked for document maintenance.",
77
+ };
78
+ try {
79
+ input.logger?.info({
80
+ workflow_ref: workflowRef,
81
+ activity_ref: context.activity_ref,
82
+ trigger: "worklist_all_tasks_completed",
83
+ }, "Automatic context sync attempt recorded");
84
+ }
85
+ catch {
86
+ /* Logging must not prevent the recorded maintenance attempt. */
87
+ }
88
+ }
66
89
  const language = projectLanguageContext(input.store.db, "documents");
67
90
  logLanguageContext(input.logger, language, {
68
91
  workflow_ref: workflowRef,
@@ -1,9 +1,16 @@
1
1
  import { type ActivityRef, type ClarificationRoundRef, type IdempotencyKey, type TaskId, type WorkflowInvocationRef } from "@tutti/shared/ids";
2
2
  import type { TrustedCollaborationAuthor } from "../collaboration-ingestion/types.js";
3
+ import type { WorkflowKind } from "@tutti/shared/domain";
3
4
  import type { ExecutionStatusProjection, MessageProjection, ContextSyncDisposition, ContextSyncResult, RefreshScratchpadDisposition, RefreshScratchpadResult, RunSchedulerNowDisposition, RunSchedulerNowPayload, RetryTaskDisposition, RetryTaskPayload, RefreshReferenceSummariesDisposition, RefreshReferenceSummariesPayload, RefreshReferenceSummariesResult, SendClarificationRoundMessageDisposition, SendClarificationRoundMessagePayload, SendClarificationRoundMessageResult, SubmitClarificationRoundDisposition, SubmitClarificationRoundPayload, SubmitClarificationRoundResult, SubmitWorklistDisposition, SubmitWorklistPayload, SubmitWorklistResult, ProcedureLane } from "@tutti/shared/schemas/api";
4
- import type { ContextSyncWorkflowTrigger } from "./workflows/index.js";
5
5
  import type { ControlPlaneCommandResult, Phase5ControlPlaneOptions, StartupRecoveryOptions, StartupRecoveryResult } from "./types.js";
6
6
  import { type ProjectBriefRefreshReason, type RefreshProjectBriefProjectionResult } from "./project-brief-refresh.js";
7
+ type ActiveActivity = {
8
+ activity_ref: ActivityRef;
9
+ activity_kind: "run" | "procedure";
10
+ lane?: ProcedureLane;
11
+ workflow_kind?: WorkflowKind;
12
+ workflow_ref?: WorkflowInvocationRef;
13
+ };
7
14
  export type { ControlPlaneCommandResult, ControlPlaneLogger, Phase5ControlPlaneOptions, RunCorrectionContext, RunContinuationContext, RunDispatchContext, RunDispatchTrigger, RunPipelineInput, RunPipelineResolution, RunPipelineResolver, RunPipelineResult, RunPipelineRunner, StartupRecoveryOptions, StartupRecoveryResult, TrustedHumanAuthor, } from "./types.js";
8
15
  export declare class Phase5ControlPlane {
9
16
  private readonly engine;
@@ -14,6 +21,7 @@ export declare class Phase5ControlPlane {
14
21
  private readonly resolveRunPipeline;
15
22
  private readonly readMainlineStatus;
16
23
  private readonly readActiveApproval;
24
+ private readonly hasPendingChatRequests;
17
25
  private readonly logger;
18
26
  private readonly projectContext;
19
27
  private readonly runLifecycle;
@@ -46,9 +54,10 @@ export declare class Phase5ControlPlane {
46
54
  }): Promise<RefreshProjectBriefProjectionResult>;
47
55
  beginShutdown(): void;
48
56
  dispose(): Promise<void>;
49
- waitForRepoInspectionIdle(): Promise<void>;
57
+ waitForRepoInspectionIdle(onBlocked?: (blocker: ActiveActivity) => void): Promise<void>;
50
58
  startTaskCompile(payload: SubmitWorklistPayload): ControlPlaneCommandResult<SubmitWorklistDisposition, SubmitWorklistResult>;
51
- startContextSyncAnalysis(trigger?: ContextSyncWorkflowTrigger): ControlPlaneCommandResult<ContextSyncDisposition, ContextSyncResult>;
59
+ startContextSyncAnalysis(): ControlPlaneCommandResult<ContextSyncDisposition, ContextSyncResult>;
60
+ private startContextSync;
52
61
  startReferenceSummaryRefresh(payload?: RefreshReferenceSummariesPayload): ControlPlaneCommandResult<RefreshReferenceSummariesDisposition, RefreshReferenceSummariesResult>;
53
62
  private tryStartPendingReferenceSummaryRefresh;
54
63
  sendClarificationRoundMessage(roundId: ClarificationRoundRef, payload: SendClarificationRoundMessagePayload, author: TrustedCollaborationAuthor): ControlPlaneCommandResult<SendClarificationRoundMessageDisposition, SendClarificationRoundMessageResult>;
@@ -71,7 +80,7 @@ export declare class Phase5ControlPlane {
71
80
  private handleRunTerminalTransition;
72
81
  private tryRunTaskArrivalScheduler;
73
82
  private maybeScheduleAfterRunCompletion;
74
- private worklistCompletionContextSyncTrigger;
83
+ notifyChatRequestsChanged(): void;
75
84
  private scheduleContextSyncAfterWorklistCompletion;
76
85
  private attemptContextSyncAfterWorklistCompletion;
77
86
  private cancelPendingWorklistCompletionContextSync;
@@ -1,9 +1,10 @@
1
- import { readActiveClarificationProjection, readActiveTaskCompileContext, markScratchpadRefreshSucceeded, readPendingTaskCompileSubmission, readTaskDetailProjection, readTaskProjectionByActivityRef, readScratchpadSourceState, readWorklistProjection, } from "../collaboration-state/index.js";
1
+ import { readActiveClarificationProjection, readActiveTaskCompileContext, markScratchpadRefreshSucceeded, readPendingTaskCompileSubmission, readTaskDetailProjection, readTaskProjectionByActivityRef, readScratchpadSourceState, } from "../collaboration-state/index.js";
2
2
  import { ProcedureEngine } from "../procedure-engine/index.js";
3
3
  import { RunEngine } from "../run-engine/index.js";
4
4
  import { WorkspaceOpsError, isReferenceFilePath, readReferenceSummaryTargets, } from "../workspace-ops/index.js";
5
5
  import { publishProcedureTransition as publishProcedureTransitionEvent, publishRunResultRecorded as publishRunResultRecordedEvent, publishRunTransition as publishRunTransitionEvent, publishTaskRunStarted as publishTaskRunStartedEvent, } from "./event-publishers.js";
6
6
  import { startContextSyncAnalysisProcedure } from "./context-sync.js";
7
+ import { pendingContextSyncCompletion } from "./context-sync-completion.js";
7
8
  import { refreshProjectBriefProjection, } from "./project-brief-refresh.js";
8
9
  import { startReferenceSummaryRefreshProcedure } from "./reference-summary-refresh.js";
9
10
  import { runControlPlaneStartupRecovery } from "./startup-recovery.js";
@@ -30,6 +31,7 @@ export class Phase5ControlPlane {
30
31
  resolveRunPipeline;
31
32
  readMainlineStatus;
32
33
  readActiveApproval;
34
+ hasPendingChatRequests;
33
35
  logger;
34
36
  projectContext;
35
37
  runLifecycle;
@@ -53,6 +55,7 @@ export class Phase5ControlPlane {
53
55
  this.resolveRunPipeline = options.resolveRunPipeline;
54
56
  this.readMainlineStatus = options.readMainlineStatus;
55
57
  this.readActiveApproval = options.readActiveApproval;
58
+ this.hasPendingChatRequests = options.hasPendingChatRequests ?? (() => false);
56
59
  this.logger = options.logger;
57
60
  this.projectContext = options.projectContext;
58
61
  this.runLifecycle = options.runLifecycle;
@@ -177,13 +180,17 @@ export class Phase5ControlPlane {
177
180
  this.beginShutdown();
178
181
  return this.waitForIdle();
179
182
  }
180
- async waitForRepoInspectionIdle() {
183
+ async waitForRepoInspectionIdle(onBlocked) {
181
184
  for (;;) {
182
185
  const waits = [];
183
- if (this.readActiveRun() !== null) {
186
+ const run = this.readActiveRun();
187
+ const procedure = this.readActiveForegroundProcedure();
188
+ if (run !== null) {
189
+ onBlocked?.(run);
184
190
  waits.push(this.runEngine.waitForIdle());
185
191
  }
186
- if (this.readActiveForegroundProcedure() !== null) {
192
+ if (procedure !== null) {
193
+ onBlocked?.(procedure);
187
194
  waits.push(this.engine.waitForLaneIdle("foreground"));
188
195
  }
189
196
  if (waits.length === 0) {
@@ -233,15 +240,17 @@ export class Phase5ControlPlane {
233
240
  shouldContinue: () => !this.closing,
234
241
  });
235
242
  }
236
- startContextSyncAnalysis(trigger = { kind: "manual" }) {
237
- if (trigger.kind === "manual") {
238
- this.cancelPendingWorklistCompletionContextSync();
239
- }
243
+ startContextSyncAnalysis() {
244
+ this.cancelPendingWorklistCompletionContextSync();
245
+ return this.startContextSync({ kind: "manual" });
246
+ }
247
+ startContextSync(trigger, completionBatchKey) {
240
248
  return startContextSyncAnalysisProcedure({
241
249
  engine: this.engine,
242
250
  store: this.store,
243
251
  events: this.events,
244
252
  trigger,
253
+ ...(completionBatchKey === undefined ? {} : { completionBatchKey }),
245
254
  resolveWorkflowRunner: this.resolveWorkflowRunner,
246
255
  readActiveActivity: () => this.readSchedulerBlocker(),
247
256
  projectContext: this.projectContext,
@@ -544,17 +553,13 @@ export class Phase5ControlPlane {
544
553
  }
545
554
  this.scheduleContextSyncAfterWorklistCompletion();
546
555
  }
547
- worklistCompletionContextSyncTrigger() {
548
- const worklist = readWorklistProjection(this.store.db);
549
- const tasks = worklist.modules.flatMap((entry) => entry.tasks);
550
- if (tasks.length === 0 || tasks.some((task) => task.status !== "done")) {
551
- return undefined;
552
- }
553
- return {
554
- kind: "worklist_all_tasks_completed",
555
- completed_task_count: tasks.length,
556
- module_count: worklist.modules.length,
557
- };
556
+ notifyChatRequestsChanged() {
557
+ if (this.closing || !this.store.db.open)
558
+ return;
559
+ if (this.hasPendingChatRequests())
560
+ this.cancelPendingWorklistCompletionContextSync();
561
+ else
562
+ this.scheduleContextSyncAfterWorklistCompletion();
558
563
  }
559
564
  scheduleContextSyncAfterWorklistCompletion() {
560
565
  if (this.closing ||
@@ -563,7 +568,8 @@ export class Phase5ControlPlane {
563
568
  return;
564
569
  }
565
570
  const sourceState = readScratchpadSourceState(this.store.db);
566
- if (this.worklistCompletionContextSyncTrigger() === undefined ||
571
+ if (pendingContextSyncCompletion(this.store.db) === undefined ||
572
+ this.hasPendingChatRequests() ||
567
573
  sourceState.dirty_since !== undefined ||
568
574
  sourceState.in_flight !== undefined ||
569
575
  this.readSchedulerBlocker() !== null) {
@@ -579,15 +585,16 @@ export class Phase5ControlPlane {
579
585
  if (this.closing || !this.store.db.open) {
580
586
  return;
581
587
  }
582
- const trigger = this.worklistCompletionContextSyncTrigger();
588
+ const completion = pendingContextSyncCompletion(this.store.db);
583
589
  const sourceState = readScratchpadSourceState(this.store.db);
584
- if (trigger === undefined ||
590
+ if (completion === undefined ||
591
+ this.hasPendingChatRequests() ||
585
592
  sourceState.dirty_since !== undefined ||
586
593
  sourceState.in_flight !== undefined ||
587
594
  this.readSchedulerBlocker() !== null) {
588
595
  return;
589
596
  }
590
- void this.startContextSyncAnalysis(trigger);
597
+ void this.startContextSync(completion.trigger, completion.key);
591
598
  }
592
599
  cancelPendingWorklistCompletionContextSync() {
593
600
  if (this.pendingWorklistCompletionContextSyncTimer === null) {
@@ -1,5 +1,6 @@
1
1
  import { materializeClarificationSuccessor, readActiveTaskCompileContext, readClarificationRoundProjection, readWorklistProjection, rollbackTaskCompileContext, } from "../collaboration-state/index.js";
2
2
  import { withHostStoreTransaction } from "../store/index.js";
3
+ import { linkTaskCompileActivity } from "../collaboration-state/task-compile-requests.js";
3
4
  import { clarificationInvalidates } from "./invalidations.js";
4
5
  import { applyTaskCompileProcedureOutput } from "./task-compile-output.js";
5
6
  import { readTaskCompileClarificationRounds } from "./task-compile-clarification-context.js";
@@ -27,6 +28,7 @@ export function startControlPlaneTaskCompileContinuation(options) {
27
28
  summary: "Continuing task_compile from clarification input.",
28
29
  now: options.now,
29
30
  onTransition: (state) => {
31
+ linkTaskCompileActivity(options.store.db, workflowRef, state.activity.activity_ref);
30
32
  options.publishProcedureTransition(state);
31
33
  if (state.transition_kind === "terminal") {
32
34
  options.onTerminal?.(state.activity.status === "failed"
@@ -0,0 +1,6 @@
1
+ import type { ExecutionStatusProjection, WorklistSubmissionStatus } from "@tutti/shared/schemas/api";
2
+ import type { SqliteDatabase } from "../store/index.js";
3
+ /** Pure projection of owned records. A missing receipt is never proof of failure. */
4
+ export declare function readTaskCompileRequestStatus(db: SqliteDatabase, execution: ExecutionStatusProjection, workflowRef?: string): WorklistSubmissionStatus | null;
5
+ export declare function readTaskCompileCommandRef(db: SqliteDatabase, commandId: string): string | undefined;
6
+ //# sourceMappingURL=task-compile-request-status.d.ts.map
@@ -0,0 +1,70 @@
1
+ import { readActiveClarificationProjection, readActiveTaskCompileContext, readPendingTaskCompileSubmission, } from "../collaboration-state/index.js";
2
+ /** Pure projection of owned records. A missing receipt is never proof of failure. */
3
+ export function readTaskCompileRequestStatus(db, execution, workflowRef) {
4
+ const row = workflowRef === undefined
5
+ ? db
6
+ .prepare("SELECT * FROM task_compile_requests ORDER BY rowid DESC LIMIT 1")
7
+ .get()
8
+ : db
9
+ .prepare("SELECT * FROM task_compile_requests WHERE workflow_ref = ?")
10
+ .get(workflowRef);
11
+ if (row === undefined)
12
+ return null;
13
+ const common = {
14
+ operation: { kind: "worklist_submit", ref: row.workflow_ref },
15
+ created_at: row.created_at,
16
+ activity_ref: row.current_activity_ref,
17
+ };
18
+ if (db.prepare("SELECT 1 FROM scratchpad_receipts WHERE workflow_ref = ?").get(row.workflow_ref)) {
19
+ return { ...common, status: "succeeded", phase: "settled" };
20
+ }
21
+ if (row.failure_code !== null) {
22
+ return {
23
+ ...common,
24
+ status: "failed",
25
+ phase: "settled",
26
+ failure: {
27
+ code: row.failure_code,
28
+ category: "internal",
29
+ recovery: "none",
30
+ diagnostic_id: row.workflow_ref,
31
+ },
32
+ };
33
+ }
34
+ const clarification = readActiveClarificationProjection(db);
35
+ if (clarification.state === "active" &&
36
+ clarification.thread.owner.kind === "task_compile" &&
37
+ clarification.thread.owner.workflow_ref === row.workflow_ref &&
38
+ clarification.round.status === "writable") {
39
+ return {
40
+ ...common,
41
+ status: "waiting_input",
42
+ phase: "clarification",
43
+ round_id: clarification.round.id,
44
+ };
45
+ }
46
+ const activities = execution.active_activities ?? (execution.active_activity ? [execution.active_activity] : []);
47
+ const active = activities.find((entry) => entry.activity_ref === row.current_activity_ref || entry.workflow_ref === row.workflow_ref);
48
+ if (active !== undefined) {
49
+ const kind = active.workflow_kind;
50
+ const phase = kind === "scratchpad_refresh" ||
51
+ kind === "reference_summary_refresh" ||
52
+ kind === "project_context_bootstrap" ||
53
+ kind === "task_compile"
54
+ ? kind
55
+ : "preparing";
56
+ return { ...common, activity_ref: active.activity_ref, status: "running", phase };
57
+ }
58
+ const ref = row.workflow_ref;
59
+ if (readPendingTaskCompileSubmission(db, ref) !== null ||
60
+ readActiveTaskCompileContext(db, ref) !== null) {
61
+ return { ...common, status: "running", phase: "preparing" };
62
+ }
63
+ return { ...common, status: "unknown", phase: "unconfirmed" };
64
+ }
65
+ export function readTaskCompileCommandRef(db, commandId) {
66
+ return db
67
+ .prepare("SELECT workflow_ref FROM task_compile_request_commands WHERE command_id = ?")
68
+ .get(commandId)?.workflow_ref;
69
+ }
70
+ //# sourceMappingURL=task-compile-request-status.js.map
@@ -12,10 +12,15 @@ import { enqueueControlPlaneIntent } from "./dispatch-intents.js";
12
12
  import { materializePromptSourceMessages } from "./task-source-context.js";
13
13
  import { materializeRecentReferences, readPendingRecentReferenceSummaryPaths, readRecentReferenceSources, } from "./recent-references.js";
14
14
  import { startReferenceSummaryRefreshProcedure } from "./reference-summary-refresh.js";
15
+ import { linkTaskCompileActivity, closeFailedTaskCompileRequest, } from "../collaboration-state/task-compile-requests.js";
15
16
  export function startControlPlaneTaskCompile(options) {
16
17
  const activeContext = readActiveTaskCompileContext(options.store.db);
17
18
  if (activeContext !== null) {
18
19
  return {
20
+ result: {
21
+ submission_ref: activeContext.workflow_ref,
22
+ activity_ref: activeContext.captured_by_activity_ref,
23
+ },
19
24
  disposition: {
20
25
  kind: "already_running",
21
26
  activity_ref: activeContext.captured_by_activity_ref,
@@ -25,6 +30,10 @@ export function startControlPlaneTaskCompile(options) {
25
30
  const pendingSubmission = readPendingTaskCompileSubmission(options.store.db);
26
31
  if (pendingSubmission !== null) {
27
32
  return {
33
+ result: {
34
+ submission_ref: pendingSubmission.workflow_ref,
35
+ activity_ref: pendingSubmission.accepted_activity_ref,
36
+ },
28
37
  disposition: {
29
38
  kind: "already_running",
30
39
  activity_ref: pendingSubmission.accepted_activity_ref,
@@ -72,7 +81,7 @@ export function startControlPlaneTaskCompile(options) {
72
81
  },
73
82
  };
74
83
  }
75
- return startScratchpadRefreshBeforeTaskCompileActivity({
84
+ const started = startScratchpadRefreshBeforeTaskCompileActivity({
76
85
  ...options,
77
86
  runner: runnerResolution.runner,
78
87
  sourceBatch,
@@ -86,6 +95,7 @@ export function startControlPlaneTaskCompile(options) {
86
95
  now: options.now,
87
96
  }),
88
97
  });
98
+ return withSubmissionRef(started, taskCompileWorkflowRef);
89
99
  }
90
100
  if (!scratchpadHasSubmittableContent(scratchpad)) {
91
101
  return { disposition: { kind: "scratchpad_empty" } };
@@ -108,7 +118,12 @@ export function startControlPlaneTaskCompile(options) {
108
118
  now: options.now,
109
119
  });
110
120
  }
111
- return started;
121
+ return withSubmissionRef(started, taskCompileWorkflowRef);
122
+ }
123
+ function withSubmissionRef(result, submissionRef) {
124
+ return result.disposition.kind === "accepted"
125
+ ? { ...result, result: { ...result.result, submission_ref: submissionRef } }
126
+ : result;
112
127
  }
113
128
  function recordTaskCompileSubmissionIntent(options) {
114
129
  return withHostStoreTransaction(options.store.db, (tx) => {
@@ -152,6 +167,7 @@ function startControlPlaneTaskCompileActivity(options) {
152
167
  summary: "Compiling Scratchpad into Worklist tasks.",
153
168
  now: options.now,
154
169
  onTransition: (state) => {
170
+ linkTaskCompileActivity(options.store.db, options.taskCompileWorkflowRef, state.activity.activity_ref);
155
171
  options.publishProcedureTransition(state);
156
172
  if (state.transition_kind === "terminal") {
157
173
  options.onChainTerminal?.(state.activity.status === "failed"
@@ -206,11 +222,16 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
206
222
  summary: "Refreshing Scratchpad before submitting to Worklist.",
207
223
  now: options.now,
208
224
  onTransition: (state) => {
225
+ linkTaskCompileActivity(options.store.db, options.taskCompileWorkflowRef, state.activity.activity_ref);
209
226
  options.publishProcedureTransition(state);
210
227
  if (state.transition_kind !== "terminal") {
211
228
  return;
212
229
  }
213
230
  if (state.activity.status !== "finished") {
231
+ rollbackTaskCompileContext(options.store.db, {
232
+ workflow_ref: options.taskCompileWorkflowRef,
233
+ now: options.now,
234
+ });
214
235
  options.onChainTerminal?.({
215
236
  state: "failed",
216
237
  errorCode: "scratchpad_refresh_failed",
@@ -222,6 +243,10 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
222
243
  }
223
244
  const scratchpad = readScratchpadProjection(options.store.db);
224
245
  if (!scratchpadHasSubmittableContent(scratchpad)) {
246
+ withHostStoreTransaction(options.store.db, (tx) => {
247
+ closeFailedTaskCompileRequest(tx, options.taskCompileWorkflowRef, options.now().toISOString(), "scratchpad_empty_after_refresh");
248
+ tx.prepare("DELETE FROM pending_task_compile_submissions WHERE workflow_ref = ?").run(options.taskCompileWorkflowRef);
249
+ });
225
250
  options.onChainTerminal?.({
226
251
  state: "failed",
227
252
  errorCode: "scratchpad_empty_after_refresh",
@@ -230,6 +255,10 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
230
255
  }
231
256
  const next = startReferenceSummaryGateOrNextStage(options);
232
257
  if (next.disposition.kind !== "accepted" && next.disposition.kind !== "already_running") {
258
+ rollbackTaskCompileContext(options.store.db, {
259
+ workflow_ref: options.taskCompileWorkflowRef,
260
+ now: options.now,
261
+ });
233
262
  options.onChainTerminal?.({
234
263
  state: "failed",
235
264
  errorCode: "task_compile_stage_unavailable",
@@ -413,6 +442,10 @@ function startReferenceSummaryGateOrNextStage(options) {
413
442
  });
414
443
  registerReferenceSummaryGateWaiter(options, activeReferenceSummary.activity_ref);
415
444
  return {
445
+ result: {
446
+ submission_ref: options.taskCompileWorkflowRef,
447
+ activity_ref: activeReferenceSummary.activity_ref,
448
+ },
416
449
  disposition: {
417
450
  kind: "already_running",
418
451
  activity_ref: activeReferenceSummary.activity_ref,
@@ -514,6 +547,10 @@ function startProjectContextBootstrapActivity(options) {
514
547
  return;
515
548
  }
516
549
  if (state.activity.status !== "finished") {
550
+ rollbackTaskCompileContext(options.store.db, {
551
+ workflow_ref: options.taskCompileWorkflowRef,
552
+ now: options.now,
553
+ });
517
554
  options.onChainTerminal?.({ state: "failed", errorCode: "project_context_failed" });
518
555
  return;
519
556
  }
@@ -697,6 +734,7 @@ function readOrCaptureTaskCompileContext(input) {
697
734
  captured_by_activity_ref: input.activityRef,
698
735
  now: input.now,
699
736
  });
737
+ linkTaskCompileActivity(tx, input.workflowRef, input.activityRef);
700
738
  enqueueControlPlaneIntent(tx, {
701
739
  kind: "task_compile_start",
702
740
  ownerKind: "task_compile",