@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/tools-goal.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ACE Goal Engine tool registrations (WS-F).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* goal: they compile/prepare, then hand execution to a detached `ace goal run`
|
|
7
|
-
* child and return immediately. The host polls `get_goal_status`.
|
|
4
|
+
* Engine goals delegate to the existing detached runner. Host goals return
|
|
5
|
+
* assignments to the connected participant and never invoke that executor.
|
|
8
6
|
*/
|
|
9
7
|
import { spawn } from "node:child_process";
|
|
10
8
|
import { fileURLToPath } from "node:url";
|
|
11
9
|
import { z } from "zod";
|
|
12
|
-
import { getGoalStatus, listGoals, startGoal, stopGoal, } from "./goal-engine.js";
|
|
10
|
+
import { getGoalStatus, listGoals, startGoal, stopGoal, resumeGoal, beginStep, completeStep, } from "./goal-engine.js";
|
|
11
|
+
import { hostRefusal } from './host-goal-engine.js';
|
|
12
|
+
import { resolveCurrentWork } from './ace-autonomy.js';
|
|
13
|
+
import { resolveWorkspaceRoot } from './helpers.js';
|
|
14
|
+
import { bindHostWork, hostAgent } from './host-work-context.js';
|
|
15
|
+
import { getCompactGoalStatus } from './host-goal-status.js';
|
|
16
|
+
import { commandPolicySchema, withMcpCommandProgress } from './mcp-command-progress.js';
|
|
17
|
+
function hostResult(result) {
|
|
18
|
+
return { isError: !!result.isError, structuredContent: { ...result }, content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
19
|
+
}
|
|
20
|
+
const artifactSchema = z.object({ path: z.string().min(1), required: z.boolean().optional(), transient: z.boolean().optional() });
|
|
21
|
+
const hostStepSchema = z.object({ id: z.string().optional(), task: z.string().min(1), role: z.string().optional(), depends_on: z.array(z.string()).optional(), expected_artifacts: z.array(z.union([z.string().min(1), artifactSchema])).optional(), required_evidence_refs: z.array(z.string()).optional(), required_observation: z.enum(['action', 'command_pass']).optional() });
|
|
13
22
|
function asJsonBlock(value) {
|
|
14
23
|
return `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
|
|
15
24
|
}
|
|
@@ -29,14 +38,34 @@ function spawnDetachedRunner(goalId, verb) {
|
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
export function registerGoalTools(server) {
|
|
32
|
-
server.tool("start_goal", "
|
|
41
|
+
server.tool("start_goal", "Start a goal with an explicit executor choice: host accepts your steps and returns the first assignment; engine plans and runs through ACE's autonomous executor. Productive work has no default duration, tick, or total replan limit.", {
|
|
42
|
+
executor: z.enum(['engine', 'host']),
|
|
43
|
+
agent: z.string().min(1).optional(),
|
|
44
|
+
steps: z.array(hostStepSchema).min(1).optional(),
|
|
45
|
+
verify: z.array(z.union([z.string().min(1), commandPolicySchema.extend({ cmd: z.string().min(1) })])).optional(),
|
|
46
|
+
todo_ids: z.record(z.string(), z.string()).optional(),
|
|
33
47
|
objective: z.string().min(1).describe("What the goal should accomplish"),
|
|
34
|
-
budget_hours: z.number().positive().
|
|
35
|
-
max_replans: z.number().int().min(0).
|
|
48
|
+
budget_hours: z.number().positive().optional().describe("Explicit operator deadline measured from the original start; omit for unlimited work."),
|
|
49
|
+
max_replans: z.number().int().min(0).optional().describe("Explicit operator cap on corrections; omitted by default. Repeated unchanged failures are detected separately."),
|
|
36
50
|
plan_only: z.boolean().optional().describe("Compile + validate the plan but do not execute"),
|
|
37
51
|
detach: z.boolean().optional().describe("Run in a detached background child (default true when not plan_only)"),
|
|
38
|
-
}, async ({ objective, budget_hours, max_replans, plan_only, detach }) => {
|
|
39
|
-
const
|
|
52
|
+
}, async ({ objective, budget_hours, max_replans, plan_only, detach, executor, agent, steps, verify, todo_ids }, extra) => {
|
|
53
|
+
const participant = hostAgent(agent, extra.sessionId);
|
|
54
|
+
let started;
|
|
55
|
+
try {
|
|
56
|
+
if (budget_hours !== undefined && !Number.isFinite(budget_hours * 3600000))
|
|
57
|
+
throw new Error('budget_hours must be a finite positive allowance.');
|
|
58
|
+
started = await startGoal(objective, { budget_hours, max_replans, plan_only, executor, agent: participant, session_id: extra.sessionId, steps, verify_commands: verify, todo_ids });
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (executor !== 'host')
|
|
62
|
+
throw error;
|
|
63
|
+
return hostResult(hostRefusal(resolveWorkspaceRoot(), 'host_plan_invalid', error instanceof Error ? error.message : String(error)));
|
|
64
|
+
}
|
|
65
|
+
if (executor === 'host') {
|
|
66
|
+
bindHostWork({ agent: participant, goal_id: started.goal.goal_id, step_id: started.assignment?.step_id, attempt: started.assignment?.attempt }, extra.sessionId);
|
|
67
|
+
return { isError: !started.ok, structuredContent: { ...started }, content: [{ type: 'text', text: JSON.stringify(started, null, 2) }] };
|
|
68
|
+
}
|
|
40
69
|
let runnerPid;
|
|
41
70
|
if (started.ok && !plan_only && detach !== false) {
|
|
42
71
|
runnerPid = spawnDetachedRunner(started.goal.goal_id, "run");
|
|
@@ -59,25 +88,75 @@ export function registerGoalTools(server) {
|
|
|
59
88
|
],
|
|
60
89
|
};
|
|
61
90
|
});
|
|
62
|
-
server.tool(
|
|
91
|
+
server.tool('next_step', 'Receive or resume your current assignment from a host goal. Never executes work itself.', {
|
|
92
|
+
goal_id: z.string().optional(), step_id: z.string().optional(), agent: z.string().min(1).optional(),
|
|
93
|
+
}, async ({ goal_id, step_id, agent }, extra) => {
|
|
94
|
+
const participant = hostAgent(agent, extra.sessionId);
|
|
95
|
+
const work = resolveCurrentWork({ goal_id, agent: participant });
|
|
96
|
+
const id = goal_id ?? work.objective?.goal_id ?? (work.idle_learning?.includes('learning step available') ? 'G-learning' : undefined);
|
|
97
|
+
if (!id)
|
|
98
|
+
return hostResult({ ...hostRefusal(resolveWorkspaceRoot(), work.state === 'ambiguous' ? 'ambiguous_goal' : 'no_active_goal', 'Select a host goal_id, or create one with start_goal executor:host.'), candidates: work.other_goals });
|
|
99
|
+
try {
|
|
100
|
+
const result = await beginStep(id, { step_id, agent: participant, session_id: extra.sessionId });
|
|
101
|
+
if (result.assignment)
|
|
102
|
+
bindHostWork({ agent: participant, goal_id: id, step_id: result.assignment.step_id, attempt: result.assignment.attempt }, extra.sessionId);
|
|
103
|
+
return hostResult(result);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return hostResult(hostRefusal(resolveWorkspaceRoot(), 'next_step_failed', error instanceof Error ? error.message : String(error), id));
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
server.tool('complete_step', 'Record this attempt once, update linked state and optionally receive the next assignment. Identical retries return the original response.', {
|
|
110
|
+
goal_id: z.string(), step_id: z.string(), attempt: z.number().int().positive(), agent: z.string().min(1).optional(), continue: z.boolean().optional(),
|
|
111
|
+
outcome: z.object({ status: z.enum(['done', 'blocked', 'failed']), summary: z.string().min(1).max(2000), waiting_on: z.string().optional(), artifacts: z.array(z.string()).optional(),
|
|
112
|
+
feedback: z.array(z.object({ id: z.string(), helpful: z.boolean() })).optional(),
|
|
113
|
+
learned: z.object({
|
|
114
|
+
lessons: z.array(z.object({ op: z.enum(['add', 'edit', 'retire']), bullet_id: z.string().optional(), section: z.enum(['strategies', 'tool_usage', 'failure_modes', 'verification', 'env_facts']), content: z.string().max(400), scope: z.object({ role: z.string().optional(), paths: z.array(z.string()).optional(), task_tags: z.array(z.string()).optional(), failure_signature: z.string().optional() }), episodes: z.array(z.string()).min(1), retired_episodes: z.array(z.string()).optional() })).max(3).optional(),
|
|
115
|
+
skill: z.object({ op: z.enum(['add', 'edit']), slug: z.string(), skill_md: z.string(), references: z.record(z.string(), z.string()).optional(), episodes: z.array(z.string()).min(3), retired_episodes: z.array(z.string()).optional() }).optional(),
|
|
116
|
+
instruction: z.object({ role: z.string(), text: z.string().max(1200), episodes: z.array(z.string()).min(1) }).optional()
|
|
117
|
+
}).optional(),
|
|
118
|
+
requirement_updates: z.array(z.object({ step_id: z.string(), reason: z.string().min(1), task: z.string().min(1).optional(), expected_artifacts: z.array(artifactSchema).optional(), required_evidence_refs: z.array(z.string()).optional() })).optional(),
|
|
119
|
+
evidence: z.array(z.object({ ref: z.string(), kind: z.enum(['command', 'artifact', 'diff', 'note']), detail: z.string().optional() })).optional(),
|
|
120
|
+
follow_ups: z.array(z.object({ task: z.string().min(1), role: z.string().optional(), depends_on: z.array(z.string()).optional() })).optional() }),
|
|
121
|
+
}, async ({ goal_id, step_id, attempt, agent, outcome, continue: proceed }, extra) => {
|
|
122
|
+
const participant = hostAgent(agent, extra.sessionId);
|
|
123
|
+
try {
|
|
124
|
+
const result = await withMcpCommandProgress(extra, 'complete_step acceptance', () => completeStep(goal_id, step_id, outcome, { agent: participant, attempt, continue: proceed }));
|
|
125
|
+
if (result.ok)
|
|
126
|
+
bindHostWork({ agent: participant, goal_id, step_id: result.assignment?.step_id, attempt: result.assignment?.attempt }, extra.sessionId);
|
|
127
|
+
return hostResult(result);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return hostResult(hostRefusal(resolveWorkspaceRoot(), 'completion_failed', error instanceof Error ? error.message : String(error), goal_id));
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
server.tool("get_goal_status", "Get goal status. view:compact gives bounded verification, blockers and executable next actions; view:full (default) retains the complete plan and evidence. Omit goal_id to list goals.", {
|
|
63
134
|
goal_id: z.string().optional().describe("Goal id; omit to list all goals"),
|
|
64
|
-
|
|
135
|
+
view: z.enum(['compact', 'full']).optional(),
|
|
136
|
+
agent: z.string().min(1).optional(),
|
|
137
|
+
}, async ({ goal_id, view: mode, agent }, extra) => {
|
|
65
138
|
if (!goal_id) {
|
|
66
139
|
return {
|
|
67
140
|
content: [{ type: "text", text: asJsonBlock({ goals: listGoals() }) }],
|
|
68
141
|
};
|
|
69
142
|
}
|
|
143
|
+
if (mode === 'compact') {
|
|
144
|
+
const compact = getCompactGoalStatus(goal_id, hostAgent(agent, extra.sessionId));
|
|
145
|
+
if (!compact)
|
|
146
|
+
return hostResult(hostRefusal(resolveWorkspaceRoot(), 'goal_not_found', `Unknown goal: ${goal_id}`, goal_id));
|
|
147
|
+
return { structuredContent: { ...compact }, content: [{ type: 'text', text: JSON.stringify(compact) }] };
|
|
148
|
+
}
|
|
70
149
|
const view = getGoalStatus(goal_id);
|
|
71
150
|
if (!view) {
|
|
72
151
|
return { content: [{ type: "text", text: `❌ Unknown goal: ${goal_id}` }] };
|
|
73
152
|
}
|
|
74
|
-
return { content: [{ type: "text", text: asJsonBlock(view) }] };
|
|
153
|
+
return { structuredContent: { ...view }, content: [{ type: "text", text: asJsonBlock(view) }] };
|
|
75
154
|
});
|
|
76
155
|
server.tool("stop_goal", "Request that a running goal stop at its next tick boundary.", { goal_id: z.string().describe("Goal id to stop") }, async ({ goal_id }) => {
|
|
77
156
|
try {
|
|
78
|
-
const goal = stopGoal(goal_id);
|
|
157
|
+
const goal = await stopGoal(goal_id);
|
|
79
158
|
return {
|
|
80
|
-
content: [{ type: "text", text: `🛑 Stop requested for ${goal.goal_id} (status ${goal.status})
|
|
159
|
+
content: [{ type: "text", text: `🛑 Stop requested for ${goal.goal_id} (status ${goal.status}).\n` }],
|
|
81
160
|
};
|
|
82
161
|
}
|
|
83
162
|
catch (err) {
|
|
@@ -86,11 +165,35 @@ export function registerGoalTools(server) {
|
|
|
86
165
|
};
|
|
87
166
|
}
|
|
88
167
|
});
|
|
89
|
-
server.tool("resume_goal", "Resume a
|
|
168
|
+
server.tool("resume_goal", "Resume a goal. budget_hours/max_replans increase explicit total allowances only with operator approval; never execute a status proposal automatically. Accepted work and original start time are preserved. Engine goals resume in the detached runner.", { goal_id: z.string().describe("Goal id to resume"),
|
|
169
|
+
budget_hours: z.number().positive().optional().describe('Operator-approved increase to an explicit total deadline from the original start.'),
|
|
170
|
+
max_replans: z.number().int().nonnegative().optional().describe('Operator-approved increase admitting retained pending follow-ups.') }, async ({ goal_id, budget_hours, max_replans }, extra) => {
|
|
90
171
|
const view = getGoalStatus(goal_id);
|
|
91
172
|
if (!view) {
|
|
92
173
|
return { content: [{ type: "text", text: `❌ Unknown goal: ${goal_id}` }] };
|
|
93
174
|
}
|
|
175
|
+
if (view.goal.executor === 'host') {
|
|
176
|
+
try {
|
|
177
|
+
const resumed = await resumeGoal(goal_id, {}, { budget_hours, max_replans });
|
|
178
|
+
const status = getCompactGoalStatus(goal_id, hostAgent(undefined, extra.sessionId));
|
|
179
|
+
const result = { ...resumed, options: status?.options, operator_proposal: status?.operator_proposal, waiting_on: status?.waiting_on, now: status?.now };
|
|
180
|
+
return { structuredContent: result, content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
return hostResult(hostRefusal(resolveWorkspaceRoot(), 'budget_recovery_rejected', error instanceof Error ? error.message : String(error), goal_id));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (budget_hours !== undefined || max_replans !== undefined) {
|
|
187
|
+
try {
|
|
188
|
+
const { recoverEngineGoalBudgets } = await import('./goal-engine.js');
|
|
189
|
+
await recoverEngineGoalBudgets(goal_id, { budget_hours, max_replans });
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
const refusal = hostRefusal(resolveWorkspaceRoot(), 'budget_recovery_rejected', error instanceof Error ? error.message : String(error), goal_id);
|
|
193
|
+
refusal.options = [{ tool: 'get_goal_status', args: { goal_id, view: 'compact' }, why: 'Inspect the retained engine allowance and obtain operator approval for any sufficient increase.' }];
|
|
194
|
+
return hostResult(refusal);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
94
197
|
const runnerPid = spawnDetachedRunner(goal_id, "resume");
|
|
95
198
|
return {
|
|
96
199
|
content: [
|
package/dist/tools-handoff.d.ts
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { HandoffRecord } from "./handoff-registry.js";
|
|
6
|
-
import { DispatchJobsResult } from "./job-scheduler.js";
|
|
7
6
|
export interface HandoffSchedulerSyncResult {
|
|
8
7
|
ok: boolean;
|
|
9
8
|
job_id: string;
|
|
10
9
|
enqueued: boolean;
|
|
11
10
|
enqueue_reason?: string;
|
|
12
|
-
|
|
11
|
+
dispatch_performed: false;
|
|
12
|
+
reconciliation_required: boolean;
|
|
13
|
+
reconciliation_reason?: string;
|
|
13
14
|
}
|
|
14
15
|
export interface HandoffSchedulerBackfillResult {
|
|
15
16
|
scanned: number;
|
package/dist/tools-handoff.js
CHANGED
|
@@ -9,10 +9,12 @@ import { ROLE_ENUM, HANDOFF_VALIDATION_MODE, HANDOFF_LIFECYCLE_STATUS_ENUM, } fr
|
|
|
9
9
|
import { validateHandoffPayload, validateSwarmHandoffPayload, lintHandoffPayload, } from "./schemas.js";
|
|
10
10
|
import { acknowledgeHandoffSafe, getHandoffRegistryPath, readHandoffRegistry, registerHandoffSafe, } from "./handoff-registry.js";
|
|
11
11
|
import { appendRunLedgerEntrySafe } from "./run-ledger.js";
|
|
12
|
-
import { refreshKanbanArtifacts } from "./kanban.js";
|
|
13
|
-
import {
|
|
12
|
+
import { formatKanbanRefresh, refreshKanbanArtifacts } from "./kanban.js";
|
|
13
|
+
import { readJobQueue, syncHandoffLifecycleJob } from "./job-scheduler.js";
|
|
14
14
|
import { appendStatusEventSafe } from "./status-events.js";
|
|
15
|
-
|
|
15
|
+
import { hostAgent, bindHostWork } from './host-work-context.js';
|
|
16
|
+
import { GoalRepository } from './store/repositories/goal-repository.js';
|
|
17
|
+
import { resolveWorkspaceRoot } from './helpers.js';
|
|
16
18
|
function toSchedulerPriority(input) {
|
|
17
19
|
if (input === "P0" || input === "P1" || input === "P2" || input === "P3") {
|
|
18
20
|
return input;
|
|
@@ -62,9 +64,11 @@ async function recordHandoffSchedulerSyncEffects(record, sync) {
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
export async function syncHandoffIntoScheduler(record, options = {}) {
|
|
65
|
-
|
|
67
|
+
if (record.step_ref)
|
|
68
|
+
return { ok: true, job_id: '', enqueued: false, enqueue_reason: 'Host step ownership follows the handoff; no engine dispatch.', dispatch_performed: false, reconciliation_required: false };
|
|
66
69
|
const jobId = handoffSchedulerJobId(record.handoff_id);
|
|
67
|
-
const enqueue = await
|
|
70
|
+
const enqueue = await syncHandoffLifecycleJob({
|
|
71
|
+
now_iso: options.now_iso,
|
|
68
72
|
job_id: jobId,
|
|
69
73
|
kind: "handoff.lifecycle",
|
|
70
74
|
priority: toSchedulerPriority(record.priority),
|
|
@@ -85,28 +89,31 @@ export async function syncHandoffIntoScheduler(record, options = {}) {
|
|
|
85
89
|
],
|
|
86
90
|
resource_requirements: buildHandoffResources(record),
|
|
87
91
|
});
|
|
88
|
-
const enqueued = enqueue.
|
|
92
|
+
const enqueued = enqueue.enqueued;
|
|
89
93
|
const enqueueReason = enqueue.ok || !enqueue.nack
|
|
90
94
|
? undefined
|
|
91
95
|
: `${enqueue.nack.reason_code}: ${enqueue.nack.reason}`;
|
|
92
|
-
const tick = await dispatchJobs({
|
|
93
|
-
owner,
|
|
94
|
-
...(options.now_iso ? { now_iso: options.now_iso } : {}),
|
|
95
|
-
});
|
|
96
96
|
const syncResult = {
|
|
97
|
-
ok: enqueue.ok
|
|
97
|
+
ok: enqueue.ok,
|
|
98
98
|
job_id: jobId,
|
|
99
99
|
enqueued,
|
|
100
100
|
enqueue_reason: enqueueReason,
|
|
101
|
-
|
|
101
|
+
dispatch_performed: false,
|
|
102
|
+
reconciliation_required: enqueue.ack?.status === "unknown_recovery",
|
|
103
|
+
reconciliation_reason: enqueue.ack?.status === "unknown_recovery"
|
|
104
|
+
? enqueue.queue.jobs.find((job) => job.job_id === jobId)?.error
|
|
105
|
+
: undefined,
|
|
102
106
|
};
|
|
103
107
|
await recordHandoffSchedulerSyncEffects(record, syncResult);
|
|
104
108
|
return syncResult;
|
|
105
109
|
}
|
|
106
110
|
export async function backfillHandoffsIntoScheduler(options = {}) {
|
|
107
111
|
const registry = readHandoffRegistry();
|
|
112
|
+
const existing = new Set(readJobQueue().jobs.filter((job) => job.kind === "handoff.lifecycle")
|
|
113
|
+
.map((job) => job.payload.handoff_id));
|
|
108
114
|
const active = Object.values(registry.handoffs)
|
|
109
|
-
.filter(
|
|
115
|
+
.filter(record => !record.step_ref)
|
|
116
|
+
.filter((record) => record.status !== "completed" || existing.has(record.handoff_id))
|
|
110
117
|
.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
|
|
111
118
|
const result = {
|
|
112
119
|
scanned: active.length,
|
|
@@ -117,16 +124,15 @@ export async function backfillHandoffsIntoScheduler(options = {}) {
|
|
|
117
124
|
for (const record of active) {
|
|
118
125
|
try {
|
|
119
126
|
const sync = await syncHandoffIntoScheduler(record, options);
|
|
127
|
+
if (sync.enqueued)
|
|
128
|
+
result.enqueued += 1;
|
|
120
129
|
if (sync.ok) {
|
|
121
130
|
result.synced += 1;
|
|
122
|
-
if (sync.enqueued) {
|
|
123
|
-
result.enqueued += 1;
|
|
124
|
-
}
|
|
125
131
|
}
|
|
126
|
-
|
|
132
|
+
if (!sync.ok || sync.reconciliation_required) {
|
|
127
133
|
result.failures.push({
|
|
128
134
|
handoff_id: record.handoff_id,
|
|
129
|
-
error: sync.enqueue_reason ?? "scheduler sync returned not-ok",
|
|
135
|
+
error: sync.reconciliation_reason ?? sync.enqueue_reason ?? "scheduler sync returned not-ok",
|
|
130
136
|
});
|
|
131
137
|
}
|
|
132
138
|
}
|
|
@@ -161,11 +167,15 @@ export function registerHandoffTools(server) {
|
|
|
161
167
|
})
|
|
162
168
|
.optional()
|
|
163
169
|
.describe("File:line pointers for context"),
|
|
164
|
-
|
|
170
|
+
step_ref: z.object({ goal_id: z.string(), step_id: z.string() }).optional(),
|
|
171
|
+
agent: z.string().optional().describe('Submitting host label for a linked step'),
|
|
172
|
+
recipient_agent: z.string().optional().describe('Host label receiving a linked step; defaults to the target role label'),
|
|
173
|
+
}, async ({ from, to, title, task_type, priority, directive, acceptance_criteria, context_pointers, step_ref, agent, recipient_agent, }, extra) => {
|
|
165
174
|
const now = new Date();
|
|
166
175
|
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
167
176
|
const shortId = randomUUID().replace(/-/g, "").slice(0, 8);
|
|
168
177
|
const handoff = {
|
|
178
|
+
...(step_ref ? { step_ref, recipient_agent: recipient_agent ?? `ACE-${to.charAt(0).toUpperCase() + to.slice(1)}` } : {}),
|
|
169
179
|
handoff_id: `HND-${dateStr}-${shortId}`,
|
|
170
180
|
timestamp_utc: now.toISOString(),
|
|
171
181
|
router: {
|
|
@@ -252,6 +262,9 @@ export function registerHandoffTools(server) {
|
|
|
252
262
|
task_type: handoff.task.type,
|
|
253
263
|
priority: handoff.task.priority,
|
|
254
264
|
source_file: payloadPath,
|
|
265
|
+
step_ref,
|
|
266
|
+
actor: step_ref ? hostAgent(agent, extra.sessionId) : undefined,
|
|
267
|
+
...(step_ref && recipient_agent ? { to: recipient_agent } : {}),
|
|
255
268
|
});
|
|
256
269
|
const runLedger = await appendRunLedgerEntrySafe({
|
|
257
270
|
tool: "create_handoff",
|
|
@@ -271,6 +284,7 @@ export function registerHandoffTools(server) {
|
|
|
271
284
|
const schedulerSync = await syncHandoffIntoScheduler(registry.record);
|
|
272
285
|
const kanban = refreshKanbanArtifacts("tool:create_handoff");
|
|
273
286
|
return {
|
|
287
|
+
structuredContent: { handoff_id: handoff.handoff_id, record: registry.record },
|
|
274
288
|
content: [
|
|
275
289
|
{
|
|
276
290
|
type: "text",
|
|
@@ -282,12 +296,15 @@ export function registerHandoffTools(server) {
|
|
|
282
296
|
`Handoff registry: ${registry.path}`,
|
|
283
297
|
`Run ledger: ${runLedger.path} (${runLedger.entry.id})`,
|
|
284
298
|
`Scheduler job: ${schedulerSync.job_id} (${schedulerSync.enqueued ? "enqueued" : "already_exists"})`,
|
|
285
|
-
|
|
299
|
+
"Scheduler: handoff metadata synchronized; no execution dispatch",
|
|
300
|
+
schedulerSync.reconciliation_required
|
|
301
|
+
? `Scheduler reconciliation required: ${schedulerSync.reconciliation_reason}`
|
|
302
|
+
: "",
|
|
286
303
|
schedulerSync.enqueue_reason
|
|
287
304
|
? `Scheduler enqueue detail: ${schedulerSync.enqueue_reason}`
|
|
288
305
|
: "",
|
|
289
306
|
"",
|
|
290
|
-
|
|
307
|
+
formatKanbanRefresh(kanban),
|
|
291
308
|
`Kanban counts: backlog=${kanban.counts.backlog}, blocked=${kanban.counts.blocked}, in_progress=${kanban.counts.in_progress}, gated=${kanban.counts.gated}, done=${kanban.counts.done}`,
|
|
292
309
|
"",
|
|
293
310
|
"```json",
|
|
@@ -408,14 +425,19 @@ export function registerHandoffTools(server) {
|
|
|
408
425
|
handoff_id: z.string().describe("Handoff identifier"),
|
|
409
426
|
status: HANDOFF_LIFECYCLE_STATUS_ENUM.describe("Lifecycle status"),
|
|
410
427
|
note: z.string().optional().describe("Optional status note"),
|
|
411
|
-
|
|
428
|
+
agent: z.string().optional().describe('Recipient host label for a linked step'),
|
|
429
|
+
}, async ({ handoff_id, status, note, agent }, extra) => {
|
|
430
|
+
const linked = readHandoffRegistry().handoffs[handoff_id]?.step_ref;
|
|
412
431
|
const ack = await acknowledgeHandoffSafe({
|
|
413
432
|
handoff_id,
|
|
414
433
|
status,
|
|
415
434
|
note,
|
|
435
|
+
actor: linked ? hostAgent(agent, extra.sessionId) : undefined,
|
|
416
436
|
});
|
|
417
437
|
if (!ack.ok) {
|
|
418
438
|
return {
|
|
439
|
+
...(linked ? { isError: true } : {}),
|
|
440
|
+
structuredContent: { ...ack },
|
|
419
441
|
content: [
|
|
420
442
|
{
|
|
421
443
|
type: "text",
|
|
@@ -435,6 +457,10 @@ export function registerHandoffTools(server) {
|
|
|
435
457
|
],
|
|
436
458
|
};
|
|
437
459
|
}
|
|
460
|
+
if (linked && ack.record) {
|
|
461
|
+
const step = new GoalRepository(resolveWorkspaceRoot()).getPlan(linked.goal_id)?.steps.find(s => s.step_id === linked.step_id);
|
|
462
|
+
bindHostWork({ agent: ack.record.to, goal_id: linked.goal_id, ...(status === 'accepted' ? { step_id: linked.step_id, attempt: step?.attempt } : {}) }, extra.sessionId);
|
|
463
|
+
}
|
|
438
464
|
const runLedger = await appendRunLedgerEntrySafe({
|
|
439
465
|
tool: "ack_handoff",
|
|
440
466
|
category: status === "blocked" ? "regression" : "major_update",
|
|
@@ -452,6 +478,7 @@ export function registerHandoffTools(server) {
|
|
|
452
478
|
: undefined;
|
|
453
479
|
const kanban = refreshKanbanArtifacts("tool:ack_handoff");
|
|
454
480
|
return {
|
|
481
|
+
structuredContent: { ...ack },
|
|
455
482
|
content: [
|
|
456
483
|
{
|
|
457
484
|
type: "text",
|
|
@@ -466,7 +493,10 @@ export function registerHandoffTools(server) {
|
|
|
466
493
|
? `scheduler-job: ${schedulerSync.job_id} (${schedulerSync.enqueued ? "enqueued" : "already_exists"})`
|
|
467
494
|
: "",
|
|
468
495
|
schedulerSync
|
|
469
|
-
?
|
|
496
|
+
? "scheduler: handoff metadata synchronized; no execution dispatch"
|
|
497
|
+
: "",
|
|
498
|
+
schedulerSync?.reconciliation_required
|
|
499
|
+
? `scheduler-reconciliation-required: ${schedulerSync.reconciliation_reason}`
|
|
470
500
|
: "",
|
|
471
501
|
schedulerSync?.enqueue_reason
|
|
472
502
|
? `scheduler-enqueue-detail: ${schedulerSync.enqueue_reason}`
|
package/dist/tools-lifecycle.js
CHANGED
|
@@ -190,7 +190,7 @@ export function registerLifecycleTools(server) {
|
|
|
190
190
|
{
|
|
191
191
|
type: "text",
|
|
192
192
|
text: [
|
|
193
|
-
"# Kanban Updated",
|
|
193
|
+
snapshot.refresh_scheduled ? "# Kanban refresh scheduled" : "# Kanban Updated",
|
|
194
194
|
"",
|
|
195
195
|
`JSON: ${snapshot.json_path}`,
|
|
196
196
|
`HTML: ${snapshot.html_path}`,
|
package/dist/tools-memory.d.ts
CHANGED
package/dist/tools-memory.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { renderLearningTask } from './learning-input-context.js';
|
|
2
|
+
import { readLearningPolicy } from './learning-policy.js';
|
|
1
3
|
/**
|
|
2
4
|
* Memory tool registrations – context snapshots and recall.
|
|
3
5
|
*/
|
|
@@ -8,7 +10,10 @@ import { isReadError } from "./shared.js";
|
|
|
8
10
|
import { appendRunLedgerEntrySafe } from "./run-ledger.js";
|
|
9
11
|
import { appendStatusEventSafe } from "./status-events.js";
|
|
10
12
|
import { readRuntimeProfile } from "./runtime-profile.js";
|
|
11
|
-
import { buildAceContinuityPacket, formatAceContinuityPacketMarkdown, normalizeContinuityPolicy, } from "./ace-autonomy.js";
|
|
13
|
+
import { buildAceContinuityPacket, formatAceContinuityPacketMarkdown, normalizeContinuityPolicy, formatCurrentWork, readCurrentWorkContext, } from "./ace-autonomy.js";
|
|
14
|
+
import { GoalRepository } from "./store/repositories/goal-repository.js";
|
|
15
|
+
import { bindHostWork } from './host-work-context.js';
|
|
16
|
+
import { formatStepTaskForBridge, collectUpstreamOutputs } from "./task-plan-context.js";
|
|
12
17
|
import { openStore } from "./store/ace-packed-store.js";
|
|
13
18
|
import { ProjectionManager } from "./store/materializers/projection-manager.js";
|
|
14
19
|
import { ContextSnapshotRepository } from "./store/repositories/context-snapshot-repository.js";
|
|
@@ -89,6 +94,8 @@ export function registerMemoryTools(server) {
|
|
|
89
94
|
if (!isReadError(indexRaw)) {
|
|
90
95
|
try {
|
|
91
96
|
index = JSON.parse(indexRaw);
|
|
97
|
+
if (!Array.isArray(index.snapshots) || index.snapshots.some((s) => !s || typeof s.name !== "string" || typeof s.file !== "string" || typeof s.timestamp !== "string"))
|
|
98
|
+
throw new Error("Invalid snapshot index");
|
|
92
99
|
}
|
|
93
100
|
catch {
|
|
94
101
|
/* keep default */
|
|
@@ -207,16 +214,43 @@ export function registerMemoryTools(server) {
|
|
|
207
214
|
],
|
|
208
215
|
};
|
|
209
216
|
});
|
|
210
|
-
server.tool("recall_context", "
|
|
217
|
+
server.tool("recall_context", "Return current work and linked context; retrieve a named snapshot or explicitly list snapshots", {
|
|
211
218
|
name: z
|
|
212
219
|
.string()
|
|
213
220
|
.optional()
|
|
214
|
-
.describe("
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
.describe("Named snapshot to retrieve; omit for current work"),
|
|
222
|
+
list: z.boolean().optional().describe("List saved snapshots instead of current work"),
|
|
223
|
+
goal_id: z.string().optional(),
|
|
224
|
+
agent: z.string().optional(),
|
|
225
|
+
}, async ({ name, list, goal_id, agent }, extra) => {
|
|
226
|
+
if (!name && !list) {
|
|
227
|
+
const context = readCurrentWorkContext({ goal_id, agent });
|
|
228
|
+
const lines = [formatCurrentWork(context.work, "block")];
|
|
229
|
+
const goalId = goal_id ?? context.work.objective?.goal_id;
|
|
230
|
+
if (goalId) {
|
|
231
|
+
const plan = new GoalRepository(resolveWorkspaceRoot()).getPlan(goalId);
|
|
232
|
+
if (plan)
|
|
233
|
+
for (const step of plan.steps.filter((s) => s.status === "running")) {
|
|
234
|
+
if (agent && step.assignee === agent)
|
|
235
|
+
bindHostWork({ agent, goal_id: goalId, step_id: step.step_id, attempt: step.attempt }, extra.sessionId);
|
|
236
|
+
lines.push(`Assignment ${step.step_id} (attempt ${step.attempt ?? 'engine'}, assignee ${step.assignee ?? step.role}):\n${formatStepTaskForBridge({ ...step, task: renderLearningTask(resolveWorkspaceRoot(), goalId, step), upstream_outputs: collectUpstreamOutputs(plan, step) })}${readLearningPolicy(resolveWorkspaceRoot()).enabled && step.served_learning?.text ? '\n' + step.served_learning.text : ''}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const failures = context.events.filter((e) => ["fail", "blocked"].includes(e.status));
|
|
240
|
+
const selected = [...new Map([...context.events.slice(-5), ...failures].map((e) => [e.event_id, e])).values()];
|
|
241
|
+
for (const event of selected.reverse())
|
|
242
|
+
lines.push(`[${event.event_id}] ${event.event_type}: ${event.payload.summary ?? ""}`);
|
|
243
|
+
for (const entry of context.entries.slice(-4).reverse())
|
|
244
|
+
lines.push(`[${entry.id}] ${entry.message}`);
|
|
245
|
+
if (context.events.length > selected.length || context.entries.length > 4)
|
|
246
|
+
lines.push("Additional linked detail: list_status_events / get_run_ledger.");
|
|
247
|
+
return { isError: false, content: [{ type: "text", text: lines.join("\n") }] };
|
|
248
|
+
}
|
|
249
|
+
const indexPath = `${CONTEXT_STORE_REL}/index.json`;
|
|
217
250
|
const indexRaw = safeRead(indexPath);
|
|
218
251
|
if (isReadError(indexRaw)) {
|
|
219
252
|
return {
|
|
253
|
+
isError: !!name,
|
|
220
254
|
content: [
|
|
221
255
|
{
|
|
222
256
|
type: "text",
|
|
@@ -228,9 +262,12 @@ export function registerMemoryTools(server) {
|
|
|
228
262
|
let index;
|
|
229
263
|
try {
|
|
230
264
|
index = JSON.parse(indexRaw);
|
|
265
|
+
if (!Array.isArray(index.snapshots) || index.snapshots.some((s) => !s || typeof s.name !== "string" || typeof s.file !== "string" || typeof s.timestamp !== "string"))
|
|
266
|
+
throw new Error("Invalid snapshot index");
|
|
231
267
|
}
|
|
232
268
|
catch {
|
|
233
269
|
return {
|
|
270
|
+
isError: true,
|
|
234
271
|
content: [
|
|
235
272
|
{
|
|
236
273
|
type: "text",
|
|
@@ -257,6 +294,7 @@ export function registerMemoryTools(server) {
|
|
|
257
294
|
const match = index.snapshots.find((s) => s.name.toLowerCase() === name.toLowerCase());
|
|
258
295
|
if (!match) {
|
|
259
296
|
return {
|
|
297
|
+
isError: true,
|
|
260
298
|
content: [
|
|
261
299
|
{
|
|
262
300
|
type: "text",
|
|
@@ -265,9 +303,10 @@ export function registerMemoryTools(server) {
|
|
|
265
303
|
],
|
|
266
304
|
};
|
|
267
305
|
}
|
|
268
|
-
const content = safeRead(
|
|
306
|
+
const content = safeRead(`${CONTEXT_STORE_REL}/${match.file}`);
|
|
269
307
|
if (isReadError(content)) {
|
|
270
308
|
return {
|
|
309
|
+
isError: true,
|
|
271
310
|
content: [
|
|
272
311
|
{
|
|
273
312
|
type: "text",
|
|
@@ -276,6 +315,12 @@ export function registerMemoryTools(server) {
|
|
|
276
315
|
],
|
|
277
316
|
};
|
|
278
317
|
}
|
|
318
|
+
try {
|
|
319
|
+
JSON.parse(content);
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return { isError: true, content: [{ type: "text", text: `Snapshot ${match.name} is corrupt. Recover its source or select another named snapshot.` }] };
|
|
323
|
+
}
|
|
279
324
|
return {
|
|
280
325
|
content: [
|
|
281
326
|
{
|