@xfey/tutti 0.1.95 → 0.1.96

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.
@@ -48,7 +48,9 @@ function failedResultCanRetry(row, result) {
48
48
  }
49
49
  export function materializeTaskRetryRun(db, input) {
50
50
  return withHostStoreTransaction(db, (tx) => {
51
- const taskRow = tx.prepare("SELECT * FROM tasks WHERE id = ?").get(input.task_id);
51
+ const taskRow = tx
52
+ .prepare("SELECT * FROM tasks WHERE id = ?")
53
+ .get(input.task_id);
52
54
  if (taskRow === undefined) {
53
55
  return { disposition: { kind: "task_not_failed" } };
54
56
  }
@@ -18,6 +18,7 @@ export declare class Phase5ControlPlane {
18
18
  private readonly runLifecycle;
19
19
  private readonly now;
20
20
  private readonly scratchpadAutoRefreshScheduler;
21
+ private pendingWorklistCompletionContextSyncTimer;
21
22
  private readonly pendingReferenceSummaryPaths;
22
23
  private readonly referenceSummaryWaiters;
23
24
  private pendingAllReferenceSummaries;
@@ -67,7 +68,9 @@ export declare class Phase5ControlPlane {
67
68
  private tryRunTaskArrivalScheduler;
68
69
  private maybeScheduleAfterRunCompletion;
69
70
  private worklistCompletionContextSyncTrigger;
70
- private maybeStartContextSyncAfterWorklistCompletion;
71
+ private scheduleContextSyncAfterWorklistCompletion;
72
+ private attemptContextSyncAfterWorklistCompletion;
73
+ private cancelPendingWorklistCompletionContextSync;
71
74
  private startTaskCompileContinuation;
72
75
  private startTaskBoundFollowUpCheck;
73
76
  private publishProcedureTransition;
@@ -1,7 +1,7 @@
1
- import { readActiveTaskCompileContext, readWorklistProjection, } from "../collaboration-state/index.js";
1
+ import { readActiveTaskCompileContext, readScratchpadSourceState, readWorklistProjection, } from "../collaboration-state/index.js";
2
2
  import { ProcedureEngine } from "../procedure-engine/index.js";
3
3
  import { RunEngine } from "../run-engine/index.js";
4
- import { WorkspaceOpsError, readReferenceSummaryTargets, } from "../workspace-ops/index.js";
4
+ import { WorkspaceOpsError, 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
7
  import { refreshProjectBriefProjection, } from "./project-brief-refresh.js";
@@ -19,6 +19,7 @@ import { startControlPlaneRunForTask } from "./run-start.js";
19
19
  import { retryControlPlaneTask } from "./run-retry.js";
20
20
  import { runSendClarificationRoundMessage, runSubmitClarificationRound, } from "./clarification-commands.js";
21
21
  import { emitTaskCompletedResultMessage, emitTaskFailedResultMessage, } from "./worklist-terminal-feedback.js";
22
+ const WORKLIST_COMPLETION_CONTEXT_SYNC_GRACE_MS = 30_000;
22
23
  export class Phase5ControlPlane {
23
24
  engine;
24
25
  runEngine;
@@ -33,6 +34,7 @@ export class Phase5ControlPlane {
33
34
  runLifecycle;
34
35
  now;
35
36
  scratchpadAutoRefreshScheduler;
37
+ pendingWorklistCompletionContextSyncTimer = null;
36
38
  pendingReferenceSummaryPaths = new Set();
37
39
  referenceSummaryWaiters = new Map();
38
40
  pendingAllReferenceSummaries = false;
@@ -112,6 +114,7 @@ export class Phase5ControlPlane {
112
114
  });
113
115
  }
114
116
  startScratchpadRefresh(options = {}) {
117
+ this.cancelPendingWorklistCompletionContextSync();
115
118
  return startControlPlaneScratchpadRefresh({
116
119
  engine: this.engine,
117
120
  store: this.store,
@@ -128,6 +131,7 @@ export class Phase5ControlPlane {
128
131
  if (!shouldUseMessageAsScratchpadSource(message)) {
129
132
  return;
130
133
  }
134
+ this.cancelPendingWorklistCompletionContextSync();
131
135
  this.scratchpadAutoRefreshScheduler.notifySourceChanged();
132
136
  }
133
137
  refreshProjectBrief(options) {
@@ -148,6 +152,7 @@ export class Phase5ControlPlane {
148
152
  }
149
153
  this.closing = true;
150
154
  this.scratchpadAutoRefreshScheduler.dispose();
155
+ this.cancelPendingWorklistCompletionContextSync();
151
156
  this.pendingAllReferenceSummaries = false;
152
157
  this.pendingReferenceSummaryPaths.clear();
153
158
  }
@@ -171,6 +176,7 @@ export class Phase5ControlPlane {
171
176
  }
172
177
  }
173
178
  startTaskCompile(payload) {
179
+ this.cancelPendingWorklistCompletionContextSync();
174
180
  const activeRun = this.readActiveRun();
175
181
  if (activeRun !== null) {
176
182
  return {
@@ -211,6 +217,9 @@ export class Phase5ControlPlane {
211
217
  });
212
218
  }
213
219
  startContextSyncAnalysis(trigger = { kind: "manual" }) {
220
+ if (trigger.kind === "manual") {
221
+ this.cancelPendingWorklistCompletionContextSync();
222
+ }
214
223
  return startContextSyncAnalysisProcedure({
215
224
  engine: this.engine,
216
225
  store: this.store,
@@ -366,6 +375,7 @@ export class Phase5ControlPlane {
366
375
  publishRunResultRecorded: (recorded) => this.publishRunResultRecorded(recorded),
367
376
  });
368
377
  if (result.kind === "accepted") {
378
+ this.cancelPendingWorklistCompletionContextSync();
369
379
  this.runLifecycle?.started();
370
380
  }
371
381
  return result;
@@ -497,7 +507,7 @@ export class Phase5ControlPlane {
497
507
  if (this.tryRunTaskArrivalScheduler()) {
498
508
  return;
499
509
  }
500
- this.maybeStartContextSyncAfterWorklistCompletion();
510
+ this.scheduleContextSyncAfterWorklistCompletion();
501
511
  }
502
512
  worklistCompletionContextSyncTrigger() {
503
513
  const worklist = readWorklistProjection(this.store.db);
@@ -511,16 +521,46 @@ export class Phase5ControlPlane {
511
521
  module_count: worklist.modules.length,
512
522
  };
513
523
  }
514
- maybeStartContextSyncAfterWorklistCompletion() {
515
- if (this.closing) {
524
+ scheduleContextSyncAfterWorklistCompletion() {
525
+ if (this.closing ||
526
+ !this.store.db.open ||
527
+ this.pendingWorklistCompletionContextSyncTimer !== null) {
528
+ return;
529
+ }
530
+ const sourceState = readScratchpadSourceState(this.store.db);
531
+ if (this.worklistCompletionContextSyncTrigger() === undefined ||
532
+ sourceState.dirty_since !== undefined ||
533
+ sourceState.in_flight !== undefined ||
534
+ this.readSchedulerBlocker() !== null) {
535
+ return;
536
+ }
537
+ this.pendingWorklistCompletionContextSyncTimer = setTimeout(() => {
538
+ this.attemptContextSyncAfterWorklistCompletion();
539
+ }, WORKLIST_COMPLETION_CONTEXT_SYNC_GRACE_MS);
540
+ this.pendingWorklistCompletionContextSyncTimer.unref?.();
541
+ }
542
+ attemptContextSyncAfterWorklistCompletion() {
543
+ this.pendingWorklistCompletionContextSyncTimer = null;
544
+ if (this.closing || !this.store.db.open) {
516
545
  return;
517
546
  }
518
547
  const trigger = this.worklistCompletionContextSyncTrigger();
519
- if (trigger === undefined || this.readSchedulerBlocker() !== null) {
548
+ const sourceState = readScratchpadSourceState(this.store.db);
549
+ if (trigger === undefined ||
550
+ sourceState.dirty_since !== undefined ||
551
+ sourceState.in_flight !== undefined ||
552
+ this.readSchedulerBlocker() !== null) {
520
553
  return;
521
554
  }
522
555
  void this.startContextSyncAnalysis(trigger);
523
556
  }
557
+ cancelPendingWorklistCompletionContextSync() {
558
+ if (this.pendingWorklistCompletionContextSyncTimer === null) {
559
+ return;
560
+ }
561
+ clearTimeout(this.pendingWorklistCompletionContextSyncTimer);
562
+ this.pendingWorklistCompletionContextSyncTimer = null;
563
+ }
524
564
  startTaskCompileContinuation(owner, roundId) {
525
565
  let tasksAdded = false;
526
566
  return startControlPlaneTaskCompileContinuation({
@@ -570,6 +610,9 @@ export class Phase5ControlPlane {
570
610
  });
571
611
  }
572
612
  publishProcedureTransition(state) {
613
+ if (state.transition_kind === "started" && state.activity.lane === "foreground") {
614
+ this.cancelPendingWorklistCompletionContextSync();
615
+ }
573
616
  this.logProcedureTransition(state);
574
617
  publishProcedureTransitionEvent({
575
618
  events: this.events,
@@ -596,6 +639,11 @@ export class Phase5ControlPlane {
596
639
  }
597
640
  if (!this.closing) {
598
641
  this.schedulePendingReferenceSummaryRefresh();
642
+ if (state.activity.status === "finished" &&
643
+ (state.activity.workflow_kind === "scratchpad_refresh" ||
644
+ state.activity.workflow_kind === "reference_summary_refresh")) {
645
+ this.scheduleContextSyncAfterWorklistCompletion();
646
+ }
599
647
  }
600
648
  }
601
649
  }
@@ -1,5 +1,5 @@
1
- import { createMainChatAgentMessage, markScratchpadRefreshSucceeded, markScratchpadSourceDirty, upsertScratchpadProjection, } from "../collaboration-state/index.js";
2
- import { mainChatInvalidates, scratchpadRefreshInvalidates, } from "./invalidations.js";
1
+ import { createMainChatAgentMessage, markScratchpadRefreshSucceeded, upsertScratchpadProjection, } from "../collaboration-state/index.js";
2
+ import { mainChatInvalidates, scratchpadRefreshInvalidates } from "./invalidations.js";
3
3
  export function applyScratchpadRefreshOutput(input) {
4
4
  if (input.output.decision === "needs_human") {
5
5
  const message = createMainChatAgentMessage(input.store.db, {
@@ -7,7 +7,7 @@ export function applyScratchpadRefreshOutput(input) {
7
7
  refs: {
8
8
  workflow_ref: input.workflowRef,
9
9
  activity_ref: input.activityRef,
10
- scratchpad_source: "include",
10
+ scratchpad_source: "exclude",
11
11
  },
12
12
  now: input.now,
13
13
  });
@@ -20,7 +20,6 @@ export function applyScratchpadRefreshOutput(input) {
20
20
  ...input.cursorUpdate,
21
21
  now: input.now,
22
22
  });
23
- markScratchpadSourceDirty(input.store.db, { now: input.now });
24
23
  return {
25
24
  kind: "needs_human",
26
25
  summary: input.output.request_payload.summary,
@@ -291,11 +291,9 @@ export function acquireCodexTaskSessionLock(session) {
291
291
  throw error;
292
292
  }
293
293
  const lockValue = readJson(lockPath);
294
- const ownerPid = isRecord(lockValue) && typeof lockValue.owner_pid === "number"
295
- ? lockValue.owner_pid
296
- : null;
294
+ const ownerPid = isRecord(lockValue) && typeof lockValue.owner_pid === "number" ? lockValue.owner_pid : null;
297
295
  if (ownerPid !== null && isProcessAlive(ownerPid)) {
298
- throw new Error("Codex task session already has an active turn owner.");
296
+ throw new Error("Codex task session already has an active turn owner.", { cause: error });
299
297
  }
300
298
  unlinkSync(lockPath);
301
299
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfey/tutti",
3
- "version": "0.1.95",
3
+ "version": "0.1.96",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",