@voybio/ace-swarm 2.4.8 → 2.4.10
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 +52 -14
- package/README.md +14 -10
- package/assets/.agents/ACE/ACE_coders/AGENTS.md +23 -30
- package/assets/.agents/ACE/ACE_coders/INSTRUCTIONS.md +21 -30
- package/assets/.agents/skills/landing-review-watcher/SKILL.md +1 -1
- package/assets/.agents/skills/learn-from-work/SKILL.md +41 -0
- package/assets/agent-state/ACE_WORKFLOW.md +25 -2
- package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +143 -2
- package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +2 -2
- package/assets/agent-state/MODULES/schemas/SWARM_HANDOFF.schema.json +10 -0
- package/assets/eval-battery/battery.json +1 -1
- package/assets/instructions/ACE_Coder.instructions.md +9 -13
- package/assets/scripts/ace-hook-dispatch.mjs +16 -411
- package/assets/tasks/SWARM_HANDOFF.example_ui_to_coders.json +1 -2
- package/assets/vendor/README.md +9 -0
- package/assets/vendor/vericify-reader.mjs +272 -0
- package/assets/vendor/vericify-reader.provenance.json +10 -0
- package/dist/ace-autonomy.d.ts +58 -0
- package/dist/ace-autonomy.js +211 -6
- package/dist/ace-internal-tools.d.ts +1 -0
- package/dist/ace-internal-tools.js +3 -3
- package/dist/ace-server-instructions.d.ts +3 -3
- package/dist/ace-server-instructions.js +46 -192
- package/dist/cli.js +44 -6
- package/dist/experience-reflector.d.ts +7 -0
- package/dist/experience-reflector.js +39 -0
- package/dist/goal-engine.d.ts +64 -7
- package/dist/goal-engine.js +197 -41
- package/dist/handoff-registry.d.ts +10 -0
- package/dist/handoff-registry.js +59 -0
- package/dist/helpers/artifacts.d.ts +6 -2
- package/dist/helpers/artifacts.js +8 -6
- package/dist/helpers/bootstrap.js +13 -20
- package/dist/helpers/path-utils.js +2 -0
- package/dist/helpers/store-resolution.js +18 -3
- package/dist/helpers/workspace-root.d.ts +2 -0
- package/dist/helpers/workspace-root.js +9 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +1 -0
- package/dist/hermes/bridge-protocol.d.ts +1 -1
- package/dist/hermes/session-manager.d.ts +17 -0
- package/dist/hermes/session-manager.js +67 -14
- package/dist/host-goal-engine.d.ts +138 -0
- package/dist/host-goal-engine.js +793 -0
- package/dist/host-goal-status.d.ts +113 -0
- package/dist/host-goal-status.js +175 -0
- package/dist/host-learning.d.ts +68 -0
- package/dist/host-learning.js +115 -0
- package/dist/host-observation.d.ts +12 -0
- package/dist/host-observation.js +91 -0
- package/dist/host-plan-revision.d.ts +14 -0
- package/dist/host-plan-revision.js +101 -0
- package/dist/host-verification-job.d.ts +12 -0
- package/dist/host-verification-job.js +183 -0
- package/dist/host-work-context.d.ts +12 -0
- package/dist/host-work-context.js +8 -0
- package/dist/job-scheduler.d.ts +58 -13
- package/dist/job-scheduler.js +298 -48
- package/dist/job-worker.d.ts +3 -1
- package/dist/job-worker.js +10 -3
- package/dist/kanban.d.ts +4 -1
- package/dist/kanban.js +44 -16
- package/dist/learned-artifacts.d.ts +64 -0
- package/dist/learned-artifacts.js +321 -0
- package/dist/learning-controls.d.ts +142 -0
- package/dist/learning-controls.js +135 -0
- package/dist/learning-input-context.d.ts +9 -0
- package/dist/learning-input-context.js +62 -0
- package/dist/learning-lifecycle.d.ts +10 -0
- package/dist/learning-lifecycle.js +11 -0
- package/dist/learning-overlays.d.ts +3 -0
- package/dist/learning-overlays.js +17 -0
- package/dist/learning-policy.d.ts +40 -0
- package/dist/learning-policy.js +38 -0
- package/dist/learning-records.d.ts +35 -0
- package/dist/learning-records.js +17 -0
- package/dist/learning-scorecard.d.ts +82 -0
- package/dist/learning-scorecard.js +86 -0
- package/dist/learning-serving.d.ts +29 -0
- package/dist/learning-serving.js +134 -0
- package/dist/linked-work.d.ts +9 -0
- package/dist/linked-work.js +28 -0
- package/dist/local-model-runtime.js +1 -1
- package/dist/mcp-command-progress.d.ts +16 -0
- package/dist/mcp-command-progress.js +70 -0
- package/dist/model-bridge.d.ts +14 -13
- package/dist/model-bridge.js +73 -41
- package/dist/orchestrator-supervisor.d.ts +111 -6
- package/dist/orchestrator-supervisor.js +67 -43
- package/dist/plan-proposal.js +3 -3
- package/dist/playbook-curator.d.ts +1 -0
- package/dist/playbook-curator.js +11 -9
- package/dist/process-identity.d.ts +8 -0
- package/dist/process-identity.js +38 -0
- package/dist/progress-command.d.ts +34 -0
- package/dist/progress-command.js +153 -0
- package/dist/prompts.js +15 -15
- package/dist/resources.js +2 -2
- package/dist/runtime-command.d.ts +8 -1
- package/dist/runtime-command.js +48 -7
- package/dist/runtime-executor.js +33 -11
- package/dist/runtime-profile-syntax.d.ts +6 -0
- package/dist/runtime-profile-syntax.js +121 -0
- package/dist/runtime-profile.d.ts +37 -5
- package/dist/runtime-profile.js +36 -129
- package/dist/runtime-tool-specs.js +1 -1
- package/dist/safe-edit.d.ts +7 -3
- package/dist/safe-edit.js +40 -47
- package/dist/schemas.js +4 -2
- package/dist/server.d.ts +2 -16
- package/dist/server.js +14 -20
- package/dist/shared.d.ts +2 -0
- package/dist/shared.js +2 -0
- package/dist/skill-auditor.d.ts +19 -0
- package/dist/skill-auditor.js +32 -2
- package/dist/skill-catalog.d.ts +4 -1
- package/dist/skill-catalog.js +6 -5
- package/dist/store/ace-packed-store.d.ts +5 -1
- package/dist/store/ace-packed-store.js +33 -1
- package/dist/store/bootstrap-store.js +19 -12
- package/dist/store/cache-workspace.d.ts +8 -0
- package/dist/store/cache-workspace.js +94 -27
- package/dist/store/importer.js +11 -20
- package/dist/store/knowledge-bake.d.ts +3 -1
- package/dist/store/knowledge-bake.js +10 -1
- package/dist/store/legacy-vericify-posts.d.ts +8 -0
- package/dist/store/legacy-vericify-posts.js +76 -0
- package/dist/store/materializers/hook-context-materializer.d.ts +2 -1
- package/dist/store/materializers/hook-context-materializer.js +6 -2
- package/dist/store/materializers/host-file-materializer.js +2 -2
- package/dist/store/materializers/projection-manager.d.ts +4 -1
- package/dist/store/materializers/projection-manager.js +10 -3
- package/dist/store/materializers/todo-syncer.d.ts +39 -20
- package/dist/store/materializers/todo-syncer.js +208 -110
- package/dist/store/materializers/vericify-projector.js +15 -26
- package/dist/store/repositories/goal-repository.d.ts +51 -2
- package/dist/store/repositories/goal-repository.js +114 -48
- package/dist/store/repositories/playbook-repository.d.ts +41 -3
- package/dist/store/repositories/playbook-repository.js +61 -5
- package/dist/store/repositories/todo-repository.d.ts +8 -2
- package/dist/store/repositories/todo-repository.js +1 -1
- package/dist/store/repositories/vericify-repository.d.ts +4 -0
- package/dist/store/repositories/vericify-repository.js +46 -0
- package/dist/store/skills-install.d.ts +1 -0
- package/dist/store/skills-install.js +20 -16
- package/dist/store/source-preservation.d.ts +24 -0
- package/dist/store/source-preservation.js +79 -0
- package/dist/store/store-artifacts.js +8 -1
- package/dist/store/test-write-boundary.d.ts +16 -0
- package/dist/store/test-write-boundary.js +175 -0
- package/dist/store/types.d.ts +5 -1
- package/dist/store/write-coordinator.js +16 -0
- package/dist/task-plan-context.d.ts +11 -0
- package/dist/task-plan-context.js +59 -0
- package/dist/todo-state.d.ts +19 -9
- package/dist/todo-state.js +204 -321
- package/dist/tools-agent.d.ts +1 -1
- package/dist/tools-agent.js +38 -62
- package/dist/tools-files.d.ts +0 -3
- package/dist/tools-files.js +21 -11
- package/dist/tools-framework.js +16 -25
- package/dist/tools-goal.d.ts +2 -4
- package/dist/tools-goal.js +119 -16
- package/dist/tools-handoff.d.ts +3 -2
- package/dist/tools-handoff.js +53 -23
- package/dist/tools-lifecycle.js +1 -1
- package/dist/tools-memory.d.ts +0 -3
- package/dist/tools-memory.js +51 -6
- package/dist/tools-scheduler.js +20 -17
- package/dist/tools-todo.js +160 -148
- package/dist/vericify-bridge.d.ts +8 -0
- package/dist/vericify-bridge.js +18 -0
- package/dist/vericify-context.d.ts +4 -0
- package/dist/vericify-context.js +26 -5
- package/dist/vericify-native.d.ts +101 -0
- package/dist/vericify-native.js +221 -0
- package/dist/verification-worker.d.ts +2 -0
- package/dist/verification-worker.js +31 -0
- package/docs/mcp-tool-reference.md +50 -10
- package/docs/troubleshooting.md +29 -0
- package/package.json +19 -6
- package/scripts/dev/run-tests-isolated.mjs +165 -0
- package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
- package/scripts/dev/test-isolation-guard.mjs +20 -0
- package/scripts/dev/test-preload.mjs +153 -0
- package/scripts/dev/test-source-inventory.mjs +31 -0
- package/docs/development.md +0 -46
package/dist/goal-engine.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ACE Goal Engine (WS-F).
|
|
2
|
+
* ACE Goal Engine (WS-F), with attached MCP execution for host-authored plans.
|
|
3
|
+
* Host goals use the same filesystem records and plan primitives through
|
|
4
|
+
* next_step/complete_step. They enforce their declared contract and explicitly
|
|
5
|
+
* label completion without verification commands as not machine-verified.
|
|
3
6
|
*
|
|
4
7
|
* `startGoal("<objective>")` compiles a plan with ACE's own planner, then a
|
|
5
8
|
* self-paced driver loop (`runGoal`) executes it step-by-step under evidence-
|
|
@@ -9,7 +12,7 @@
|
|
|
9
12
|
* structurally impossible (there is no code path from a model message to
|
|
10
13
|
* `status: "done"`).
|
|
11
14
|
*
|
|
12
|
-
*
|
|
15
|
+
* In the engine-executed path, that guarantee rests on the success contract carrying at least one
|
|
13
16
|
* machine-checkable obligation. A contract with none is refused at `startGoal`
|
|
14
17
|
* and again at verify time with `contract_uncheckable`, never satisfied
|
|
15
18
|
* vacuously — see {@link enforceableObligationCount}.
|
|
@@ -27,14 +30,19 @@
|
|
|
27
30
|
* hermetically in tests with a stub bridge.
|
|
28
31
|
*/
|
|
29
32
|
import { existsSync } from "node:fs";
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
30
34
|
import { isAbsolute, join } from "node:path";
|
|
35
|
+
import { stripVTControlCharacters } from "node:util";
|
|
31
36
|
import { amendTaskPlan, applyBridgeResultToStep, createTaskPlan, deriveTaskPlanStatus, getReadyTaskSteps, markTaskStepStatus, } from "./orchestrator-supervisor.js";
|
|
32
37
|
import { proposePlan, validatePlan } from "./plan-proposal.js";
|
|
33
38
|
import { resolveWorkspaceRoot } from "./helpers.js";
|
|
34
|
-
import {
|
|
39
|
+
import { appendAutomaticVericifyProcessPostSafe } from "./vericify-bridge.js";
|
|
35
40
|
import { appendStatusEventSafe } from "./status-events.js";
|
|
36
41
|
import { GoalRepository, defaultGoalBudgets, newGoalId, } from "./store/repositories/goal-repository.js";
|
|
37
42
|
import { PlaybookRepository } from "./store/repositories/playbook-repository.js";
|
|
43
|
+
import { failureSignature } from './experience-reflector.js';
|
|
44
|
+
import { runProgressCommand } from './progress-command.js';
|
|
45
|
+
export const verificationCommandText = (command) => typeof command === 'string' ? command : command.cmd;
|
|
38
46
|
// ── Defaults for the injectable seams ────────────────────────────────────────
|
|
39
47
|
function defaultArtifactExists(path, workspaceRoot) {
|
|
40
48
|
if (!path || path.trim().length === 0)
|
|
@@ -43,16 +51,9 @@ function defaultArtifactExists(path, workspaceRoot) {
|
|
|
43
51
|
const abs = isAbsolute(clean) ? clean : join(workspaceRoot, clean);
|
|
44
52
|
return existsSync(abs);
|
|
45
53
|
}
|
|
46
|
-
async function defaultRunVerification(command, workspaceRoot) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
exec(command, { cwd: workspaceRoot, timeout: 10 * 60 * 1000, maxBuffer: 4 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
50
|
-
resolve({
|
|
51
|
-
ok: !error,
|
|
52
|
-
output: `${stdout ?? ""}${stderr ? `\n${stderr}` : ""}`.trim(),
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
});
|
|
54
|
+
async function defaultRunVerification(command, workspaceRoot, options) {
|
|
55
|
+
return runProgressCommand(command, workspaceRoot, { ...options,
|
|
56
|
+
...(options?.timeout_ms !== undefined ? { deadline_min: options.timeout_ms / 60000 } : {}) });
|
|
56
57
|
}
|
|
57
58
|
async function defaultSpawnStep(step, _plan, _goal) {
|
|
58
59
|
const { runLocalModelTask } = await import("./local-model-runtime.js");
|
|
@@ -66,7 +67,7 @@ async function defaultSpawnStep(step, _plan, _goal) {
|
|
|
66
67
|
function defaultSleep(ms) {
|
|
67
68
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
68
69
|
}
|
|
69
|
-
function
|
|
70
|
+
export function resolveGoalDeps(deps = {}) {
|
|
70
71
|
const workspaceRoot = deps.workspaceRoot ?? resolveWorkspaceRoot();
|
|
71
72
|
const repo = new GoalRepository(workspaceRoot);
|
|
72
73
|
return {
|
|
@@ -98,7 +99,7 @@ function resolveDeps(deps = {}) {
|
|
|
98
99
|
const kind = input.kind === "progress" || input.kind === "completion" || input.kind === "blocker"
|
|
99
100
|
? input.kind
|
|
100
101
|
: "blocker";
|
|
101
|
-
await
|
|
102
|
+
await appendAutomaticVericifyProcessPostSafe({
|
|
102
103
|
run_id: "workspace:current",
|
|
103
104
|
agent_id: "ace-goal-engine",
|
|
104
105
|
kind: kind === "completion" ? "completion" : kind === "progress" ? "progress" : "blocker",
|
|
@@ -109,6 +110,7 @@ function resolveDeps(deps = {}) {
|
|
|
109
110
|
}),
|
|
110
111
|
};
|
|
111
112
|
}
|
|
113
|
+
const resolveDeps = resolveGoalDeps;
|
|
112
114
|
// ── Success-contract compilation ─────────────────────────────────────────────
|
|
113
115
|
const CRITERION_VERIFICATION_HINT = /^\s*verify\s*:\s*(.+)$/i;
|
|
114
116
|
const OBJECTIVE_VERIFICATION_HINT = /(?:^|[.!?]\s+)verify\s*:\s*(.+)$/i;
|
|
@@ -203,15 +205,18 @@ function compileSuccessContract(proposal, input = {}) {
|
|
|
203
205
|
for (const command of extractVerificationCommands(input.objective ?? "", "objective")) {
|
|
204
206
|
commands.add(command);
|
|
205
207
|
}
|
|
206
|
-
for (const
|
|
207
|
-
const trimmed =
|
|
208
|
+
for (const declaration of input.verify_commands ?? []) {
|
|
209
|
+
const trimmed = verificationCommandText(declaration).trim();
|
|
208
210
|
if (trimmed)
|
|
209
211
|
commands.add(trimmed);
|
|
210
212
|
}
|
|
211
213
|
return {
|
|
212
214
|
expected_artifacts: Array.from(expected.values()),
|
|
213
215
|
required_evidence_refs: Array.from(evidence),
|
|
214
|
-
verification_commands: Array.from(commands)
|
|
216
|
+
verification_commands: Array.from(commands).map(command => {
|
|
217
|
+
const declaration = input.verify_commands?.find(c => verificationCommandText(c).trim() === command);
|
|
218
|
+
return declaration && typeof declaration !== 'string' ? { ...declaration, cmd: command } : command;
|
|
219
|
+
}),
|
|
215
220
|
gate_ids: [],
|
|
216
221
|
uncompilable_criteria: uncompilable,
|
|
217
222
|
};
|
|
@@ -252,6 +257,10 @@ async function escalate(d, goal, kind, reasonCode, summary) {
|
|
|
252
257
|
});
|
|
253
258
|
}
|
|
254
259
|
export async function startGoal(objective, options = {}, deps = {}) {
|
|
260
|
+
if (options.executor === 'host') {
|
|
261
|
+
const { startHostGoal } = await import('./host-goal-engine.js');
|
|
262
|
+
return startHostGoal(objective, options, deps);
|
|
263
|
+
}
|
|
255
264
|
const d = resolveDeps(deps);
|
|
256
265
|
const trimmed = objective.trim();
|
|
257
266
|
// `typeof NaN === "number"`, and NaN silently disables a budget in memory
|
|
@@ -259,11 +268,11 @@ export async function startGoal(objective, options = {}, deps = {}) {
|
|
|
259
268
|
// budget fire immediately after the record is reloaded. Neither is a budget.
|
|
260
269
|
const budgets = {
|
|
261
270
|
...defaultGoalBudgets(),
|
|
262
|
-
...(Number.isFinite(options.budget_hours)
|
|
263
|
-
? { max_wall_clock_ms:
|
|
271
|
+
...(Number.isFinite(options.budget_hours) && options.budget_hours > 0 && Number.isFinite(options.budget_hours * 3600000)
|
|
272
|
+
? { max_wall_clock_ms: options.budget_hours * 3600000, deadline_explicit: true }
|
|
264
273
|
: {}),
|
|
265
|
-
...(Number.
|
|
266
|
-
...(Number.
|
|
274
|
+
...(Number.isSafeInteger(options.max_ticks) && options.max_ticks >= 0 ? { max_ticks: options.max_ticks, tick_budget_explicit: true } : {}),
|
|
275
|
+
...(Number.isSafeInteger(options.max_replans) && options.max_replans >= 0 ? { max_replans: options.max_replans, replan_budget_explicit: true } : {}),
|
|
267
276
|
};
|
|
268
277
|
const iso = new Date(d.now()).toISOString();
|
|
269
278
|
let goal = {
|
|
@@ -366,6 +375,8 @@ export async function tickGoal(goalId, deps = {}) {
|
|
|
366
375
|
const d = resolveDeps(deps);
|
|
367
376
|
const goal = requireGoal(d, goalId);
|
|
368
377
|
const plan = requirePlan(d, goal);
|
|
378
|
+
if (goal.executor === 'host')
|
|
379
|
+
return { goal, plan, progressed: false };
|
|
369
380
|
const ready = getReadyTaskSteps(plan);
|
|
370
381
|
if (ready.length === 0) {
|
|
371
382
|
return { goal, plan, progressed: false };
|
|
@@ -400,8 +411,13 @@ export async function tickGoal(goalId, deps = {}) {
|
|
|
400
411
|
reason_code: "spawn_threw",
|
|
401
412
|
};
|
|
402
413
|
}
|
|
403
|
-
nextPlan = applyBridgeResultToStep(nextPlan, step.step_id, result);
|
|
414
|
+
nextPlan = applyBridgeResultToStep(d.repo.getPlan(goalId) ?? nextPlan, step.step_id, result);
|
|
404
415
|
const finalStep = nextPlan.steps.find((s) => s.step_id === step.step_id);
|
|
416
|
+
// Preserve the bridge's existing evidence declarations alongside tool receipts.
|
|
417
|
+
// New references distinguish progress; they do not prove changed path contents.
|
|
418
|
+
if (finalStep && result.evidence_refs?.length) {
|
|
419
|
+
finalStep.evidence_refs = [...new Set([...(finalStep.evidence_refs ?? []), ...result.evidence_refs])];
|
|
420
|
+
}
|
|
405
421
|
// Re-hydrate so any field mutated *during* the spawn (e.g. an operator
|
|
406
422
|
// stop_requested via stopGoal) is preserved rather than clobbered by this
|
|
407
423
|
// tick's stale in-memory snapshot.
|
|
@@ -449,17 +465,21 @@ export async function checkSuccessContract(d, goal) {
|
|
|
449
465
|
if (!d.artifactExists(ref, d.workspaceRoot))
|
|
450
466
|
missingEvidence.push(ref);
|
|
451
467
|
}
|
|
452
|
-
const failed = [];
|
|
453
|
-
for (const
|
|
454
|
-
const
|
|
455
|
-
|
|
468
|
+
const failed = [], signatures = [];
|
|
469
|
+
for (const declaration of contract.verification_commands) {
|
|
470
|
+
const command = verificationCommandText(declaration);
|
|
471
|
+
const outcome = await d.runVerification(command, d.workspaceRoot, typeof declaration === 'string' ? undefined : declaration);
|
|
472
|
+
if (!outcome.ok) {
|
|
456
473
|
failed.push(command);
|
|
474
|
+
signatures.push(`${command}:${failureProgressSignature(outcome.output)}`);
|
|
475
|
+
}
|
|
457
476
|
}
|
|
458
477
|
return {
|
|
459
478
|
ok: missing.length === 0 && missingEvidence.length === 0 && failed.length === 0,
|
|
460
479
|
missing_artifacts: missing,
|
|
461
480
|
missing_evidence_refs: missingEvidence,
|
|
462
481
|
failed_commands: failed,
|
|
482
|
+
failure_signatures: signatures,
|
|
463
483
|
uncheckable: false,
|
|
464
484
|
};
|
|
465
485
|
}
|
|
@@ -487,12 +507,14 @@ function playbookRetryNote(workspaceRoot, reasonCode) {
|
|
|
487
507
|
export async function runGoal(goalId, deps = {}) {
|
|
488
508
|
const d = resolveDeps(deps);
|
|
489
509
|
let goal = requireGoal(d, goalId);
|
|
510
|
+
if (goal.executor === 'host')
|
|
511
|
+
return { goal, plan: d.repo.getPlan(goalId) };
|
|
490
512
|
if (goal.status !== "executing" && goal.status !== "verifying") {
|
|
491
513
|
return { goal, plan: d.repo.getPlan(goalId) };
|
|
492
514
|
}
|
|
493
515
|
let backoff = BACKOFF_START_MS;
|
|
494
|
-
//
|
|
495
|
-
//
|
|
516
|
+
// Productive work has no implicit tick or elapsed-time ceiling. Only an
|
|
517
|
+
// operator allowance, unchanged failures, or an explicit transition stops it.
|
|
496
518
|
for (;;) {
|
|
497
519
|
goal = requireGoal(d, goalId);
|
|
498
520
|
// 1. Operator stop.
|
|
@@ -543,8 +565,21 @@ export async function runGoal(goalId, deps = {}) {
|
|
|
543
565
|
d.repo.saveGoal(goal);
|
|
544
566
|
break;
|
|
545
567
|
}
|
|
546
|
-
//
|
|
547
|
-
|
|
568
|
+
// Repeated unchanged failures park the loop; different failures are progress.
|
|
569
|
+
const repeated = observeFailure(goal, JSON.stringify({
|
|
570
|
+
artifacts: [...check.missing_artifacts].sort(), evidence: [...check.missing_evidence_refs].sort(),
|
|
571
|
+
commands: [...(check.failure_signatures ?? check.failed_commands)].sort(),
|
|
572
|
+
}));
|
|
573
|
+
if (repeated && !(goal.budgets.replan_budget_explicit && goal.replan_count >= goal.budgets.max_replans)) {
|
|
574
|
+
goal.status = 'blocked';
|
|
575
|
+
goal.reason_code = 'repeated_failure';
|
|
576
|
+
goal = d.repo.saveGoal(goal);
|
|
577
|
+
await escalate(d, goal, 'blocker', 'repeated_failure', 'The same success-contract failure repeated three times without progress; operator review is required.');
|
|
578
|
+
d.repo.saveGoal(goal);
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
// An explicit operator cap is still honored; defaults never limit corrections.
|
|
582
|
+
if (goal.budgets.replan_budget_explicit && goal.replan_count >= goal.budgets.max_replans) {
|
|
548
583
|
const replanCode = "goal_budget_exhausted:max_replans";
|
|
549
584
|
goal.status = "blocked";
|
|
550
585
|
goal.reason_code = replanCode;
|
|
@@ -584,14 +619,24 @@ export async function runGoal(goalId, deps = {}) {
|
|
|
584
619
|
goal = d.repo.saveGoal(goal);
|
|
585
620
|
await d.appendProcessPost({
|
|
586
621
|
kind: "progress",
|
|
587
|
-
summary: `Goal ${goalId}: success contract unmet, synthesized remediation step (replan ${goal.replan_count}
|
|
622
|
+
summary: `Goal ${goalId}: success contract unmet, synthesized remediation step (replan ${goal.replan_count}${goal.budgets.replan_budget_explicit ? `/${goal.budgets.max_replans}` : ''}).`,
|
|
588
623
|
});
|
|
589
624
|
backoff = BACKOFF_START_MS;
|
|
590
625
|
continue;
|
|
591
626
|
}
|
|
592
627
|
// 4. Plan blocked/failed → classify and either replan or park.
|
|
593
628
|
if (status === "blocked" || status === "failed") {
|
|
594
|
-
|
|
629
|
+
const failures = plan.steps.filter(s => s.status === 'blocked' || s.status === 'failed')
|
|
630
|
+
.map(s => JSON.stringify({ task: s.task, failure: failureProgressSignature(s.blocked_reason ?? s.result_summary ?? s.status), evidence: [...(s.evidence_refs ?? [])].sort() })).sort();
|
|
631
|
+
if (observeFailure(goal, JSON.stringify(failures)) && !(goal.budgets.replan_budget_explicit && goal.replan_count >= goal.budgets.max_replans)) {
|
|
632
|
+
goal.status = 'blocked';
|
|
633
|
+
goal.reason_code = 'repeated_failure';
|
|
634
|
+
goal = d.repo.saveGoal(goal);
|
|
635
|
+
await escalate(d, goal, 'blocker', 'repeated_failure', `The same step failure repeated three times without progress (${blockedReasons(plan)}).`);
|
|
636
|
+
d.repo.saveGoal(goal);
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
if (goal.budgets.replan_budget_explicit && goal.replan_count >= goal.budgets.max_replans) {
|
|
595
640
|
goal.status = "blocked";
|
|
596
641
|
goal.reason_code = "plan_blocked";
|
|
597
642
|
goal = d.repo.saveGoal(goal);
|
|
@@ -637,7 +682,7 @@ export async function runGoal(goalId, deps = {}) {
|
|
|
637
682
|
d.repo.savePlan(goalId, retried);
|
|
638
683
|
await d.appendProcessPost({
|
|
639
684
|
kind: "progress",
|
|
640
|
-
summary: `Goal ${goalId}: plan ${status}, retrying blocked steps (replan ${goal.replan_count}
|
|
685
|
+
summary: `Goal ${goalId}: plan ${status}, retrying blocked steps (replan ${goal.replan_count}${goal.budgets.replan_budget_explicit ? `/${goal.budgets.max_replans}` : ''}).`,
|
|
641
686
|
});
|
|
642
687
|
backoff = BACKOFF_START_MS;
|
|
643
688
|
continue;
|
|
@@ -661,8 +706,8 @@ export async function runGoal(goalId, deps = {}) {
|
|
|
661
706
|
d.repo.saveGoal(goal);
|
|
662
707
|
break;
|
|
663
708
|
}
|
|
664
|
-
// A step is still running (an engine restarted mid-step).
|
|
665
|
-
//
|
|
709
|
+
// A step is still running (an engine restarted mid-step). Wait cooperatively;
|
|
710
|
+
// operator allowances apply only when explicitly supplied.
|
|
666
711
|
goal.tick_count += 1;
|
|
667
712
|
goal = d.repo.saveGoal(goal);
|
|
668
713
|
await d.sleep(backoff);
|
|
@@ -671,10 +716,24 @@ export async function runGoal(goalId, deps = {}) {
|
|
|
671
716
|
return { goal, plan: d.repo.getPlan(goalId) };
|
|
672
717
|
}
|
|
673
718
|
// ── resumeGoal / stopGoal / status ───────────────────────────────────────────
|
|
674
|
-
export async function resumeGoal(goalId, deps = {}) {
|
|
719
|
+
export async function resumeGoal(goalId, deps = {}, options = {}) {
|
|
675
720
|
const d = resolveDeps(deps);
|
|
676
|
-
|
|
721
|
+
let goal = requireGoal(d, goalId);
|
|
677
722
|
const plan = d.repo.getPlan(goalId);
|
|
723
|
+
if (goal.executor === 'host') {
|
|
724
|
+
const resumed = await withHostResume(d, goalId, options);
|
|
725
|
+
if (resumed.plan?.steps.some(step => step.observation_check && step.observation_check.attempt === step.attempt && ['pending', 'unmet', 'unsupported'].includes(step.observation_check.status)) && !resumed.goal.reason_code?.startsWith('follow-up budget reached') && resumed.goal.reason_code !== 'host_budget_exhausted') {
|
|
726
|
+
const { retryHostVerification } = await import('./host-goal-engine.js');
|
|
727
|
+
return retryHostVerification(goalId, deps);
|
|
728
|
+
}
|
|
729
|
+
if (resumed.goal.status !== 'done' && !resumed.goal.reason_code?.startsWith('follow-up budget reached') && resumed.goal.reason_code !== 'host_budget_exhausted' && !resumed.goal.pending_follow_ups?.length && resumed.plan?.steps.every(s => ['done', 'skipped'].includes(s.status))) {
|
|
730
|
+
const { retryHostVerification } = await import('./host-goal-engine.js');
|
|
731
|
+
return retryHostVerification(goalId, deps);
|
|
732
|
+
}
|
|
733
|
+
return resumed;
|
|
734
|
+
}
|
|
735
|
+
if (options.budget_hours !== undefined || options.max_replans !== undefined)
|
|
736
|
+
goal = await recoverEngineGoalBudgets(goalId, options, deps);
|
|
678
737
|
if (plan) {
|
|
679
738
|
// A step left `running` means the engine died mid-step — reset it to planned
|
|
680
739
|
// (the supervisor's own revisit pattern) so it re-executes on resume.
|
|
@@ -705,6 +764,7 @@ export async function resumeGoal(goalId, deps = {}) {
|
|
|
705
764
|
goal.status = "executing";
|
|
706
765
|
goal.stop_requested = false;
|
|
707
766
|
goal.reason_code = undefined;
|
|
767
|
+
goal.failure_repetition = undefined;
|
|
708
768
|
d.repo.saveGoal(goal);
|
|
709
769
|
}
|
|
710
770
|
// `--plan-only` parks at `planning` with a compiled plan. Resuming is the
|
|
@@ -721,9 +781,94 @@ export async function resumeGoal(goalId, deps = {}) {
|
|
|
721
781
|
}
|
|
722
782
|
return runGoal(goalId, deps);
|
|
723
783
|
}
|
|
724
|
-
|
|
784
|
+
/** Operator-approved increases retain the original clock and all accepted work. */
|
|
785
|
+
export async function recoverEngineGoalBudgets(goalId, options, deps = {}) {
|
|
786
|
+
const d = resolveDeps(deps);
|
|
787
|
+
const { withStoreWriteCoordinator } = await import('./store/write-coordinator.js');
|
|
788
|
+
return withStoreWriteCoordinator(d.repo.planPath(goalId), async () => {
|
|
789
|
+
const goal = requireGoal(d, goalId);
|
|
790
|
+
if (goal.executor === 'host')
|
|
791
|
+
throw new Error('Use host budget recovery for a host goal.');
|
|
792
|
+
const hours = options.budget_hours, replans = options.max_replans;
|
|
793
|
+
if (hours !== undefined) {
|
|
794
|
+
if (!Number.isFinite(hours) || hours <= 0 || !Number.isSafeInteger(hours * 3600000))
|
|
795
|
+
throw new Error('budget_hours must yield a finite positive millisecond allowance.');
|
|
796
|
+
if (!goal.budgets.deadline_explicit)
|
|
797
|
+
throw new Error('Cannot impose a finite deadline on an unlimited goal through resume; no explicit deadline exists.');
|
|
798
|
+
if (hours * 3600000 < goal.budgets.max_wall_clock_ms)
|
|
799
|
+
throw new Error('Cannot lower budget_hours; resume only increases the total allowance.');
|
|
800
|
+
}
|
|
801
|
+
if (replans !== undefined) {
|
|
802
|
+
if (!Number.isSafeInteger(replans) || replans < 0)
|
|
803
|
+
throw new Error('max_replans must be a nonnegative safe integer.');
|
|
804
|
+
if (!goal.budgets.replan_budget_explicit)
|
|
805
|
+
throw new Error('Cannot replace an unlimited replan allowance with a finite allowance through resume.');
|
|
806
|
+
if (replans < goal.budgets.max_replans)
|
|
807
|
+
throw new Error('Cannot lower max_replans; resume only increases the total allowance.');
|
|
808
|
+
}
|
|
809
|
+
if (hours !== undefined)
|
|
810
|
+
goal.budgets.max_wall_clock_ms = hours * 3600000;
|
|
811
|
+
if (replans !== undefined)
|
|
812
|
+
goal.budgets.max_replans = replans;
|
|
813
|
+
return d.repo.saveGoal(goal);
|
|
814
|
+
}, { operation_label: 'engine_budget_recovery' });
|
|
815
|
+
}
|
|
816
|
+
async function withHostResume(d, goalId, options) {
|
|
817
|
+
const { withStoreWriteCoordinator } = await import('./store/write-coordinator.js');
|
|
818
|
+
return withStoreWriteCoordinator(d.repo.planPath(goalId), async () => {
|
|
819
|
+
const goal = requireGoal(d, goalId);
|
|
820
|
+
let plan = requirePlan(d, goal);
|
|
821
|
+
const { recoverHostBudgetsLocked } = await import('./host-goal-engine.js');
|
|
822
|
+
plan = recoverHostBudgetsLocked(goal, plan, options, d.now());
|
|
823
|
+
if (goal.status === 'done' || goal.reason_code?.startsWith('follow-up budget reached') || goal.reason_code === 'host_budget_exhausted') {
|
|
824
|
+
d.repo.savePlan(goalId, plan);
|
|
825
|
+
d.repo.saveGoal(goal);
|
|
826
|
+
return { goal, plan };
|
|
827
|
+
}
|
|
828
|
+
if (plan.steps.some(step => step.observation_check && step.observation_check.attempt === step.attempt && ['pending', 'unmet', 'unsupported'].includes(step.observation_check.status))) {
|
|
829
|
+
d.repo.savePlan(goalId, plan);
|
|
830
|
+
d.repo.saveGoal(goal);
|
|
831
|
+
return { goal, plan };
|
|
832
|
+
}
|
|
833
|
+
// retryHostVerification clears a stop and advances check identity together.
|
|
834
|
+
goal.failure_repetition = undefined;
|
|
835
|
+
if (goal.reason_code === 'repeated_failure')
|
|
836
|
+
goal.reason_code = undefined;
|
|
837
|
+
if (plan.steps.every(s => ['done', 'skipped'].includes(s.status))) {
|
|
838
|
+
d.repo.savePlan(goalId, plan);
|
|
839
|
+
d.repo.saveGoal(goal);
|
|
840
|
+
return { goal, plan };
|
|
841
|
+
}
|
|
842
|
+
// Explicit recovery permits a new attempt on blocked work; in-flight host work is never reset.
|
|
843
|
+
for (const step of plan.steps)
|
|
844
|
+
if (['blocked', 'failed'].includes(step.status) && !step.waiting_on?.startsWith('handoff:')) {
|
|
845
|
+
step.status = 'planned';
|
|
846
|
+
step.blocked_reason = undefined;
|
|
847
|
+
}
|
|
848
|
+
plan.status = deriveTaskPlanStatus(plan);
|
|
849
|
+
goal.status = 'executing';
|
|
850
|
+
goal.stop_requested = false;
|
|
851
|
+
goal.reason_code = undefined;
|
|
852
|
+
d.repo.savePlan(goalId, plan);
|
|
853
|
+
d.repo.saveGoal(goal);
|
|
854
|
+
return { goal, plan };
|
|
855
|
+
}, { operation_label: 'host_resume_goal' });
|
|
856
|
+
}
|
|
857
|
+
export { beginStep, completeStep, applyHostResultToStep } from './host-goal-engine.js';
|
|
858
|
+
export async function stopGoal(goalId, deps = {}) {
|
|
725
859
|
const d = resolveDeps(deps);
|
|
726
860
|
const goal = requireGoal(d, goalId);
|
|
861
|
+
if (goal.executor === 'host') {
|
|
862
|
+
const { withStoreWriteCoordinator } = await import('./store/write-coordinator.js');
|
|
863
|
+
return withStoreWriteCoordinator(d.repo.planPath(goalId), async () => {
|
|
864
|
+
const fresh = requireGoal(d, goalId);
|
|
865
|
+
if (fresh.status !== 'done') {
|
|
866
|
+
fresh.stop_requested = true;
|
|
867
|
+
fresh.status = 'stopped';
|
|
868
|
+
}
|
|
869
|
+
return d.repo.saveGoal(fresh);
|
|
870
|
+
}, { operation_label: 'host_stop_goal' });
|
|
871
|
+
}
|
|
727
872
|
goal.stop_requested = true;
|
|
728
873
|
return d.repo.saveGoal(goal);
|
|
729
874
|
}
|
|
@@ -777,15 +922,26 @@ function requirePlan(d, goal) {
|
|
|
777
922
|
return plan;
|
|
778
923
|
}
|
|
779
924
|
function budgetExhausted(d, goal) {
|
|
780
|
-
if (goal.tick_count >= goal.budgets.max_ticks)
|
|
925
|
+
if (goal.budgets.tick_budget_explicit && goal.tick_count >= goal.budgets.max_ticks)
|
|
781
926
|
return "max_ticks";
|
|
782
|
-
if (goal.started_at) {
|
|
927
|
+
if (goal.budgets.deadline_explicit && goal.started_at) {
|
|
783
928
|
const elapsed = d.now() - Date.parse(goal.started_at);
|
|
784
929
|
if (elapsed >= goal.budgets.max_wall_clock_ms)
|
|
785
930
|
return "max_wall_clock";
|
|
786
931
|
}
|
|
787
932
|
return undefined;
|
|
788
933
|
}
|
|
934
|
+
function observeFailure(goal, signature) {
|
|
935
|
+
const previous = goal.failure_repetition;
|
|
936
|
+
goal.failure_repetition = { signature, count: previous?.signature === signature ? previous.count + 1 : 1 };
|
|
937
|
+
return goal.failure_repetition.count >= 3;
|
|
938
|
+
}
|
|
939
|
+
/** Learning groups related failures; loop detection must also retain exact test identities. */
|
|
940
|
+
export function failureProgressSignature(text) {
|
|
941
|
+
const normalized = stripVTControlCharacters(text).replace(/\r\n/g, '\n')
|
|
942
|
+
.replace(/\b\d{4}-\d\d-\d\d[T ][\d:.]+Z?\b/g, '<time>').trim();
|
|
943
|
+
return `${failureSignature(normalized) ?? 'failure'}:${createHash('sha256').update(normalized).digest('hex')}`;
|
|
944
|
+
}
|
|
789
945
|
function blockedReasons(plan) {
|
|
790
946
|
return plan.steps
|
|
791
947
|
.filter((s) => s.status === "blocked" || s.status === "failed")
|
|
@@ -6,6 +6,10 @@ export interface HandoffLifecycleEvent {
|
|
|
6
6
|
note?: string;
|
|
7
7
|
}
|
|
8
8
|
export interface HandoffRecord {
|
|
9
|
+
step_ref?: {
|
|
10
|
+
goal_id: string;
|
|
11
|
+
step_id: string;
|
|
12
|
+
};
|
|
9
13
|
handoff_id: string;
|
|
10
14
|
created_at: string;
|
|
11
15
|
updated_at: string;
|
|
@@ -24,6 +28,10 @@ export interface HandoffRegistryFile {
|
|
|
24
28
|
handoffs: Record<string, HandoffRecord>;
|
|
25
29
|
}
|
|
26
30
|
export interface RegisterHandoffInput {
|
|
31
|
+
step_ref?: {
|
|
32
|
+
goal_id: string;
|
|
33
|
+
step_id: string;
|
|
34
|
+
};
|
|
27
35
|
handoff_id: string;
|
|
28
36
|
from: string;
|
|
29
37
|
to: string;
|
|
@@ -68,4 +76,6 @@ export declare function registerHandoffSafe(input: RegisterHandoffInput): Promis
|
|
|
68
76
|
* Concurrency-safe variant of acknowledgeHandoff.
|
|
69
77
|
*/
|
|
70
78
|
export declare function acknowledgeHandoffSafe(input: AcknowledgeHandoffInput): Promise<HandoffAckResult>;
|
|
79
|
+
/** Called after the goal plan is saved; this packed record is a repairable view. */
|
|
80
|
+
export declare function syncLinkedHandoffStepSafe(id: string, summary: string): Promise<void>;
|
|
71
81
|
//# sourceMappingURL=handoff-registry.d.ts.map
|
package/dist/handoff-registry.js
CHANGED
|
@@ -68,6 +68,7 @@ function parseRegistry(raw) {
|
|
|
68
68
|
task_type: typeof row.task_type === "string" ? row.task_type : undefined,
|
|
69
69
|
priority: typeof row.priority === "string" ? row.priority : undefined,
|
|
70
70
|
source_file: typeof row.source_file === "string" ? row.source_file : undefined,
|
|
71
|
+
step_ref: row.step_ref,
|
|
71
72
|
status: row.status === "open" ||
|
|
72
73
|
row.status === "accepted" ||
|
|
73
74
|
row.status === "blocked" ||
|
|
@@ -168,6 +169,7 @@ function toLegacyHandoffRecord(record) {
|
|
|
168
169
|
task_type: typeof metadata.task_type === "string" ? metadata.task_type : record.task,
|
|
169
170
|
priority: typeof metadata.priority === "string" ? metadata.priority : undefined,
|
|
170
171
|
source_file: typeof metadata.source_file === "string" ? metadata.source_file : undefined,
|
|
172
|
+
step_ref: metadata.step_ref,
|
|
171
173
|
status,
|
|
172
174
|
history: normalizeHistory(metadata.history, status, updatedAt),
|
|
173
175
|
};
|
|
@@ -229,6 +231,7 @@ export function registerHandoff(input) {
|
|
|
229
231
|
task_type: input.task_type,
|
|
230
232
|
priority: input.priority,
|
|
231
233
|
source_file: input.source_file,
|
|
234
|
+
step_ref: input.step_ref,
|
|
232
235
|
status: "open",
|
|
233
236
|
history: [
|
|
234
237
|
{
|
|
@@ -291,6 +294,13 @@ export function getHandoffRegistryPath() {
|
|
|
291
294
|
* Concurrency-safe variant of registerHandoff.
|
|
292
295
|
*/
|
|
293
296
|
export async function registerHandoffSafe(input) {
|
|
297
|
+
if (input.step_ref) {
|
|
298
|
+
const { withLinkedHandoff } = await import('./host-goal-engine.js');
|
|
299
|
+
return withLinkedHandoff(input.step_ref, { id: input.handoff_id, status: 'open', actor: input.actor ?? input.from, to: input.to }, () => registerHandoffStoreSafe(input));
|
|
300
|
+
}
|
|
301
|
+
return registerHandoffStoreSafe(input);
|
|
302
|
+
}
|
|
303
|
+
async function registerHandoffStoreSafe(input) {
|
|
294
304
|
const root = workspaceRoot();
|
|
295
305
|
const storePath = getWorkspaceStorePath(root);
|
|
296
306
|
await waitForTodoStoreMirror();
|
|
@@ -325,6 +335,7 @@ export async function registerHandoffSafe(input) {
|
|
|
325
335
|
task_type: input.task_type,
|
|
326
336
|
priority: input.priority,
|
|
327
337
|
source_file: input.source_file,
|
|
338
|
+
step_ref: input.step_ref,
|
|
328
339
|
history: [
|
|
329
340
|
{
|
|
330
341
|
timestamp_utc: now,
|
|
@@ -353,6 +364,34 @@ export async function registerHandoffSafe(input) {
|
|
|
353
364
|
* Concurrency-safe variant of acknowledgeHandoff.
|
|
354
365
|
*/
|
|
355
366
|
export async function acknowledgeHandoffSafe(input) {
|
|
367
|
+
const record = readHandoffRegistry().handoffs[input.handoff_id];
|
|
368
|
+
if (record?.step_ref) {
|
|
369
|
+
const { withLinkedHandoff, completeStep } = await import('./host-goal-engine.js');
|
|
370
|
+
const actor = input.actor;
|
|
371
|
+
if (!actor || actor !== record.to)
|
|
372
|
+
return { ok: false, path: getHandoffRegistryPath(), error: `Linked handoff belongs to ${record.to}; supply that agent label.` };
|
|
373
|
+
if (input.status === 'completed') {
|
|
374
|
+
if (record.status === 'completed')
|
|
375
|
+
return acknowledgeHandoffStoreSafe(input);
|
|
376
|
+
const { GoalRepository } = await import('./store/repositories/goal-repository.js');
|
|
377
|
+
const step = new GoalRepository(workspaceRoot()).getPlan(record.step_ref.goal_id)?.steps.find(s => s.step_id === record.step_ref.step_id);
|
|
378
|
+
if (!step)
|
|
379
|
+
return { ok: false, path: getHandoffRegistryPath(), error: 'Linked step missing' };
|
|
380
|
+
const result = await completeStep(record.step_ref.goal_id, record.step_ref.step_id, { status: 'done', summary: input.note || record.title, evidence: [{ ref: `handoff:${record.handoff_id}`, kind: 'note', detail: input.note }] }, { agent: actor, attempt: step.attempt ?? 0 });
|
|
381
|
+
if (!result.ok || result.status !== 'done')
|
|
382
|
+
return { ok: false, path: getHandoffRegistryPath(), error: result.message ?? `Step retained as ${result.status}: ${result.summary}` };
|
|
383
|
+
return acknowledgeHandoffStoreSafe(input);
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
return await withLinkedHandoff(record.step_ref, { id: record.handoff_id, status: input.status, actor, to: record.to }, () => acknowledgeHandoffStoreSafe(input));
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
return { ok: false, path: getHandoffRegistryPath(), error: error instanceof Error ? error.message : String(error) };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return acknowledgeHandoffStoreSafe(input);
|
|
393
|
+
}
|
|
394
|
+
async function acknowledgeHandoffStoreSafe(input) {
|
|
356
395
|
const root = workspaceRoot();
|
|
357
396
|
const storePath = getWorkspaceStorePath(root);
|
|
358
397
|
await waitForTodoStoreMirror();
|
|
@@ -423,4 +462,24 @@ export async function acknowledgeHandoffSafe(input) {
|
|
|
423
462
|
}, { operation_label: "acknowledgeHandoffSafe" });
|
|
424
463
|
});
|
|
425
464
|
}
|
|
465
|
+
/** Called after the goal plan is saved; this packed record is a repairable view. */
|
|
466
|
+
export async function syncLinkedHandoffStepSafe(id, summary) {
|
|
467
|
+
const root = workspaceRoot(), path = getWorkspaceStorePath(root);
|
|
468
|
+
await withStoreWriteCoordinator(path, async () => {
|
|
469
|
+
const store = await openStore(path);
|
|
470
|
+
try {
|
|
471
|
+
const repo = new HandoffRepository(store), record = await repo.get(id);
|
|
472
|
+
if (!record)
|
|
473
|
+
throw new Error(`Linked handoff missing: ${id}`);
|
|
474
|
+
if (record.status !== 'closed') {
|
|
475
|
+
await repo.update(id, { status: 'closed', metadata: { ...record.metadata, legacy_status: 'completed', history: [...(record.metadata?.history ?? []), { timestamp_utc: new Date().toISOString(), status: 'completed', actor: 'goal-step', note: summary }] } });
|
|
476
|
+
await store.commit();
|
|
477
|
+
await new ProjectionManager(store, root).projectAfterCommit(['handoffs']);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
finally {
|
|
481
|
+
await store.close({ commit: false });
|
|
482
|
+
}
|
|
483
|
+
}, { operation_label: 'linked_handoff_view' });
|
|
484
|
+
}
|
|
426
485
|
//# sourceMappingURL=handoff-registry.js.map
|
|
@@ -6,7 +6,9 @@ export interface SkillReference {
|
|
|
6
6
|
}
|
|
7
7
|
export declare function getAgentInstructionPath(role: AgentRole): string | undefined;
|
|
8
8
|
export declare function getAgentManifestPath(role: AgentRole): string | undefined;
|
|
9
|
-
export declare function readAgentInstructions(role: AgentRole
|
|
9
|
+
export declare function readAgentInstructions(role: AgentRole, options?: {
|
|
10
|
+
includeLearned?: boolean;
|
|
11
|
+
}): string;
|
|
10
12
|
export declare function readAgentManifest(role: AgentRole): string;
|
|
11
13
|
export declare function getTaskArtifactPath(key: TaskKey): string | undefined;
|
|
12
14
|
export declare function readTaskArtifact(key: TaskKey): string;
|
|
@@ -15,5 +17,7 @@ export declare function readKernelArtifact(key: KernelKey): string;
|
|
|
15
17
|
export declare function resolveWritableTaskPath(key: TaskKey): string;
|
|
16
18
|
export declare function listAvailableSkills(): SkillReference[];
|
|
17
19
|
export declare function getSkillPath(name: string): string | undefined;
|
|
18
|
-
export declare function readSkillInstructions(name: string
|
|
20
|
+
export declare function readSkillInstructions(name: string, options?: {
|
|
21
|
+
includeLearned?: boolean;
|
|
22
|
+
}): string;
|
|
19
23
|
//# sourceMappingURL=artifacts.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { approvedInstructionText, approvedDistillerText } from '../learning-overlays.js';
|
|
1
2
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { AGENT_FILES, AGENT_MANIFEST_FILES, DEFAULT_AGENT_INSTRUCTION_FILES, DEFAULT_AGENT_MANIFEST_FILES, DEFAULT_KERNEL_FILES, DEFAULTS_SKILLS_ROOT, DEFAULT_TASK_FILES, KERNEL_FILES, STORE_AGENT_FILES, STORE_KERNEL_FILES, STORE_TASK_FILES, TASK_FILES, } from "./constants.js";
|
|
@@ -30,12 +31,13 @@ export function getAgentManifestPath(role) {
|
|
|
30
31
|
const workspaceCandidates = toAbsoluteWorkspaceCandidates(AGENT_MANIFEST_FILES[role]);
|
|
31
32
|
return firstExistingPath([...workspaceCandidates, DEFAULT_AGENT_MANIFEST_FILES[role]]);
|
|
32
33
|
}
|
|
33
|
-
export function readAgentInstructions(role) {
|
|
34
|
+
export function readAgentInstructions(role, options = {}) {
|
|
34
35
|
const store = readStoreText(STORE_AGENT_FILES[role].instructions.map((file) => `knowledge/agents/${STORE_AGENT_FILES[role].agent}/${file}`));
|
|
36
|
+
const overlay = options.includeLearned ? approvedInstructionText(currentWorkspaceRoot(), role) : "";
|
|
35
37
|
if (store)
|
|
36
|
-
return store.text;
|
|
38
|
+
return store.text + overlay;
|
|
37
39
|
const workspaceCandidates = toAbsoluteWorkspaceCandidates(AGENT_FILES[role]);
|
|
38
|
-
return readFirstAvailable([...workspaceCandidates, DEFAULT_AGENT_INSTRUCTION_FILES[role]]).text;
|
|
40
|
+
return readFirstAvailable([...workspaceCandidates, DEFAULT_AGENT_INSTRUCTION_FILES[role]]).text + overlay;
|
|
39
41
|
}
|
|
40
42
|
export function readAgentManifest(role) {
|
|
41
43
|
const store = readStoreText(STORE_AGENT_FILES[role].manifests.map((file) => `knowledge/agents/${STORE_AGENT_FILES[role].agent}/${file}`));
|
|
@@ -136,7 +138,7 @@ export function getSkillPath(name) {
|
|
|
136
138
|
const exact = skills.find((skill) => skill.name.toLowerCase() === target);
|
|
137
139
|
return exact?.path;
|
|
138
140
|
}
|
|
139
|
-
export function readSkillInstructions(name) {
|
|
141
|
+
export function readSkillInstructions(name, options = {}) {
|
|
140
142
|
const target = name.trim().toLowerCase();
|
|
141
143
|
const match = listAvailableSkills().find((skill) => skill.name.toLowerCase() === target);
|
|
142
144
|
if (!match) {
|
|
@@ -145,8 +147,8 @@ export function readSkillInstructions(name) {
|
|
|
145
147
|
.join(", ")}`;
|
|
146
148
|
}
|
|
147
149
|
if (match.source === "store") {
|
|
148
|
-
return readStoreText([`knowledge/skills/${match.name}/SKILL.md`])?.text ?? "";
|
|
150
|
+
return (readStoreText([`knowledge/skills/${match.name}/SKILL.md`])?.text ?? "") + (options.includeLearned && target === 'learn-from-work' ? approvedDistillerText(currentWorkspaceRoot()) : '');
|
|
149
151
|
}
|
|
150
|
-
return readText(match.path);
|
|
152
|
+
return readText(match.path) + (options.includeLearned && target === 'learn-from-work' ? approvedDistillerText(currentWorkspaceRoot()) : '');
|
|
151
153
|
}
|
|
152
154
|
//# sourceMappingURL=artifacts.js.map
|