@voybio/ace-swarm 0.2.4 → 2.4.0

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 (125) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/README.md +20 -13
  3. package/assets/.agents/skills/eval-harness/SKILL.md +14 -0
  4. package/assets/.agents/skills/handoff-lint/SKILL.md +14 -0
  5. package/assets/.agents/skills/incident-commander/SKILL.md +14 -0
  6. package/assets/.agents/skills/memory-curator/SKILL.md +14 -0
  7. package/assets/.agents/skills/release-sentry/SKILL.md +14 -0
  8. package/assets/.agents/skills/risk-quant/SKILL.md +14 -0
  9. package/assets/.agents/skills/schema-forge/SKILL.md +14 -0
  10. package/assets/.agents/skills/state-auditor/SKILL.md +14 -0
  11. package/assets/agent-state/EVIDENCE_LOG.md +1 -1
  12. package/assets/agent-state/MODULES/gates/gate-correctness.json +1 -1
  13. package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
  14. package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
  15. package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
  16. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
  17. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
  18. package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
  19. package/assets/agent-state/STATUS.md +2 -2
  20. package/assets/scripts/ace-hook-dispatch.mjs +70 -6
  21. package/assets/scripts/render-mcp-configs.sh +19 -5
  22. package/dist/ace-context.js +22 -1
  23. package/dist/ace-server-instructions.js +3 -3
  24. package/dist/ace-state-resolver.js +5 -3
  25. package/dist/astgrep-index.d.ts +9 -1
  26. package/dist/astgrep-index.js +14 -3
  27. package/dist/cli.js +52 -20
  28. package/dist/handoff-registry.js +5 -5
  29. package/dist/helpers/artifacts.d.ts +19 -0
  30. package/dist/helpers/artifacts.js +152 -0
  31. package/dist/helpers/bootstrap.d.ts +24 -0
  32. package/dist/helpers/bootstrap.js +894 -0
  33. package/dist/helpers/constants.d.ts +53 -0
  34. package/dist/helpers/constants.js +288 -0
  35. package/dist/helpers/drift.d.ts +13 -0
  36. package/dist/helpers/drift.js +45 -0
  37. package/dist/helpers/path-utils.d.ts +17 -0
  38. package/dist/helpers/path-utils.js +104 -0
  39. package/dist/helpers/store-resolution.d.ts +19 -0
  40. package/dist/helpers/store-resolution.js +301 -0
  41. package/dist/helpers/workspace-root.d.ts +3 -0
  42. package/dist/helpers/workspace-root.js +80 -0
  43. package/dist/helpers.d.ts +8 -123
  44. package/dist/helpers.js +8 -1747
  45. package/dist/job-scheduler.js +3 -3
  46. package/dist/local-model-runtime.js +12 -1
  47. package/dist/model-bridge.d.ts +7 -0
  48. package/dist/model-bridge.js +75 -5
  49. package/dist/orchestrator-supervisor.d.ts +14 -0
  50. package/dist/orchestrator-supervisor.js +72 -1
  51. package/dist/run-ledger.js +3 -3
  52. package/dist/runtime-command.d.ts +8 -0
  53. package/dist/runtime-command.js +38 -6
  54. package/dist/runtime-executor.d.ts +14 -0
  55. package/dist/runtime-executor.js +669 -171
  56. package/dist/runtime-profile.d.ts +32 -0
  57. package/dist/runtime-profile.js +89 -13
  58. package/dist/runtime-tool-specs.d.ts +21 -0
  59. package/dist/runtime-tool-specs.js +78 -3
  60. package/dist/safe-edit.d.ts +7 -0
  61. package/dist/safe-edit.js +163 -37
  62. package/dist/schemas.js +19 -0
  63. package/dist/shared.d.ts +2 -2
  64. package/dist/status-events.js +9 -6
  65. package/dist/store/ace-packed-store.d.ts +3 -2
  66. package/dist/store/ace-packed-store.js +188 -110
  67. package/dist/store/bootstrap-store.d.ts +1 -1
  68. package/dist/store/bootstrap-store.js +94 -81
  69. package/dist/store/cache-workspace.d.ts +22 -0
  70. package/dist/store/cache-workspace.js +149 -0
  71. package/dist/store/materializers/context-snapshot-materializer.js +6 -7
  72. package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
  73. package/dist/store/materializers/hook-context-materializer.js +11 -21
  74. package/dist/store/materializers/host-file-materializer.js +6 -0
  75. package/dist/store/materializers/projection-manager.d.ts +0 -1
  76. package/dist/store/materializers/projection-manager.js +5 -13
  77. package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
  78. package/dist/store/materializers/vericify-projector.d.ts +7 -7
  79. package/dist/store/materializers/vericify-projector.js +11 -11
  80. package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
  81. package/dist/store/repositories/local-model-runtime-repository.js +242 -6
  82. package/dist/store/skills-install.d.ts +4 -0
  83. package/dist/store/skills-install.js +21 -12
  84. package/dist/store/state-reader.d.ts +2 -0
  85. package/dist/store/state-reader.js +20 -0
  86. package/dist/store/store-artifacts.d.ts +7 -0
  87. package/dist/store/store-artifacts.js +27 -1
  88. package/dist/store/store-authority-audit.d.ts +18 -1
  89. package/dist/store/store-authority-audit.js +115 -5
  90. package/dist/store/store-snapshot.d.ts +3 -0
  91. package/dist/store/store-snapshot.js +22 -2
  92. package/dist/store/workspace-store-paths.d.ts +39 -0
  93. package/dist/store/workspace-store-paths.js +94 -0
  94. package/dist/store/write-coordinator.d.ts +65 -0
  95. package/dist/store/write-coordinator.js +386 -0
  96. package/dist/todo-state.js +5 -5
  97. package/dist/tools-agent.js +319 -34
  98. package/dist/tools-discovery.js +1 -1
  99. package/dist/tools-files.d.ts +7 -0
  100. package/dist/tools-files.js +299 -10
  101. package/dist/tools-framework.js +107 -27
  102. package/dist/tools-handoff.js +2 -2
  103. package/dist/tools-lifecycle.js +4 -4
  104. package/dist/tools-memory.js +6 -6
  105. package/dist/tools-todo.js +2 -2
  106. package/dist/tracker-adapters.d.ts +1 -1
  107. package/dist/tracker-adapters.js +13 -18
  108. package/dist/tracker-sync.js +5 -3
  109. package/dist/tui/agent-runner.js +3 -1
  110. package/dist/tui/chat.js +103 -7
  111. package/dist/tui/dashboard.d.ts +1 -0
  112. package/dist/tui/dashboard.js +43 -0
  113. package/dist/tui/layout.d.ts +20 -0
  114. package/dist/tui/layout.js +31 -1
  115. package/dist/tui/local-model-contract.d.ts +6 -2
  116. package/dist/tui/local-model-contract.js +16 -3
  117. package/dist/vericify-bridge.d.ts +5 -0
  118. package/dist/vericify-bridge.js +27 -3
  119. package/dist/workspace-manager.d.ts +30 -3
  120. package/dist/workspace-manager.js +257 -27
  121. package/package.json +1 -2
  122. package/dist/internal-tool-runtime.d.ts +0 -21
  123. package/dist/internal-tool-runtime.js +0 -136
  124. package/dist/store/workspace-snapshot.d.ts +0 -26
  125. package/dist/store/workspace-snapshot.js +0 -107
