@voybio/ace-swarm 0.2.5 → 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.
- package/CHANGELOG.md +11 -1
- package/README.md +20 -13
- package/assets/agent-state/EVIDENCE_LOG.md +1 -1
- package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
- package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
- package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
- package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
- package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
- package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
- package/assets/agent-state/STATUS.md +2 -2
- package/assets/scripts/ace-hook-dispatch.mjs +70 -6
- package/assets/scripts/render-mcp-configs.sh +19 -5
- package/dist/ace-context.js +22 -1
- package/dist/ace-server-instructions.js +3 -3
- package/dist/ace-state-resolver.js +5 -3
- package/dist/astgrep-index.d.ts +9 -1
- package/dist/astgrep-index.js +14 -3
- package/dist/cli.js +27 -20
- package/dist/handoff-registry.js +5 -5
- package/dist/helpers/artifacts.d.ts +19 -0
- package/dist/helpers/artifacts.js +152 -0
- package/dist/helpers/bootstrap.d.ts +24 -0
- package/dist/helpers/bootstrap.js +894 -0
- package/dist/helpers/constants.d.ts +53 -0
- package/dist/helpers/constants.js +288 -0
- package/dist/helpers/drift.d.ts +13 -0
- package/dist/helpers/drift.js +45 -0
- package/dist/helpers/path-utils.d.ts +17 -0
- package/dist/helpers/path-utils.js +104 -0
- package/dist/helpers/store-resolution.d.ts +19 -0
- package/dist/helpers/store-resolution.js +301 -0
- package/dist/helpers/workspace-root.d.ts +3 -0
- package/dist/helpers/workspace-root.js +80 -0
- package/dist/helpers.d.ts +8 -125
- package/dist/helpers.js +8 -1768
- package/dist/job-scheduler.js +3 -3
- package/dist/local-model-runtime.js +12 -1
- package/dist/model-bridge.d.ts +7 -0
- package/dist/model-bridge.js +75 -5
- package/dist/orchestrator-supervisor.d.ts +14 -0
- package/dist/orchestrator-supervisor.js +72 -1
- package/dist/run-ledger.js +3 -3
- package/dist/runtime-command.d.ts +8 -0
- package/dist/runtime-command.js +38 -6
- package/dist/runtime-executor.d.ts +14 -0
- package/dist/runtime-executor.js +669 -171
- package/dist/runtime-profile.d.ts +32 -0
- package/dist/runtime-profile.js +89 -13
- package/dist/runtime-tool-specs.d.ts +21 -0
- package/dist/runtime-tool-specs.js +78 -3
- package/dist/safe-edit.d.ts +7 -0
- package/dist/safe-edit.js +163 -37
- package/dist/schemas.js +19 -0
- package/dist/shared.d.ts +2 -2
- package/dist/status-events.js +9 -6
- package/dist/store/ace-packed-store.d.ts +3 -2
- package/dist/store/ace-packed-store.js +188 -110
- package/dist/store/bootstrap-store.d.ts +1 -1
- package/dist/store/bootstrap-store.js +94 -81
- package/dist/store/cache-workspace.js +11 -5
- package/dist/store/materializers/context-snapshot-materializer.js +6 -2
- package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
- package/dist/store/materializers/hook-context-materializer.js +11 -21
- package/dist/store/materializers/host-file-materializer.js +6 -0
- package/dist/store/materializers/projection-manager.d.ts +0 -1
- package/dist/store/materializers/projection-manager.js +5 -13
- package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
- package/dist/store/materializers/vericify-projector.d.ts +7 -7
- package/dist/store/materializers/vericify-projector.js +11 -11
- package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
- package/dist/store/repositories/local-model-runtime-repository.js +242 -6
- package/dist/store/skills-install.d.ts +4 -0
- package/dist/store/skills-install.js +21 -12
- package/dist/store/state-reader.d.ts +2 -0
- package/dist/store/state-reader.js +20 -0
- package/dist/store/store-artifacts.d.ts +7 -0
- package/dist/store/store-artifacts.js +27 -1
- package/dist/store/store-authority-audit.d.ts +18 -1
- package/dist/store/store-authority-audit.js +115 -5
- package/dist/store/store-snapshot.d.ts +3 -0
- package/dist/store/store-snapshot.js +22 -2
- package/dist/store/workspace-store-paths.d.ts +39 -0
- package/dist/store/workspace-store-paths.js +94 -0
- package/dist/store/write-coordinator.d.ts +65 -0
- package/dist/store/write-coordinator.js +386 -0
- package/dist/todo-state.js +5 -5
- package/dist/tools-agent.js +268 -14
- package/dist/tools-discovery.js +1 -1
- package/dist/tools-files.d.ts +7 -0
- package/dist/tools-files.js +299 -10
- package/dist/tools-framework.js +25 -5
- package/dist/tools-handoff.js +2 -2
- package/dist/tools-lifecycle.js +4 -4
- package/dist/tools-memory.js +6 -6
- package/dist/tools-todo.js +2 -2
- package/dist/tracker-adapters.d.ts +1 -1
- package/dist/tracker-adapters.js +13 -18
- package/dist/tracker-sync.js +5 -3
- package/dist/tui/agent-runner.js +3 -1
- package/dist/tui/chat.js +103 -7
- package/dist/tui/dashboard.d.ts +1 -0
- package/dist/tui/dashboard.js +43 -0
- package/dist/tui/layout.d.ts +20 -0
- package/dist/tui/layout.js +31 -1
- package/dist/tui/local-model-contract.d.ts +6 -2
- package/dist/tui/local-model-contract.js +16 -3
- package/dist/vericify-bridge.d.ts +5 -0
- package/dist/vericify-bridge.js +27 -3
- package/dist/workspace-manager.d.ts +30 -3
- package/dist/workspace-manager.js +257 -27
- package/package.json +1 -2
- package/dist/internal-tool-runtime.d.ts +0 -21
- package/dist/internal-tool-runtime.js +0 -136
- package/dist/store/workspace-snapshot.d.ts +0 -26
- package/dist/store/workspace-snapshot.js +0 -107
package/dist/job-scheduler.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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 {
|
|
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")
|
package/dist/model-bridge.d.ts
CHANGED
|
@@ -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;
|
package/dist/model-bridge.js
CHANGED
|
@@ -68,8 +68,8 @@ function parseEnvelope(raw) {
|
|
|
68
68
|
}
|
|
69
69
|
catch {
|
|
70
70
|
return {
|
|
71
|
-
status: "
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
package/dist/run-ledger.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
package/dist/runtime-command.js
CHANGED
|
@@ -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
|
-
|
|
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;
|