zmemory 0.1.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/LICENSE +21 -0
- package/README.md +167 -0
- package/adapters/claude-code-adapter.sh +11 -0
- package/adapters/codex-adapter.sh +12 -0
- package/adapters/cursor-adapter.sh +14 -0
- package/adapters/opencode-adapter.sh +9 -0
- package/bin/zmemory.js +426 -0
- package/package.json +42 -0
- package/src/bus/server.js +51 -0
- package/src/commands/agent.js +42 -0
- package/src/commands/archive.js +32 -0
- package/src/commands/bootstrap.js +7 -0
- package/src/commands/bus.js +6 -0
- package/src/commands/claim.js +66 -0
- package/src/commands/config.js +29 -0
- package/src/commands/context.js +269 -0
- package/src/commands/daemon.js +6 -0
- package/src/commands/decision.js +12 -0
- package/src/commands/doctor.js +100 -0
- package/src/commands/event.js +149 -0
- package/src/commands/exec.js +25 -0
- package/src/commands/explain.js +68 -0
- package/src/commands/failure.js +12 -0
- package/src/commands/gitHook.js +22 -0
- package/src/commands/handoff.js +101 -0
- package/src/commands/health.js +18 -0
- package/src/commands/help.js +33 -0
- package/src/commands/index.js +21 -0
- package/src/commands/init.js +38 -0
- package/src/commands/install.js +28 -0
- package/src/commands/locks.js +9 -0
- package/src/commands/next.js +59 -0
- package/src/commands/orchestrator.js +6 -0
- package/src/commands/plan.js +30 -0
- package/src/commands/resume.js +79 -0
- package/src/commands/route.js +67 -0
- package/src/commands/runs.js +47 -0
- package/src/commands/search.js +159 -0
- package/src/commands/setup.js +148 -0
- package/src/commands/skill.js +103 -0
- package/src/commands/startRun.js +82 -0
- package/src/commands/state.js +68 -0
- package/src/commands/status.js +51 -0
- package/src/commands/stream.js +27 -0
- package/src/commands/summary.js +65 -0
- package/src/commands/sync.js +20 -0
- package/src/commands/tasks.js +89 -0
- package/src/commands/timeline.js +75 -0
- package/src/commands/upgrade.js +19 -0
- package/src/commands/version.js +12 -0
- package/src/commands/who.js +26 -0
- package/src/commands/worker.js +71 -0
- package/src/commands/workspaces.js +25 -0
- package/src/daemon/orchestrator.js +34 -0
- package/src/daemon/zmemoryd.js +17 -0
- package/src/dashboard/server.js +40 -0
- package/src/integrations/agent-hooks.md +39 -0
- package/src/integrations/auto-bootstrap.js +90 -0
- package/src/integrations/harness-adapter.md +39 -0
- package/src/lib/agents.js +36 -0
- package/src/lib/config.js +15 -0
- package/src/lib/coordination/agents.js +49 -0
- package/src/lib/coordination/claims.js +127 -0
- package/src/lib/coordination/who.js +49 -0
- package/src/lib/fileIndex.js +36 -0
- package/src/lib/fileLocks.js +44 -0
- package/src/lib/fs.js +140 -0
- package/src/lib/lock.js +103 -0
- package/src/lib/repoScan.js +19 -0
- package/src/lib/run.js +63 -0
- package/src/lib/sqlite.js +7 -0
- package/src/lib/tasks.js +77 -0
- package/src/lib/teamSync.js +36 -0
- package/src/lib/workspaces.js +29 -0
- package/src/mcp/autostart.js +12 -0
- package/src/mcp/server.js +420 -0
- package/src/mcp/tools.json +28 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { assertValidRunId, getRunFile, isValidRunId } from "../lib/run.js";
|
|
3
|
+
import { ensureZMemory } from "../integrations/auto-bootstrap.js";
|
|
4
|
+
import { addTask, listTasks, claimTask, completeTask } from "../lib/tasks.js";
|
|
5
|
+
import { listAgents, registerAgent } from "../lib/agents.js";
|
|
6
|
+
import { listLocks } from "../lib/fileLocks.js";
|
|
7
|
+
import { execCommand } from "../commands/exec.js";
|
|
8
|
+
import { planCommand } from "../commands/plan.js";
|
|
9
|
+
import { routeRun } from "../commands/route.js";
|
|
10
|
+
import { readJSON, writeJSON } from "../lib/fs.js";
|
|
11
|
+
import { acquireLock, releaseLock } from "../lib/lock.js";
|
|
12
|
+
const EVENTS_FILE = `.zmemory/logs/events.jsonl`;
|
|
13
|
+
const ARCHIVE_DIR = `.zmemory/logs/archive`;
|
|
14
|
+
const MAX_ACTIVE_EVENTS = 5000;
|
|
15
|
+
const ARCHIVE_BATCH = 2000;
|
|
16
|
+
import { claimPattern, listClaims, releaseClaims } from "../lib/coordination/claims.js";
|
|
17
|
+
import { who as whoLookup } from "../lib/coordination/who.js";
|
|
18
|
+
|
|
19
|
+
// ---- Tool Contracts (source of truth) ----
|
|
20
|
+
const toolContracts = {
|
|
21
|
+
zmemory_tools_list: { description: "List MCP tool schemas", input: { required: {}, optional: {} } },
|
|
22
|
+
zmemory_get_context: { description: "Get run context", input: { required: { run_id: "run_id" }, optional: { role: "nonempty:string" } } },
|
|
23
|
+
zmemory_append_event: { description: "Append event", input: { required: { role: "nonempty:string", type: "nonempty:string", summary: "nonempty:string" }, optional: { run_id: "run_id", ts: "nonempty:string", files: "array:string", evidence: "nonempty:string", error_signature: "nonempty:string" } } },
|
|
24
|
+
zmemory_get_run_state: { description: "Read run state", input: { required: { run_id: "run_id" }, optional: {} } },
|
|
25
|
+
zmemory_update_run_state: { description: "Update run state", input: { required: { run_id: "run_id" }, optional: { status: "nonempty:string", current_step: "number", next_action: "nonempty:string" } } },
|
|
26
|
+
zmemory_create_handoff: { description: "Create run handoff", input: { required: { run_id: "run_id" }, optional: {} } },
|
|
27
|
+
zmemory_record_decision: { description: "Record architectural decision", input: { required: { decision: "nonempty:string" }, optional: { reason: "nonempty:string" } } },
|
|
28
|
+
zmemory_record_failure: { description: "Record failure pattern", input: { required: { symptom: "nonempty:string" }, optional: { root_cause: "nonempty:string", fix: "nonempty:string" } } },
|
|
29
|
+
zmemory_search: { description: "Search zmemory files", input: { required: { query: "nonempty:string" }, optional: {} } },
|
|
30
|
+
zmemory_stream_events: { description: "Get recent activity events", input: { required: {}, optional: { limit: "number", since: "nonempty:string" } } },
|
|
31
|
+
zmemory_tasks_list: { description: "List tasks", input: { required: {}, optional: {} } },
|
|
32
|
+
zmemory_tasks_add: { description: "Add task", input: { required: { title: "nonempty:string" }, optional: {} } },
|
|
33
|
+
zmemory_tasks_claim: { description: "Claim task", input: { required: { id: "nonempty:string", worker: "nonempty:string" }, optional: {} } },
|
|
34
|
+
zmemory_tasks_complete: { description: "Complete task", input: { required: { id: "nonempty:string" }, optional: {} } },
|
|
35
|
+
zmemory_agents_list: { description: "List agents", input: { required: {}, optional: {} } },
|
|
36
|
+
zmemory_agents_register: { description: "Register agent", input: { required: { name: "nonempty:string", role: "nonempty:string" }, optional: {} } },
|
|
37
|
+
zmemory_filelocks_list: { description: "List file locks", input: { required: {}, optional: {} } },
|
|
38
|
+
zmemory_claim: { description: "Claim file pattern", input: { required: { pattern: "nonempty:string", session: "nonempty:string" }, optional: { task: "nonempty:string" } } },
|
|
39
|
+
zmemory_claims: { description: "List claims", input: { required: {}, optional: {} } },
|
|
40
|
+
zmemory_release: { description: "Release claims for session", input: { required: { session: "nonempty:string" }, optional: {} } },
|
|
41
|
+
zmemory_who: { description: "Who is working on file", input: { required: { file: "nonempty:string" }, optional: {} } },
|
|
42
|
+
zmemory_plan: { description: "Generate plan", input: { required: { goal: "nonempty:string" }, optional: {} } },
|
|
43
|
+
zmemory_route: { description: "Route run", input: { required: { run: "run_id" }, optional: {} } },
|
|
44
|
+
zmemory_exec: { description: "Execute command (gated)", input: { required: { command: "nonempty:string" }, optional: {} } }
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function checkType(val, type) {
|
|
48
|
+
if (type === "string") return typeof val === "string";
|
|
49
|
+
if (type === "nonempty:string") return typeof val === "string" && val.trim() !== "";
|
|
50
|
+
if (type === "number") return typeof val === "number" && Number.isFinite(val);
|
|
51
|
+
if (type === "boolean") return typeof val === "boolean";
|
|
52
|
+
if (type === "array:string") return Array.isArray(val) && val.every(v => typeof v === "string" && v.trim() !== "");
|
|
53
|
+
if (type === "run_id") return typeof val === "string" && isValidRunId(val);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateInput(tool, input) {
|
|
58
|
+
const spec = toolContracts[tool];
|
|
59
|
+
if (!spec) return;
|
|
60
|
+
const req = spec.input.required || {};
|
|
61
|
+
const opt = spec.input.optional || {};
|
|
62
|
+
const allowed = new Set([...Object.keys(req), ...Object.keys(opt)]);
|
|
63
|
+
|
|
64
|
+
for (const k of Object.keys(req)) {
|
|
65
|
+
if (!(k in input)) throw new Error(`invalid input: missing ${k}`);
|
|
66
|
+
if (!checkType(input[k], req[k])) throw new Error(`invalid input: expected ${k} ${req[k]}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const k of Object.keys(input)) {
|
|
70
|
+
if (!allowed.has(k)) throw new Error(`invalid input: unknown field ${k}`);
|
|
71
|
+
const t = req[k] || opt[k];
|
|
72
|
+
if (t && !checkType(input[k], t)) throw new Error(`invalid input: expected ${k} ${t}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function appendEvent(evt) {
|
|
77
|
+
if (!evt.ts) {
|
|
78
|
+
evt.ts = new Date().toISOString();
|
|
79
|
+
}
|
|
80
|
+
if (evt.run_id) {
|
|
81
|
+
assertValidRunId(evt.run_id);
|
|
82
|
+
}
|
|
83
|
+
const line = JSON.stringify(evt) + "\n";
|
|
84
|
+
if (evt.run_id) {
|
|
85
|
+
try {
|
|
86
|
+
acquireLock(evt.run_id);
|
|
87
|
+
} catch {
|
|
88
|
+
throw new Error("run locked by another agent");
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
fs.appendFileSync(EVENTS_FILE, line);
|
|
92
|
+
fs.appendFileSync(getRunFile(evt.run_id, "events.jsonl"), line);
|
|
93
|
+
rotateEventsIfNeeded();
|
|
94
|
+
} finally {
|
|
95
|
+
releaseLock(evt.run_id);
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
fs.appendFileSync(EVENTS_FILE, line);
|
|
99
|
+
rotateEventsIfNeeded();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function rotateEventsIfNeeded() {
|
|
104
|
+
try {
|
|
105
|
+
if (!fs.existsSync(EVENTS_FILE)) return;
|
|
106
|
+
const lines = fs.readFileSync(EVENTS_FILE, "utf8").split("\n").filter(Boolean);
|
|
107
|
+
if (lines.length <= MAX_ACTIVE_EVENTS) return;
|
|
108
|
+
|
|
109
|
+
fs.mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
110
|
+
|
|
111
|
+
const archive = lines.slice(0, ARCHIVE_BATCH);
|
|
112
|
+
const keep = lines.slice(ARCHIVE_BATCH);
|
|
113
|
+
|
|
114
|
+
const existing = fs.readdirSync(ARCHIVE_DIR).filter(f => f.startsWith("events-") && f.endsWith(".jsonl"));
|
|
115
|
+
const next = String(existing.length + 1).padStart(5, "0");
|
|
116
|
+
const archivePath = `${ARCHIVE_DIR}/events-${next}.jsonl`;
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(archivePath, archive.join("\n") + "\n");
|
|
119
|
+
fs.writeFileSync(EVENTS_FILE, keep.join("\n") + (keep.length ? "\n" : ""));
|
|
120
|
+
} catch {}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getContext({ run_id, role }) {
|
|
124
|
+
assertValidRunId(run_id);
|
|
125
|
+
const state = readJSON(getRunFile(run_id, "state.json"));
|
|
126
|
+
const task = fs.readFileSync(getRunFile(run_id, "task.md"), "utf8");
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
role,
|
|
130
|
+
task,
|
|
131
|
+
status: state?.status,
|
|
132
|
+
step: state?.current_step,
|
|
133
|
+
next_action: state?.next_action
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function updateRunState(input) {
|
|
138
|
+
assertValidRunId(input.run_id);
|
|
139
|
+
const path = getRunFile(input.run_id, "state.json");
|
|
140
|
+
try {
|
|
141
|
+
acquireLock(input.run_id);
|
|
142
|
+
} catch {
|
|
143
|
+
throw new Error("run locked by another agent");
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const state = readJSON(path);
|
|
147
|
+
if (!state) return null;
|
|
148
|
+
|
|
149
|
+
if (input.status) state.status = input.status;
|
|
150
|
+
if (input.current_step !== undefined) state.current_step = input.current_step;
|
|
151
|
+
if (input.next_action) state.next_action = input.next_action;
|
|
152
|
+
|
|
153
|
+
writeJSON(path, state);
|
|
154
|
+
return state;
|
|
155
|
+
} finally {
|
|
156
|
+
releaseLock(input.run_id);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function createHandoff({ run_id }) {
|
|
161
|
+
assertValidRunId(run_id);
|
|
162
|
+
const state = readJSON(getRunFile(run_id, "state.json"));
|
|
163
|
+
const task = fs.readFileSync(getRunFile(run_id, "task.md"), "utf8");
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
task,
|
|
167
|
+
status: state?.status,
|
|
168
|
+
next_action: state?.next_action
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function recordDecision({ decision, reason }) {
|
|
173
|
+
const entry = `\n## ${new Date().toISOString()}\n${decision}\nReason: ${reason || ""}\n`;
|
|
174
|
+
fs.appendFileSync(`.zmemory/decisions.md`, entry);
|
|
175
|
+
return { ok: true };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function recordFailure({ symptom, root_cause, fix }) {
|
|
179
|
+
const entry = `\n## ${new Date().toISOString()}\nSymptom: ${symptom}\nRoot cause: ${root_cause || ""}\nFix: ${fix || ""}\n`;
|
|
180
|
+
fs.appendFileSync(`.zmemory/failures.md`, entry);
|
|
181
|
+
return { ok: true };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function search({ query }) {
|
|
185
|
+
const base = ".zmemory";
|
|
186
|
+
const results = [];
|
|
187
|
+
|
|
188
|
+
function scan(dir) {
|
|
189
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
190
|
+
for (const it of items) {
|
|
191
|
+
const p = `${dir}/${it.name}`;
|
|
192
|
+
if (it.isDirectory()) scan(p);
|
|
193
|
+
else {
|
|
194
|
+
const txt = fs.readFileSync(p, "utf8");
|
|
195
|
+
if (txt.toLowerCase().includes(query.toLowerCase())) results.push(p);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
scan(base);
|
|
201
|
+
return results;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const tools = {
|
|
205
|
+
zmemory_tools_list: () => toolContracts,
|
|
206
|
+
zmemory_get_context: getContext,
|
|
207
|
+
zmemory_append_event: appendEvent,
|
|
208
|
+
zmemory_get_run_state: ({ run_id }) => {
|
|
209
|
+
assertValidRunId(run_id);
|
|
210
|
+
return readJSON(getRunFile(run_id, "state.json"));
|
|
211
|
+
},
|
|
212
|
+
zmemory_update_run_state: updateRunState,
|
|
213
|
+
zmemory_create_handoff: createHandoff,
|
|
214
|
+
zmemory_record_decision: recordDecision,
|
|
215
|
+
zmemory_record_failure: recordFailure,
|
|
216
|
+
zmemory_search: search,
|
|
217
|
+
zmemory_stream_events: ({ limit, since }) => {
|
|
218
|
+
const file = `.zmemory/logs/events.jsonl`;
|
|
219
|
+
const n = typeof limit === "number" && limit > 0 ? limit : 20;
|
|
220
|
+
if (!fs.existsSync(file)) return [];
|
|
221
|
+
|
|
222
|
+
const lines = fs
|
|
223
|
+
.readFileSync(file, "utf8")
|
|
224
|
+
.split("\n")
|
|
225
|
+
.filter(Boolean)
|
|
226
|
+
.slice(-Math.max(n, 5));
|
|
227
|
+
|
|
228
|
+
const events = [];
|
|
229
|
+
for (const l of lines) {
|
|
230
|
+
try {
|
|
231
|
+
events.push(JSON.parse(l));
|
|
232
|
+
} catch {}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Always include last 5 events to guard against context loss
|
|
236
|
+
const lastFive = events.slice(-5);
|
|
237
|
+
|
|
238
|
+
if (!since) return events;
|
|
239
|
+
|
|
240
|
+
const newer = events.filter(e => {
|
|
241
|
+
if (!e?.ts) return true;
|
|
242
|
+
return e.ts > since;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Merge unique events (by ts+summary heuristic)
|
|
246
|
+
const merged = [...newer];
|
|
247
|
+
for (const e of lastFive) {
|
|
248
|
+
if (!merged.find(x => x.ts === e.ts && x.summary === e.summary)) {
|
|
249
|
+
merged.push(e);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return merged;
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
// Task graph tools
|
|
257
|
+
zmemory_tasks_list: () => listTasks(),
|
|
258
|
+
zmemory_tasks_add: ({ title }) => addTask(title),
|
|
259
|
+
zmemory_tasks_claim: ({ id, worker }) => claimTask(id, worker),
|
|
260
|
+
zmemory_tasks_complete: ({ id }) => completeTask(id),
|
|
261
|
+
|
|
262
|
+
// Agent registry
|
|
263
|
+
zmemory_agents_list: () => listAgents(),
|
|
264
|
+
zmemory_agents_register: ({ name, role }) => registerAgent(name, role),
|
|
265
|
+
|
|
266
|
+
// Locks
|
|
267
|
+
zmemory_filelocks_list: () => listLocks()
|
|
268
|
+
,
|
|
269
|
+
// Claims / coordination
|
|
270
|
+
zmemory_claim: ({ pattern, session, task }) => claimPattern(pattern, { session, task }),
|
|
271
|
+
zmemory_claims: () => listClaims(),
|
|
272
|
+
zmemory_release: ({ session }) => {
|
|
273
|
+
releaseClaims(session);
|
|
274
|
+
return { ok: true };
|
|
275
|
+
},
|
|
276
|
+
zmemory_who: ({ file }) => whoLookup(file),
|
|
277
|
+
// planner + routing
|
|
278
|
+
zmemory_plan: ({ goal }) => planCommand([goal]),
|
|
279
|
+
zmemory_route: ({ run }) => routeRun(["--run", run]),
|
|
280
|
+
|
|
281
|
+
// command execution wrapper
|
|
282
|
+
zmemory_exec: ({ command }) => {
|
|
283
|
+
const cfgPath = `.zmemory/config.json`;
|
|
284
|
+
let allow = false;
|
|
285
|
+
if (fs.existsSync(cfgPath)) {
|
|
286
|
+
try {
|
|
287
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
288
|
+
allow = cfg.allow_mcp_exec === true;
|
|
289
|
+
} catch {}
|
|
290
|
+
}
|
|
291
|
+
if (!allow) {
|
|
292
|
+
throw new Error("zmemory_exec disabled; set allow_mcp_exec true to enable");
|
|
293
|
+
}
|
|
294
|
+
return execCommand((command || "").split(" "));
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// Ensure runtime + skill exist whenever MCP starts
|
|
299
|
+
ensureZMemory();
|
|
300
|
+
|
|
301
|
+
// ---- contract drift check (toolContracts vs tools) ----
|
|
302
|
+
{
|
|
303
|
+
const contractKeys = Object.keys(toolContracts).slice().sort();
|
|
304
|
+
const toolKeys = Object.keys(tools).slice().sort();
|
|
305
|
+
if (JSON.stringify(contractKeys) !== JSON.stringify(toolKeys)) {
|
|
306
|
+
throw new Error("tool contract mismatch");
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ---- tools.json drift check ----
|
|
311
|
+
try {
|
|
312
|
+
const p = new URL("./tools.json", import.meta.url);
|
|
313
|
+
const json = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
314
|
+
const fileTools = (json.tools || []).slice().sort();
|
|
315
|
+
const serverTools = Object.keys(tools).slice().sort();
|
|
316
|
+
if (JSON.stringify(fileTools) !== JSON.stringify(serverTools)) {
|
|
317
|
+
throw new Error("tools.json mismatch with server tools");
|
|
318
|
+
}
|
|
319
|
+
} catch (e) {
|
|
320
|
+
console.error(e.message);
|
|
321
|
+
throw e;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let buffer = "";
|
|
325
|
+
let lineQueue = Promise.resolve();
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
async function handleLine(line) {
|
|
330
|
+
if (!line.trim()) return;
|
|
331
|
+
|
|
332
|
+
let msg;
|
|
333
|
+
try {
|
|
334
|
+
msg = JSON.parse(line);
|
|
335
|
+
} catch {
|
|
336
|
+
process.stdout.write(JSON.stringify({ error: "malformed json" }) + "\n");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const id = msg?.id;
|
|
341
|
+
if (!msg || typeof msg !== "object" || Array.isArray(msg)) {
|
|
342
|
+
const out = { error: "invalid message" };
|
|
343
|
+
if (id !== undefined) out.id = id;
|
|
344
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (typeof msg.tool !== "string" || !msg.tool.trim()) {
|
|
349
|
+
const out = { error: "invalid message: tool" };
|
|
350
|
+
if (id !== undefined) out.id = id;
|
|
351
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (msg.input !== undefined && (msg.input === null || typeof msg.input !== "object" || Array.isArray(msg.input))) {
|
|
356
|
+
const out = { error: "invalid message: input" };
|
|
357
|
+
if (id !== undefined) out.id = id;
|
|
358
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const toolName = msg.tool.trim();
|
|
362
|
+
const fn = tools[toolName];
|
|
363
|
+
if (!fn) {
|
|
364
|
+
const out = { error: "unknown tool" };
|
|
365
|
+
if (id !== undefined) out.id = id;
|
|
366
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
let capturedOut = "";
|
|
371
|
+
let capturedErr = "";
|
|
372
|
+
|
|
373
|
+
const origLog = console.log;
|
|
374
|
+
const origErr = console.error;
|
|
375
|
+
|
|
376
|
+
console.log = (...a) => {
|
|
377
|
+
capturedOut += a.map(v => String(v)).join(" ") + "\n";
|
|
378
|
+
};
|
|
379
|
+
console.error = (...a) => {
|
|
380
|
+
capturedErr += a.map(v => String(v)).join(" ") + "\n";
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
const input = msg.input || {};
|
|
385
|
+
validateInput(toolName, input);
|
|
386
|
+
const raw = await fn(input);
|
|
387
|
+
const result = raw === undefined ? null : raw;
|
|
388
|
+
const out = { result };
|
|
389
|
+
if (id !== undefined) out.id = id;
|
|
390
|
+
if (capturedOut) out.stdout = capturedOut;
|
|
391
|
+
if (capturedErr) out.stderr = capturedErr;
|
|
392
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
393
|
+
} catch (e) {
|
|
394
|
+
const out = { error: e?.message || String(e) };
|
|
395
|
+
if (id !== undefined) out.id = id;
|
|
396
|
+
if (capturedOut) out.stdout = capturedOut;
|
|
397
|
+
if (capturedErr) out.stderr = capturedErr;
|
|
398
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
399
|
+
} finally {
|
|
400
|
+
console.log = origLog;
|
|
401
|
+
console.error = origErr;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
process.stdin.on("data", (chunk) => {
|
|
406
|
+
buffer += chunk.toString();
|
|
407
|
+
let idx;
|
|
408
|
+
while ((idx = buffer.indexOf("\n")) >= 0) {
|
|
409
|
+
const line = buffer.slice(0, idx);
|
|
410
|
+
buffer = buffer.slice(idx + 1);
|
|
411
|
+
lineQueue = lineQueue.then(() => handleLine(line)).catch(() => {});
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
process.stdin.on("end", () => {
|
|
416
|
+
const remaining = buffer.trim();
|
|
417
|
+
if (remaining) {
|
|
418
|
+
lineQueue = lineQueue.then(() => handleLine(remaining)).catch(() => {});
|
|
419
|
+
}
|
|
420
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tools": [
|
|
3
|
+
"zmemory_tools_list",
|
|
4
|
+
"zmemory_get_context",
|
|
5
|
+
"zmemory_append_event",
|
|
6
|
+
"zmemory_get_run_state",
|
|
7
|
+
"zmemory_update_run_state",
|
|
8
|
+
"zmemory_create_handoff",
|
|
9
|
+
"zmemory_record_decision",
|
|
10
|
+
"zmemory_record_failure",
|
|
11
|
+
"zmemory_search",
|
|
12
|
+
"zmemory_stream_events",
|
|
13
|
+
"zmemory_tasks_list",
|
|
14
|
+
"zmemory_tasks_add",
|
|
15
|
+
"zmemory_tasks_claim",
|
|
16
|
+
"zmemory_tasks_complete",
|
|
17
|
+
"zmemory_agents_list",
|
|
18
|
+
"zmemory_agents_register",
|
|
19
|
+
"zmemory_filelocks_list",
|
|
20
|
+
"zmemory_claim",
|
|
21
|
+
"zmemory_claims",
|
|
22
|
+
"zmemory_release",
|
|
23
|
+
"zmemory_who",
|
|
24
|
+
"zmemory_plan",
|
|
25
|
+
"zmemory_route",
|
|
26
|
+
"zmemory_exec"
|
|
27
|
+
]
|
|
28
|
+
}
|