@@ -9,7 +9,7 @@ import { openStore } from "./store/ace-packed-store.js";
9
9
  import { ProjectionManager } from "./store/materializers/projection-manager.js";
10
10
  import { SchedulerRepository } from "./store/repositories/scheduler-repository.js";
11
11
  import { getWorkspaceStorePath, readStoreJsonSync, storeExistsSync, } from "./store/store-snapshot.js";
12
- import { withStoreWriteQueue } from "./store/write-queue.js";
12
+ import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
13
13
  // ── Buffered transition-event infrastructure ────────────────────────
14
14
  // Events are collected during the scheduler lock and flushed after
15
15
  // the lock is released. Emission is failure-tolerant: a broken
@@ -319,7 +319,7 @@ async function persistSchedulerState(input) {
319
319
  return;
320
320
  }
321
321
  const storePath = getWorkspaceStorePath(root);
322
- await withStoreWriteQueue(storePath, async () => {
322
+ await withStoreWriteCoordinator(storePath, async () => {
323
323
  const store = await openStore(storePath);
324
324
  try {
325
325
  const repo = new SchedulerRepository(store);
@@ -331,7 +331,7 @@ async function persistSchedulerState(input) {
331
331
  finally {
332
332
  await store.close();
333
333
  }
334
- });
334
+ }, { operation_label: "persistSchedulerState" });
335
335
  }
336
336
  function compareJobs(a, b) {
337
337
  const priorityCmp = PRIORITY_WEIGHT[a.priority] - PRIORITY_WEIGHT[b.priority];
@@ -18,6 +18,14 @@ function extractTextContent(result) {
18
18
  .filter(Boolean)
19
19
  .join("\n");
20
20
  }
21
+ function extractRoutedRole(result) {
22
+ const structured = result?.structuredContent;
23
+ if (!structured || typeof structured !== "object")
24
+ return undefined;
25
+ const suggestedRole = structured
26
+ .suggested_execution_role;
27
+ return typeof suggestedRole === "string" ? normalizeRoleCandidate(suggestedRole) : undefined;
28
+ }
21
29
  function normalizeRoleCandidate(input) {
22
30
  if (!input)
23
31
  return undefined;
@@ -37,7 +45,10 @@ async function resolveRole(task, sessionId, requestedRole) {
37
45
  }
38
46
  const routing = await executeAceInternalTool("route_task", { description: task, domain: "unknown" }, sessionId);
39
47
  const routingSummary = extractTextContent(routing);
40
- return { role: fallbackRoleForTask(), routingSummary };
48
+ return {
49
+ role: extractRoutedRole(routing) ?? fallbackRoleForTask(),
50
+ routingSummary,
51
+ };
41
52
  }
42
53
  function resolveTier(requested, provider, model, role) {
43
54
  if (requested && requested !== "auto")
@@ -11,6 +11,11 @@ export interface BridgeToolResult {
11
11
  summary: string;
12
12
  isError: boolean;
13
13
  }
14
+ export interface BridgeProgressEvent {
15
+ kind: "model_chunk" | "tool_start" | "tool_finish" | "process_post" | "output" | "thinking";
16
+ at: number;
17
+ detail?: Record<string, unknown>;
18
+ }
14
19
  export interface BridgeResult {
15
20
  bridge_id: string;
16
21
  role: string;
@@ -19,6 +24,7 @@ export interface BridgeResult {
19
24
  turns: number;
20
25
  tool_calls: BridgeToolResult[];
21
26
  child_results: BridgeResult[];
27
+ evidence_refs?: string[];
22
28
  }
23
29
  export interface ModelBridgeClients {
24
30
  ollama: Pick<OllamaClient, "chat" | "abort">;
@@ -39,6 +45,7 @@ export interface ModelBridgeRunOptions {
39
45
  onToolResult?: (tool: string, result: BridgeToolResult) => void;
40
46
  onOutput?: (text: string) => void;
41
47
  onThinking?: (text: string) => void;
48
+ onProgress?: (event: BridgeProgressEvent) => void;
42
49
  }
43
50
  export declare class ModelBridge {
44
51
  private clients;
@@ -68,8 +68,8 @@ function parseEnvelope(raw) {
68
68
  }
69
69
  catch {
70
70
  return {
71
- status: "message",
72
- message: raw.trim(),
71
+ status: "parse_error",
72
+ message: summarizeSnippet(raw.trim() || "[empty response]", 240),
73
73
  };
74
74
  }
75
75
  }
@@ -232,13 +232,21 @@ async function collectOllamaResponse(client, model, messages, options) {
232
232
  num_ctx: options?.num_ctx ?? 8192,
233
233
  },
234
234
  })) {
235
- combined += chunk.message?.content ?? "";
235
+ const text = chunk.message?.content ?? "";
236
+ combined += text;
237
+ if (text || chunk.done) {
238
+ options?.onProgress?.({
239
+ kind: "model_chunk",
240
+ at: Date.now(),
241
+ detail: { provider: "ollama", done: Boolean(chunk.done), bytes: Buffer.byteLength(text) },
242
+ });
243
+ }
236
244
  if (chunk.done)
237
245
  break;
238
246
  }
239
247
  return combined;
240
248
  }
241
- async function collectOpenAiCompatibleResponse(client, provider, model, messages) {
249
+ async function collectOpenAiCompatibleResponse(client, provider, model, messages, onProgress) {
242
250
  let combined = "";
243
251
  for await (const chunk of client.chat({
244
252
  provider,
@@ -248,6 +256,17 @@ async function collectOpenAiCompatibleResponse(client, provider, model, messages
248
256
  topP: 0.9,
249
257
  })) {
250
258
  combined += chunk.text;
259
+ if (chunk.text || chunk.done) {
260
+ onProgress?.({
261
+ kind: "model_chunk",
262
+ at: Date.now(),
263
+ detail: {
264
+ provider,
265
+ done: Boolean(chunk.done),
266
+ bytes: Buffer.byteLength(chunk.text),
267
+ },
268
+ });
269
+ }
251
270
  if (chunk.done)
252
271
  break;
253
272
  }
@@ -257,8 +276,9 @@ async function collectProviderResponse(clients, provider, options) {
257
276
  return provider.client === "ollama"
258
277
  ? collectOllamaResponse(clients.ollama, options.model, options.messages, {
259
278
  num_ctx: options.numCtx,
279
+ onProgress: options.onProgress,
260
280
  })
261
- : collectOpenAiCompatibleResponse(clients.openai, provider.display, options.model, options.messages);
281
+ : collectOpenAiCompatibleResponse(clients.openai, provider.display, options.model, options.messages, options.onProgress);
262
282
  }
263
283
  async function collectProviderResponseWithRetry(clients, provider, options, onThinking) {
264
284
  try {
@@ -269,6 +289,11 @@ async function collectProviderResponseWithRetry(clients, provider, options, onTh
269
289
  throw error;
270
290
  }
271
291
  onThinking?.(`Provider error, retrying once: ${formatErrorMessage(error)}`);
292
+ options.onProgress?.({
293
+ kind: "thinking",
294
+ at: Date.now(),
295
+ detail: { reason: "provider_retry" },
296
+ });
272
297
  return collectProviderResponse(clients, provider, options);
273
298
  }
274
299
  }
@@ -404,6 +429,9 @@ export class ModelBridge {
404
429
  const availableTools = new Set(listAceInternalToolCatalog().map((tool) => tool.name));
405
430
  const allowedTools = new Set(aceContext.tools.map((tool) => tool.name));
406
431
  this.currentRunChildResults = childResults;
432
+ const noteProgress = (kind, detail) => {
433
+ options.onProgress?.({ kind, at: Date.now(), detail });
434
+ };
407
435
  try {
408
436
  await appendVericifyProcessPostSafe({
409
437
  run_id: refs.run_id,
@@ -414,24 +442,52 @@ export class ModelBridge {
414
442
  summary: `Bridge started for ${role} via ${provider.display}: ${options.task}`,
415
443
  tool_refs: aceContext.tools.map((tool) => tool.name),
416
444
  });
445
+ noteProgress("process_post", { kind: "intent" });
417
446
  for (let turn = 1; turn <= options.maxTurns; turn += 1) {
418
447
  const compressed = compressConversationHistory(messages, numCtx);
419
448
  if (compressed.compressed) {
420
449
  messages.splice(0, messages.length, ...compressed.messages);
421
450
  options.onThinking?.(`Context window compressed to ~${compressed.promptTokens} tokens.`);
451
+ noteProgress("thinking", { reason: "context_compressed", turn });
422
452
  }
423
453
  this.activeProviderClient = provider.client;
424
454
  const rawResponse = await collectProviderResponseWithRetry(this.clients, provider, {
425
455
  model: options.model,
426
456
  messages,
427
457
  numCtx,
458
+ onProgress: options.onProgress,
428
459
  }, options.onThinking);
429
460
  this.activeProviderClient = null;
430
461
  const envelope = parseEnvelope(rawResponse);
431
462
  if (envelope.thinking) {
432
463
  options.onThinking?.(envelope.thinking);
464
+ noteProgress("thinking", { turn });
433
465
  }
434
466
  messages.push({ role: "assistant", content: rawResponse });
467
+ if (envelope.status === "parse_error") {
468
+ const summary = `Model bridge returned malformed or non-JSON output: ${envelope.message ?? "[empty response]"}`;
469
+ options.onOutput?.(summary);
470
+ noteProgress("output", { status: "parse_error" });
471
+ await appendVericifyProcessPostSafe({
472
+ run_id: refs.run_id,
473
+ branch_id: refs.branch_id,
474
+ lane_id: refs.lane_id,
475
+ agent_id: `agent-${role}`,
476
+ kind: "blocker",
477
+ summary,
478
+ tool_refs: toolResults.map((entry) => entry.tool),
479
+ });
480
+ noteProgress("process_post", { kind: "blocker" });
481
+ return {
482
+ bridge_id: this.bridgeId,
483
+ role,
484
+ status: "failed",
485
+ summary,
486
+ turns: turn,
487
+ tool_calls: toolResults,
488
+ child_results: childResults,
489
+ };
490
+ }
435
491
  if (envelope.status === "tool" &&
436
492
  Array.isArray(envelope.tool_calls) &&
437
493
  envelope.tool_calls.length > 0) {
@@ -460,6 +516,11 @@ export class ModelBridge {
460
516
  : `Tool '${toolCall.tool}' is not available in the active ACE catalog.`,
461
517
  };
462
518
  options.onToolResult?.(toolCall.tool, result);
519
+ noteProgress("tool_finish", {
520
+ tool: toolCall.tool,
521
+ ok: false,
522
+ blocked: true,
523
+ });
463
524
  toolResults.push(result);
464
525
  return result;
465
526
  });
@@ -472,6 +533,7 @@ export class ModelBridge {
472
533
  const executed = await Promise.all(envelope.tool_calls.map(async (toolCall) => {
473
534
  const args = toolCall.input ?? {};
474
535
  options.onToolCall?.(toolCall.tool, args);
536
+ noteProgress("tool_start", { tool: toolCall.tool });
475
537
  const rawToolResult = await executeAceInternalTool(toolCall.tool, args, sessionId);
476
538
  const result = truncateToolResult({
477
539
  tool: toolCall.tool,
@@ -480,6 +542,7 @@ export class ModelBridge {
480
542
  summary: summarizeToolText(rawToolResult),
481
543
  }, options.workspace);
482
544
  options.onToolResult?.(toolCall.tool, result);
545
+ noteProgress("tool_finish", { tool: toolCall.tool, ok: result.ok });
483
546
  toolResults.push(result);
484
547
  return result;
485
548
  }));
@@ -494,6 +557,7 @@ export class ModelBridge {
494
557
  if (envelope.status === "message") {
495
558
  const message = envelope.message?.trim() || rawResponse.trim();
496
559
  options.onOutput?.(message);
560
+ noteProgress("output", { status: "message" });
497
561
  await appendVericifyProcessPostSafe({
498
562
  run_id: refs.run_id,
499
563
  branch_id: refs.branch_id,
@@ -503,6 +567,7 @@ export class ModelBridge {
503
567
  summary: message,
504
568
  tool_refs: [],
505
569
  });
570
+ noteProgress("process_post", { kind: "progress" });
506
571
  return {
507
572
  bridge_id: this.bridgeId,
508
573
  role,
@@ -516,6 +581,7 @@ export class ModelBridge {
516
581
  if (envelope.status === "need_input") {
517
582
  const message = envelope.message?.trim() || "Additional operator input required.";
518
583
  options.onOutput?.(message);
584
+ noteProgress("output", { status: "need_input" });
519
585
  await appendVericifyProcessPostSafe({
520
586
  run_id: refs.run_id,
521
587
  branch_id: refs.branch_id,
@@ -525,6 +591,7 @@ export class ModelBridge {
525
591
  summary: message,
526
592
  tool_refs: [],
527
593
  });
594
+ noteProgress("process_post", { kind: "blocker" });
528
595
  return {
529
596
  bridge_id: this.bridgeId,
530
597
  role,
@@ -538,6 +605,7 @@ export class ModelBridge {
538
605
  if (envelope.status === "complete") {
539
606
  const summary = envelope.summary?.trim() || "Bridge completed.";
540
607
  options.onOutput?.(summary);
608
+ noteProgress("output", { status: "complete" });
541
609
  await appendVericifyProcessPostSafe({
542
610
  run_id: refs.run_id,
543
611
  branch_id: refs.branch_id,
@@ -547,6 +615,7 @@ export class ModelBridge {
547
615
  summary,
548
616
  tool_refs: toolResults.map((entry) => entry.tool),
549
617
  });
618
+ noteProgress("process_post", { kind: "completion" });
550
619
  return {
551
620
  bridge_id: this.bridgeId,
552
621
  role,
@@ -568,6 +637,7 @@ export class ModelBridge {
568
637
  summary,
569
638
  tool_refs: toolResults.map((entry) => entry.tool),
570
639
  });
640
+ noteProgress("process_post", { kind: "blocker" });
571
641
  return {
572
642
  bridge_id: this.bridgeId,
573
643
  role,
@@ -1,4 +1,5 @@
1
1
  import type { BridgeResult } from "./model-bridge.js";
2
+ import type { LocalModelRuntimeRepository } from "./store/repositories/local-model-runtime-repository.js";
2
3
  export type TaskStepStatus = "planned" | "running" | "done" | "failed" | "skipped" | "blocked";
3
4
  export type TaskPlanStatus = "planning" | "executing" | "reviewing" | "blocked" | "done" | "failed";
4
5
  export interface TaskStep {
@@ -14,6 +15,11 @@ export interface TaskStep {
14
15
  job_id?: string;
15
16
  handoff_id?: string;
16
17
  blocked_reason?: string;
18
+ upstream_outputs?: {
19
+ step_id: string;
20
+ result_summary: string;
21
+ evidence_refs: string[];
22
+ }[];
17
23
  }
18
24
  export interface TaskPlan {
19
25
  plan_id: string;
@@ -36,6 +42,7 @@ export interface TaskPlanAmendment {
36
42
  reorder_step_ids?: string[];
37
43
  execution_mode?: TaskPlan["execution_mode"];
38
44
  vcx_cursor?: string;
45
+ evidence_refs?: string[];
39
46
  }
40
47
  export interface SupervisorHooks {
41
48
  spawnStep: (step: TaskStep, plan: TaskPlan) => Promise<BridgeResult>;
@@ -92,6 +99,13 @@ export declare function getReadyTaskSteps(plan: TaskPlan): TaskStep[];
92
99
  export declare function markTaskStepStatus(plan: TaskPlan, stepId: string, status: TaskStepStatus, patch?: Partial<Omit<TaskStep, "step_id" | "status">>): TaskPlan;
93
100
  export declare function applyBridgeResultToStep(plan: TaskPlan, stepId: string, result: BridgeResult): TaskPlan;
94
101
  export declare function amendTaskPlan(plan: TaskPlan, amendment: TaskPlanAmendment): TaskPlan;
102
+ export declare function collectUpstreamOutputs(plan: TaskPlan, step: TaskStep): {
103
+ step_id: string;
104
+ result_summary: string;
105
+ evidence_refs: string[];
106
+ }[];
107
+ export declare function applyBridgeResultToStepWithTransition(plan: TaskPlan, stepId: string, result: BridgeResult, repo?: LocalModelRuntimeRepository): Promise<TaskPlan>;
108
+ export declare function amendTaskPlanWithTransition(plan: TaskPlan, amendment: TaskPlanAmendment, repo?: LocalModelRuntimeRepository, reason?: string, reason_code?: string): Promise<TaskPlan>;
95
109
  export declare function promoteParallelGroupToScheduler(plan: TaskPlan, parallelGroup: string, owner?: string): Promise<{
96
110
  plan: TaskPlan;
97
111
  job_ids: string[];
@@ -212,6 +212,56 @@ export function amendTaskPlan(plan, amendment) {
212
212
  vcx_cursor: amendment.vcx_cursor ?? plan.vcx_cursor,
213
213
  });
214
214
  }
215
+ export function collectUpstreamOutputs(plan, step) {
216
+ if (!step.depends_on || step.depends_on.length === 0)
217
+ return [];
218
+ return step.depends_on
219
+ .map((depId) => plan.steps.find((s) => s.step_id === depId))
220
+ .filter((dep) => dep?.status === "done")
221
+ .map((dep) => ({
222
+ step_id: dep.step_id,
223
+ result_summary: dep.result_summary ?? "",
224
+ evidence_refs: dep.evidence_refs ?? [],
225
+ }));
226
+ }
227
+ export async function applyBridgeResultToStepWithTransition(plan, stepId, result, repo) {
228
+ const updated = applyBridgeResultToStep(plan, stepId, result);
229
+ if (repo && plan.plan_id) {
230
+ const step = plan.steps.find((s) => s.step_id === stepId);
231
+ const updatedStep = updated.steps.find((s) => s.step_id === stepId);
232
+ if (step && updatedStep) {
233
+ await repo.appendTransitionRecord({
234
+ subject_kind: "plan_step",
235
+ subject_id: `${plan.plan_id}/${stepId}`,
236
+ from: step.status,
237
+ to: updatedStep.status,
238
+ reason: result.summary ?? "(bridge result applied)",
239
+ reason_code: updatedStep.status === "done"
240
+ ? "step_completed"
241
+ : updatedStep.status === "blocked"
242
+ ? "step_blocked"
243
+ : "step_failed",
244
+ evidence_refs: result.evidence_refs ?? [],
245
+ });
246
+ }
247
+ }
248
+ return updated;
249
+ }
250
+ export async function amendTaskPlanWithTransition(plan, amendment, repo, reason, reason_code) {
251
+ const updated = amendTaskPlan(plan, amendment);
252
+ if (repo && plan.plan_id) {
253
+ await repo.appendTransitionRecord({
254
+ subject_kind: "plan_step",
255
+ subject_id: plan.plan_id,
256
+ from: plan.status,
257
+ to: updated.status,
258
+ reason: reason ?? "(plan amended)",
259
+ reason_code: reason_code ?? "manual_amendment",
260
+ evidence_refs: amendment.evidence_refs ?? [],
261
+ });
262
+ }
263
+ return updated;
264
+ }
215
265
  export async function promoteParallelGroupToScheduler(plan, parallelGroup, owner = "capability-ops") {
216
266
  const readySteps = getReadyTaskSteps(plan).filter((step) => step.parallel_group === parallelGroup);
217
267
  if (readySteps.length === 0) {
@@ -311,7 +361,10 @@ export async function superviseTaskPlan(inputPlan, hooks) {
311
361
  }
312
362
  }
313
363
  const currentStep = readySteps[0];
314
- plan = markTaskStepStatus(plan, currentStep.step_id, "running");
364
+ const upstreamOutputs = collectUpstreamOutputs(plan, currentStep);
365
+ plan = markTaskStepStatus(plan, currentStep.step_id, "running", {
366
+ upstream_outputs: upstreamOutputs.length > 0 ? upstreamOutputs : undefined,
367
+ });
315
368
  await hooks.emitStatusEvent?.({
316
369
  status: "in_progress",
317
370
  summary: `Running ${currentStep.step_id}: ${currentStep.task}`,
@@ -372,6 +425,24 @@ export async function superviseTaskPlan(inputPlan, hooks) {
372
425
  }
373
426
  if (plan.steps.length > 0 && plan.steps.every((step) => step.status === "done" || step.status === "skipped")) {
374
427
  const finalGate = await hooks.executeGates?.(plan);
428
+ if (finalGate && typeof finalGate === "object" && finalGate.ok === false) {
429
+ const blockedPlan = {
430
+ ...plan,
431
+ status: "blocked",
432
+ };
433
+ await hooks.contextSnapshot?.(blockedPlan);
434
+ await hooks.emitStatusEvent?.({
435
+ status: "blocked",
436
+ summary: `Task plan ${plan.plan_id} blocked by failing gates`,
437
+ });
438
+ return {
439
+ plan: blockedPlan,
440
+ handoff_ids: handoffIds,
441
+ job_ids: jobIds,
442
+ circuit_opened: false,
443
+ final_gate: finalGate,
444
+ };
445
+ }
375
446
  await hooks.contextSnapshot?.(plan);
376
447
  await hooks.emitStatusEvent?.({
377
448
  status: "done",
@@ -4,7 +4,7 @@ import { isReadError } from "./shared.js";
4
4
  import { openStore } from "./store/ace-packed-store.js";
5
5
  import { ProjectionManager } from "./store/materializers/projection-manager.js";
6
6
  import { LedgerRepository } from "./store/repositories/ledger-repository.js";
7
- import { withStoreWriteQueue } from "./store/write-queue.js";
7
+ import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
8
8
  import { getWorkspaceStorePath, listStoreKeysSync, readStoreJsonSync, storeExistsSync, } from "./store/store-snapshot.js";
9
9
  import { operationalArtifactVirtualPath } from "./store/store-artifacts.js";
10
10
  /** Maximum entries kept in run-ledger.json before rotation. */
@@ -216,7 +216,7 @@ export async function appendRunLedgerEntrySafe(input) {
216
216
  return withFileLock(RUN_LEDGER_REL, () => appendRunLedgerEntry(input));
217
217
  }
218
218
  return withFileLock(RUN_LEDGER_REL, async () => {
219
- return withStoreWriteQueue(storePath, async () => {
219
+ return withStoreWriteCoordinator(storePath, async () => {
220
220
  const store = await openStore(storePath);
221
221
  try {
222
222
  const ledger = new LedgerRepository(store);
@@ -244,7 +244,7 @@ export async function appendRunLedgerEntrySafe(input) {
244
244
  finally {
245
245
  await store.close();
246
246
  }
247
- });
247
+ }, { operation_label: "appendRunLedgerEntrySafe" });
248
248
  });
249
249
  }
250
250
  export function getRunLedgerPath() {
@@ -5,14 +5,22 @@ export interface ShellCommandResult {
5
5
  stdout: string;
6
6
  stderr: string;
7
7
  timed_out: boolean;
8
+ timeout_reason?: "turn_budget" | "stall";
8
9
  duration_ms: number;
10
+ last_progress_at: number;
11
+ }
12
+ export interface ShellCommandProgressEvent {
13
+ source: "spawn" | "stdout" | "stderr";
14
+ at: number;
9
15
  }
10
16
  export interface RunShellCommandOptions {
11
17
  cwd: string;
12
18
  env?: NodeJS.ProcessEnv;
13
19
  timeout_ms: number;
20
+ stall_timeout_ms?: number;
14
21
  max_output_bytes?: number;
15
22
  on_spawn?: (child: ChildProcess) => void;
23
+ on_progress?: (event: ShellCommandProgressEvent) => void;
16
24
  }
17
25
  export declare function runShellCommand(command: string, options: RunShellCommandOptions): Promise<ShellCommandResult>;
18
26
  //# sourceMappingURL=runtime-command.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ const STALL_TIMEOUT_SCHEDULER_GRACE_MS = 50;
2
3
  function appendChunk(buffer, chunk, maxBytes) {
3
4
  const next = buffer + chunk;
4
5
  if (Buffer.byteLength(next, "utf8") <= maxBytes) {
@@ -14,7 +15,10 @@ export async function runShellCommand(command, options) {
14
15
  let stdout = "";
15
16
  let stderr = "";
16
17
  let timedOut = false;
18
+ let timeoutReason;
17
19
  let finished = false;
20
+ let lastProgressAt = startedAt;
21
+ let stallTimer;
18
22
  let forcedKillTimer;
19
23
  let child;
20
24
  try {
@@ -29,32 +33,58 @@ export async function runShellCommand(command, options) {
29
33
  return;
30
34
  }
31
35
  options.on_spawn?.(child);
36
+ const requestTimeout = (reason) => {
37
+ if (finished || timedOut)
38
+ return;
39
+ timedOut = true;
40
+ timeoutReason = reason;
41
+ child.kill("SIGTERM");
42
+ forcedKillTimer = setTimeout(() => {
43
+ if (!finished)
44
+ child.kill("SIGKILL");
45
+ }, 500);
46
+ };
47
+ const resetStallTimer = () => {
48
+ if (stallTimer)
49
+ clearTimeout(stallTimer);
50
+ const stallTimeoutMs = options.stall_timeout_ms;
51
+ if (!stallTimeoutMs || stallTimeoutMs <= 0)
52
+ return;
53
+ stallTimer = setTimeout(() => requestTimeout("stall"), stallTimeoutMs + STALL_TIMEOUT_SCHEDULER_GRACE_MS);
54
+ };
55
+ const recordProgress = (source) => {
56
+ lastProgressAt = Date.now();
57
+ options.on_progress?.({ source, at: lastProgressAt });
58
+ resetStallTimer();
59
+ };
32
60
  const finish = (result) => {
33
61
  if (finished)
34
62
  return;
35
63
  finished = true;
64
+ if (stallTimer)
65
+ clearTimeout(stallTimer);
36
66
  if (forcedKillTimer)
37
67
  clearTimeout(forcedKillTimer);
38
68
  resolve(result);
39
69
  };
40
70
  const timeout = setTimeout(() => {
41
- timedOut = true;
42
- child.kill("SIGTERM");
43
- forcedKillTimer = setTimeout(() => {
44
- if (!finished)
45
- child.kill("SIGKILL");
46
- }, 500);
71
+ requestTimeout("turn_budget");
47
72
  }, options.timeout_ms);
73
+ recordProgress("spawn");
48
74
  child.stdout?.setEncoding("utf8");
49
75
  child.stdout?.on("data", (chunk) => {
50
76
  stdout = appendChunk(stdout, String(chunk), maxOutputBytes);
77
+ recordProgress("stdout");
51
78
  });
52
79
  child.stderr?.setEncoding("utf8");
53
80
  child.stderr?.on("data", (chunk) => {
54
81
  stderr = appendChunk(stderr, String(chunk), maxOutputBytes);
82
+ recordProgress("stderr");
55
83
  });
56
84
  child.on("error", (error) => {
57
85
  clearTimeout(timeout);
86
+ if (stallTimer)
87
+ clearTimeout(stallTimer);
58
88
  if (forcedKillTimer)
59
89
  clearTimeout(forcedKillTimer);
60
90
  if (!finished)
@@ -68,7 +98,9 @@ export async function runShellCommand(command, options) {
68
98
  stdout,
69
99
  stderr,
70
100
  timed_out: timedOut,
101
+ timeout_reason: timeoutReason,
71
102
  duration_ms: Date.now() - startedAt,
103
+ last_progress_at: lastProgressAt,
72
104
  });
73
105
  });
74
106
  });
@@ -5,6 +5,13 @@ export declare const RUNTIME_EXECUTOR_SESSION_SCHEMA_REL_PATH = "agent-state/MOD
5
5
  export declare const RUNTIME_EXECUTOR_SESSION_SCHEMA_NAME = "runtime-executor-session-registry@1.0.0";
6
6
  export type UnattendedSessionStatus = "starting" | "running" | "completed" | "failed" | "blocked" | "stopped";
7
7
  export type UnattendedTurnStatus = "completed" | "failed" | "blocked" | "stopped";
8
+ export type TurnOutcome = "no_op_success" | "meaningful_completion" | "escalation_blocker";
9
+ export interface TurnOutputPolicy {
10
+ emit_to: ("tui" | "tracker" | "handoff" | "vericify")[];
11
+ silent_unless_blocked: boolean;
12
+ require_approval_before_emit: boolean;
13
+ }
14
+ export declare const DEFAULT_TURN_OUTPUT_POLICY: TurnOutputPolicy;
8
15
  export interface UnattendedToolCallRecord {
9
16
  tool_name: string;
10
17
  ok: boolean;
@@ -32,6 +39,8 @@ export interface UnattendedTurnRecord {
32
39
  stdout: string;
33
40
  stderr: string;
34
41
  tool_calls: UnattendedToolCallRecord[];
42
+ turn_outcome?: TurnOutcome;
43
+ outcome_reason?: string;
35
44
  }
36
45
  export interface UnattendedSessionRecord {
37
46
  session_id: string;
@@ -55,6 +64,7 @@ export interface UnattendedSessionRecord {
55
64
  last_error?: string;
56
65
  cleanup_error?: string;
57
66
  workspace_cleanup_status: "pending" | "removed" | "archived" | "failed";
67
+ output_policy?: TurnOutputPolicy;
58
68
  turns: UnattendedTurnRecord[];
59
69
  }
60
70
  export interface UnattendedSessionRegistry {
@@ -64,6 +74,7 @@ export interface UnattendedSessionRegistry {
64
74
  }
65
75
  export interface StartUnattendedSessionInput {
66
76
  session_id?: string;
77
+ workspace_root?: string;
67
78
  task: string;
68
79
  context?: Record<string, unknown>;
69
80
  workspace_name?: string;
@@ -73,6 +84,9 @@ export interface StartUnattendedSessionInput {
73
84
  max_turns?: number;
74
85
  turn_timeout_ms?: number;
75
86
  auto_cleanup?: boolean;
87
+ emit_to?: TurnOutputPolicy["emit_to"];
88
+ silent_unless_blocked?: boolean;
89
+ require_approval_before_emit?: boolean;
76
90
  }
77
91
  export interface StartUnattendedSessionResult {
78
92
  ok: boolean;