@synaplink/orqlaude 0.3.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/.mcp.json.template +8 -0
- package/README.md +239 -0
- package/dist/__tests__/hallucination.test.d.ts +1 -0
- package/dist/__tests__/hallucination.test.js +65 -0
- package/dist/__tests__/hallucination.test.js.map +1 -0
- package/dist/__tests__/state.test.d.ts +1 -0
- package/dist/__tests__/state.test.js +124 -0
- package/dist/__tests__/state.test.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +322 -0
- package/dist/cli.js.map +1 -0
- package/dist/lib/audit.d.ts +38 -0
- package/dist/lib/audit.js +108 -0
- package/dist/lib/audit.js.map +1 -0
- package/dist/lib/budgeting.d.ts +37 -0
- package/dist/lib/budgeting.js +67 -0
- package/dist/lib/budgeting.js.map +1 -0
- package/dist/lib/hallucination.d.ts +46 -0
- package/dist/lib/hallucination.js +154 -0
- package/dist/lib/hallucination.js.map +1 -0
- package/dist/lib/jsonl_tail.d.ts +40 -0
- package/dist/lib/jsonl_tail.js +126 -0
- package/dist/lib/jsonl_tail.js.map +1 -0
- package/dist/lib/pricing.d.ts +24 -0
- package/dist/lib/pricing.js +43 -0
- package/dist/lib/pricing.js.map +1 -0
- package/dist/lib/state.d.ts +118 -0
- package/dist/lib/state.js +138 -0
- package/dist/lib/state.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +34 -0
- package/dist/server.js.map +1 -0
- package/dist/telegram/api.d.ts +45 -0
- package/dist/telegram/api.js +59 -0
- package/dist/telegram/api.js.map +1 -0
- package/dist/telegram/bot.d.ts +11 -0
- package/dist/telegram/bot.js +73 -0
- package/dist/telegram/bot.js.map +1 -0
- package/dist/telegram/commands.d.ts +22 -0
- package/dist/telegram/commands.js +217 -0
- package/dist/telegram/commands.js.map +1 -0
- package/dist/telegram/config.d.ts +30 -0
- package/dist/telegram/config.js +37 -0
- package/dist/telegram/config.js.map +1 -0
- package/dist/telegram/notifier.d.ts +14 -0
- package/dist/telegram/notifier.js +136 -0
- package/dist/telegram/notifier.js.map +1 -0
- package/dist/tools/broker.d.ts +15 -0
- package/dist/tools/broker.js +245 -0
- package/dist/tools/broker.js.map +1 -0
- package/dist/tools/dispatch.d.ts +14 -0
- package/dist/tools/dispatch.js +231 -0
- package/dist/tools/dispatch.js.map +1 -0
- package/dist/tools/lifecycle.d.ts +18 -0
- package/dist/tools/lifecycle.js +124 -0
- package/dist/tools/lifecycle.js.map +1 -0
- package/dist/tools/ping.d.ts +2 -0
- package/dist/tools/ping.js +26 -0
- package/dist/tools/ping.js.map +1 -0
- package/dist/tools/planning.d.ts +4 -0
- package/dist/tools/planning.js +160 -0
- package/dist/tools/planning.js.map +1 -0
- package/dist/tools/review.d.ts +18 -0
- package/dist/tools/review.js +93 -0
- package/dist/tools/review.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { findPlan, findTask } from "../lib/state.js";
|
|
3
|
+
import { jsonlPathFor, snapshotSession } from "../lib/jsonl_tail.js";
|
|
4
|
+
import { detectHallucination, extractToolUses } from "../lib/hallucination.js";
|
|
5
|
+
/**
|
|
6
|
+
* Dispatch-phase tools: next_task, register_spawn, status, collect.
|
|
7
|
+
*
|
|
8
|
+
* v0.2.0:
|
|
9
|
+
* • status() now includes per-agent hallucination report + token usage.
|
|
10
|
+
* • status() flips the plan to `cancelled_overbudget` and queues STOP
|
|
11
|
+
* broker messages when total token usage exceeds the budget cap.
|
|
12
|
+
* • The spawn prompt embeds the task_id so children can self-register via
|
|
13
|
+
* `checkin` on their first turn — register_spawn becomes a fallback.
|
|
14
|
+
*/
|
|
15
|
+
export function registerDispatch(server, store, audit) {
|
|
16
|
+
// ---- next_task ------------------------------------------------------------
|
|
17
|
+
server.tool("next_task", "Return the next pending task to dispatch, or null if all tasks have been spawned. The returned prompt embeds the task_id and instructs the agent to self-register via `checkin` on its first turn — so register_spawn is usually unnecessary.", { plan_id: z.string() }, audit.wrap("next_task", async ({ plan_id }) => {
|
|
18
|
+
const result = await store.update((state) => {
|
|
19
|
+
const plan = findPlan(state, plan_id);
|
|
20
|
+
if (plan.status !== "approved" && plan.status !== "dispatching") {
|
|
21
|
+
throw new Error(`Plan ${plan_id} is not approved (status=${plan.status}). Call confirm first.`);
|
|
22
|
+
}
|
|
23
|
+
plan.status = "dispatching";
|
|
24
|
+
const next = plan.tasks.find((t) => t.status === "pending");
|
|
25
|
+
if (!next) {
|
|
26
|
+
plan.status = "running";
|
|
27
|
+
return { plan_id, task: null, message: "All tasks dispatched." };
|
|
28
|
+
}
|
|
29
|
+
next.status = "dispatched";
|
|
30
|
+
next.startedAt = Date.now();
|
|
31
|
+
return {
|
|
32
|
+
plan_id,
|
|
33
|
+
task: {
|
|
34
|
+
task_id: next.id,
|
|
35
|
+
title: next.title,
|
|
36
|
+
prompt: buildSpawnPrompt(plan.id, next.id, next.prompt, next.branchHint),
|
|
37
|
+
tldr: next.tldr,
|
|
38
|
+
scope: next.scope ?? [],
|
|
39
|
+
},
|
|
40
|
+
next_step: "Call `mcp__ccd_session__spawn_task` with this title/prompt/tldr. The spawned agent will self-register via checkin on its first turn — usually no manual register_spawn needed.",
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
44
|
+
}, ({ plan_id }) => ({ planId: plan_id })));
|
|
45
|
+
// ---- register_spawn (manual fallback) ------------------------------------
|
|
46
|
+
server.tool("register_spawn", "Manually register a spawn (fallback). Normally the spawned agent self-registers on first checkin; only call this if a child fails to do so within ~30s.", {
|
|
47
|
+
plan_id: z.string(),
|
|
48
|
+
task_id: z.string(),
|
|
49
|
+
session_id: z.string(),
|
|
50
|
+
}, audit.wrap("register_spawn", async ({ plan_id, task_id, session_id }) => {
|
|
51
|
+
const result = await store.update((state) => {
|
|
52
|
+
const plan = findPlan(state, plan_id);
|
|
53
|
+
const task = findTask(plan, task_id);
|
|
54
|
+
task.spawnedSessionId = session_id;
|
|
55
|
+
task.status = "running";
|
|
56
|
+
return { plan_id, task_id, session_id, status: task.status, source: "manual" };
|
|
57
|
+
});
|
|
58
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
59
|
+
}, ({ plan_id }) => ({ planId: plan_id })));
|
|
60
|
+
// ---- status ---------------------------------------------------------------
|
|
61
|
+
server.tool("status", "Snapshot of every tracked task: current activity, cumulative cost+tokens, last assistant preview, termination state, AND a hallucination report per agent (path validation, tool-pattern sanity). Auto-cancels the plan and queues STOP messages if total token usage exceeds the budget cap. Call this when the user asks how agents are doing or to drive automated decisions.", { plan_id: z.string() }, audit.wrap("status", async ({ plan_id }) => {
|
|
62
|
+
const plan0 = await store.read((state) => findPlan(state, plan_id));
|
|
63
|
+
const cwd = process.cwd();
|
|
64
|
+
// Gather snapshots + hallucination reports in parallel.
|
|
65
|
+
const snapshots = await Promise.all(plan0.tasks.map(async (t) => {
|
|
66
|
+
if (!t.spawnedSessionId) {
|
|
67
|
+
return {
|
|
68
|
+
task_id: t.id,
|
|
69
|
+
title: t.title,
|
|
70
|
+
status: t.status,
|
|
71
|
+
session_id: null,
|
|
72
|
+
tokens_used: 0,
|
|
73
|
+
cost_usd: 0,
|
|
74
|
+
note: "Not yet spawned (waiting for chip click + self-registration).",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const snap = await snapshotSession(cwd, t.spawnedSessionId);
|
|
78
|
+
const toolUses = await extractToolUses(jsonlPathFor(cwd, t.spawnedSessionId));
|
|
79
|
+
const hallu = await detectHallucination(toolUses, cwd);
|
|
80
|
+
return {
|
|
81
|
+
task_id: t.id,
|
|
82
|
+
title: t.title,
|
|
83
|
+
status: snap.terminated ? "done" : t.status,
|
|
84
|
+
session_id: t.spawnedSessionId,
|
|
85
|
+
tokens_used: snap.totalEffectiveTokens,
|
|
86
|
+
cost_usd: snap.totalCostUsd,
|
|
87
|
+
last_event_type: snap.lastEventType,
|
|
88
|
+
last_activity_at: snap.lastActivityAt,
|
|
89
|
+
last_assistant_preview: snap.lastAssistantText?.slice(0, 200) ?? null,
|
|
90
|
+
current_tool: snap.lastToolUse?.name ?? null,
|
|
91
|
+
terminated: snap.terminated,
|
|
92
|
+
termination_reason: snap.terminationReason,
|
|
93
|
+
hallucination: { score: hallu.score, level: hallu.level, concerns: hallu.concerns },
|
|
94
|
+
stop_requested: t.stopRequested ?? null,
|
|
95
|
+
};
|
|
96
|
+
}));
|
|
97
|
+
const totalTokens = snapshots.reduce((sum, s) => sum + (s.tokens_used ?? 0), 0);
|
|
98
|
+
const totalCost = snapshots.reduce((sum, s) => sum + (s.cost_usd ?? 0), 0);
|
|
99
|
+
// ---- budget enforcement: kill on overbudget --------------------------
|
|
100
|
+
const overbudget = totalTokens > plan0.budgetCapTokens;
|
|
101
|
+
let autoCancelled = false;
|
|
102
|
+
if (overbudget && plan0.status !== "cancelled_overbudget" && plan0.status !== "cancelled") {
|
|
103
|
+
await store.update((state) => {
|
|
104
|
+
const plan = findPlan(state, plan_id);
|
|
105
|
+
plan.status = "cancelled_overbudget";
|
|
106
|
+
for (const t of plan.tasks) {
|
|
107
|
+
if (t.spawnedSessionId && !t.stopRequested) {
|
|
108
|
+
t.stopRequested = { reason: "fleet overbudget", requestedAt: Date.now() };
|
|
109
|
+
plan.messages.push({
|
|
110
|
+
id: cryptoRandomId(),
|
|
111
|
+
toSessionId: t.spawnedSessionId,
|
|
112
|
+
text: `STOP: fleet exceeded token budget (used ${Math.round(totalTokens / 1000)}k of ${Math.round(plan.budgetCapTokens / 1000)}k cap). Commit what you have and exit.`,
|
|
113
|
+
queuedAt: Date.now(),
|
|
114
|
+
delivered: false,
|
|
115
|
+
kind: "stop",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
autoCancelled = true;
|
|
121
|
+
}
|
|
122
|
+
// ---- aggregated hallucination warning -------------------------------
|
|
123
|
+
const concerningAgents = snapshots
|
|
124
|
+
.filter((s) => s.hallucination && s.hallucination.score >= 0.3)
|
|
125
|
+
.map((s) => ({ task_id: s.task_id, title: s.title, level: s.hallucination.level, concerns: s.hallucination.concerns }));
|
|
126
|
+
return {
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: "text",
|
|
130
|
+
text: JSON.stringify({
|
|
131
|
+
plan_id,
|
|
132
|
+
plan_status: autoCancelled ? "cancelled_overbudget" : plan0.status,
|
|
133
|
+
budget_cap_tokens: plan0.budgetCapTokens,
|
|
134
|
+
total_tokens_used: totalTokens,
|
|
135
|
+
budget_remaining_tokens: Math.max(0, plan0.budgetCapTokens - totalTokens),
|
|
136
|
+
total_cost_usd: totalCost,
|
|
137
|
+
hallucination_alerts: concerningAgents,
|
|
138
|
+
agents: snapshots,
|
|
139
|
+
next_step: concerningAgents.length > 0
|
|
140
|
+
? "One or more agents show hallucination signs. Consider `send_message` to nudge, or `kill_task` if the agent is irrecoverable."
|
|
141
|
+
: autoCancelled
|
|
142
|
+
? "Fleet auto-cancelled (overbudget). STOP messages queued to all running children."
|
|
143
|
+
: undefined,
|
|
144
|
+
}, null, 2),
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
}, ({ plan_id }) => ({ planId: plan_id })));
|
|
149
|
+
// ---- collect --------------------------------------------------------------
|
|
150
|
+
server.tool("collect", "Final result aggregation: per-task summary, PR URLs, total tokens+cost, exit reasons. Call once all agents have terminated. The result includes a `ready_for_review` flag suggesting you call `review_prs` (when available) to spawn reviewers.", { plan_id: z.string() }, audit.wrap("collect", async ({ plan_id }) => {
|
|
151
|
+
const plan = await store.update((state) => {
|
|
152
|
+
const p = findPlan(state, plan_id);
|
|
153
|
+
const allDone = p.tasks.every((t) => t.status === "done" || t.status === "failed" || t.status === "cancelled");
|
|
154
|
+
if (allDone && p.status !== "collected")
|
|
155
|
+
p.status = "collected";
|
|
156
|
+
return p;
|
|
157
|
+
});
|
|
158
|
+
const cwd = process.cwd();
|
|
159
|
+
const results = await Promise.all(plan.tasks.map(async (t) => {
|
|
160
|
+
const snap = t.spawnedSessionId ? await snapshotSession(cwd, t.spawnedSessionId) : null;
|
|
161
|
+
return {
|
|
162
|
+
task_id: t.id,
|
|
163
|
+
title: t.title,
|
|
164
|
+
status: t.status,
|
|
165
|
+
session_id: t.spawnedSessionId ?? null,
|
|
166
|
+
pr_url: t.prUrl ?? null,
|
|
167
|
+
tokens_used: snap?.totalEffectiveTokens ?? 0,
|
|
168
|
+
cost_usd: snap?.totalCostUsd ?? t.costUsd ?? 0,
|
|
169
|
+
summary: t.summary ?? snap?.lastAssistantText?.slice(0, 300) ?? null,
|
|
170
|
+
exit_reason: t.exitReason ?? snap?.terminationReason ?? null,
|
|
171
|
+
};
|
|
172
|
+
}));
|
|
173
|
+
const totalTokens = results.reduce((s, r) => s + r.tokens_used, 0);
|
|
174
|
+
const totalCost = results.reduce((s, r) => s + r.cost_usd, 0);
|
|
175
|
+
const withPr = results.filter((r) => r.pr_url);
|
|
176
|
+
return {
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: JSON.stringify({
|
|
181
|
+
plan_id,
|
|
182
|
+
plan_status: plan.status,
|
|
183
|
+
root_task: plan.rootTask,
|
|
184
|
+
total_tokens_used: totalTokens,
|
|
185
|
+
total_cost_usd: totalCost,
|
|
186
|
+
ready_for_review: withPr.length > 0,
|
|
187
|
+
pr_count: withPr.length,
|
|
188
|
+
results,
|
|
189
|
+
next_step: withPr.length > 0
|
|
190
|
+
? "Consider calling `review_prs(plan_id)` to spawn reviewer agents for each PR."
|
|
191
|
+
: "No PRs opened yet — wait for agents to post their PR URLs via post_note.",
|
|
192
|
+
}, null, 2),
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
}, ({ plan_id }) => ({ planId: plan_id })));
|
|
197
|
+
}
|
|
198
|
+
function buildSpawnPrompt(planId, taskId, userPrompt, branchHint) {
|
|
199
|
+
const branchSection = branchHint ? `\n\nSuggested branch: \`${branchHint}\`.` : "";
|
|
200
|
+
return `${userPrompt}${branchSection}
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
ORQLAUDE FLEET PROTOCOL
|
|
205
|
+
plan_id: ${planId}
|
|
206
|
+
task_id: ${taskId}
|
|
207
|
+
|
|
208
|
+
Step 1 — register yourself.
|
|
209
|
+
Your FIRST action must be to call \`mcp__orqlaude__checkin\` with your session id (from $CLAUDE_CODE_SESSION_ID) AND the task_id above. This claims your task and lets the orchestrator track you.
|
|
210
|
+
|
|
211
|
+
Step 2 — do the work.
|
|
212
|
+
Complete the task above. Read before editing. Run tests before committing.
|
|
213
|
+
|
|
214
|
+
Step 3 — periodic check-ins.
|
|
215
|
+
Call \`mcp__orqlaude__checkin\` every few turns to pull any directed messages from the orchestrator (the primary Claude). If you receive a STOP message, commit what you have and exit immediately.
|
|
216
|
+
|
|
217
|
+
Step 4 — share findings (optional).
|
|
218
|
+
If you discover something other agents should know (a removed function, a schema change), call \`mcp__orqlaude__post_note\` with the finding. Set \`blocking: true\` if it must be ack'd before you continue.
|
|
219
|
+
|
|
220
|
+
Step 5 — claim files (optional, recommended).
|
|
221
|
+
Before editing files that others might touch, call \`mcp__orqlaude__claim_files\` with the absolute paths. Conflicts surface to the orchestrator.
|
|
222
|
+
|
|
223
|
+
Step 6 — finish.
|
|
224
|
+
When done, commit, push, open a PR via \`gh pr create\`, then call \`mcp__orqlaude__post_note\` with the PR URL. The orchestrator's \`collect\` reads it from there.
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
function cryptoRandomId() {
|
|
228
|
+
// small helper to avoid importing crypto in this top-of-file scope twice
|
|
229
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/tools/dispatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StateStore } from "../lib/state.js";
|
|
3
|
+
import type { AuditLog } from "../lib/audit.js";
|
|
4
|
+
/**
|
|
5
|
+
* Lifecycle tools: kill_task, resume_plan, list_plans.
|
|
6
|
+
*
|
|
7
|
+
* `kill_task` queues a STOP broker message and returns the session id ready to
|
|
8
|
+
* pass into `mcp__ccd_session_mgmt__archive_session` if the agent doesn't
|
|
9
|
+
* shut itself down cleanly.
|
|
10
|
+
*
|
|
11
|
+
* `resume_plan` lets the primary Claude pick up an in-progress fleet after a
|
|
12
|
+
* Desktop-app restart or new session — it returns the current state plus a
|
|
13
|
+
* prescriptive "do this next" hint.
|
|
14
|
+
*
|
|
15
|
+
* `list_plans` returns a short summary of every plan in this project's state
|
|
16
|
+
* file (active first), so a fresh session can find what's in flight.
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerLifecycle(server: McpServer, store: StateStore, audit: AuditLog): void;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { findPlan, findTask } from "../lib/state.js";
|
|
4
|
+
import { snapshotSession } from "../lib/jsonl_tail.js";
|
|
5
|
+
/**
|
|
6
|
+
* Lifecycle tools: kill_task, resume_plan, list_plans.
|
|
7
|
+
*
|
|
8
|
+
* `kill_task` queues a STOP broker message and returns the session id ready to
|
|
9
|
+
* pass into `mcp__ccd_session_mgmt__archive_session` if the agent doesn't
|
|
10
|
+
* shut itself down cleanly.
|
|
11
|
+
*
|
|
12
|
+
* `resume_plan` lets the primary Claude pick up an in-progress fleet after a
|
|
13
|
+
* Desktop-app restart or new session — it returns the current state plus a
|
|
14
|
+
* prescriptive "do this next" hint.
|
|
15
|
+
*
|
|
16
|
+
* `list_plans` returns a short summary of every plan in this project's state
|
|
17
|
+
* file (active first), so a fresh session can find what's in flight.
|
|
18
|
+
*/
|
|
19
|
+
export function registerLifecycle(server, store, audit) {
|
|
20
|
+
// ---- kill_task ------------------------------------------------------------
|
|
21
|
+
server.tool("kill_task", "Stop a running task. Queues a STOP broker message that delivers on the agent's next checkin, AND returns the session id you should pass to `mcp__ccd_session_mgmt__archive_session` if the agent doesn't comply. Use this when an agent is hallucinating, looping, or otherwise off the rails.", {
|
|
22
|
+
plan_id: z.string(),
|
|
23
|
+
task_id: z.string(),
|
|
24
|
+
reason: z.string().describe("Why you're killing this task. Surfaced to the agent and stored in audit log."),
|
|
25
|
+
}, audit.wrap("kill_task", async ({ plan_id, task_id, reason }) => {
|
|
26
|
+
const result = await store.update((state) => {
|
|
27
|
+
const plan = findPlan(state, plan_id);
|
|
28
|
+
const task = findTask(plan, task_id);
|
|
29
|
+
if (!task.spawnedSessionId) {
|
|
30
|
+
task.status = "cancelled";
|
|
31
|
+
return { plan_id, task_id, status: "cancelled_before_spawn", session_id: null };
|
|
32
|
+
}
|
|
33
|
+
task.stopRequested = { reason, requestedAt: Date.now() };
|
|
34
|
+
plan.messages.push({
|
|
35
|
+
id: randomUUID(),
|
|
36
|
+
toSessionId: task.spawnedSessionId,
|
|
37
|
+
text: `STOP: ${reason}. Commit what you have and exit.`,
|
|
38
|
+
queuedAt: Date.now(),
|
|
39
|
+
delivered: false,
|
|
40
|
+
kind: "stop",
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
plan_id,
|
|
44
|
+
task_id,
|
|
45
|
+
session_id: task.spawnedSessionId,
|
|
46
|
+
queued_stop: true,
|
|
47
|
+
next_step: "Wait ~30s for the agent to acknowledge via checkin. If it doesn't terminate cleanly, call `mcp__ccd_session_mgmt__archive_session` with the session_id above.",
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
51
|
+
}, ({ plan_id, task_id }) => ({ planId: plan_id })));
|
|
52
|
+
// ---- resume_plan ----------------------------------------------------------
|
|
53
|
+
server.tool("resume_plan", "Resume monitoring of an in-flight plan after a Desktop-app restart or new primary-Claude session. Returns plan state, per-task status (refreshed from JSONL), and a 'do this next' hint. Use when you spot a plan in `list_plans` and the user wants to continue.", { plan_id: z.string() }, audit.wrap("resume_plan", async ({ plan_id }) => {
|
|
54
|
+
const plan = await store.read((state) => findPlan(state, plan_id));
|
|
55
|
+
const cwd = process.cwd();
|
|
56
|
+
const tasks = await Promise.all(plan.tasks.map(async (t) => {
|
|
57
|
+
const snap = t.spawnedSessionId ? await snapshotSession(cwd, t.spawnedSessionId) : null;
|
|
58
|
+
return {
|
|
59
|
+
task_id: t.id,
|
|
60
|
+
title: t.title,
|
|
61
|
+
status: snap?.terminated ? "done" : t.status,
|
|
62
|
+
session_id: t.spawnedSessionId ?? null,
|
|
63
|
+
tokens_used: snap?.totalEffectiveTokens ?? 0,
|
|
64
|
+
cost_usd: snap?.totalCostUsd ?? 0,
|
|
65
|
+
pr_url: t.prUrl ?? null,
|
|
66
|
+
terminated: snap?.terminated ?? false,
|
|
67
|
+
};
|
|
68
|
+
}));
|
|
69
|
+
const pending = tasks.filter((t) => t.status === "pending");
|
|
70
|
+
const running = tasks.filter((t) => t.status === "running" || t.status === "dispatched");
|
|
71
|
+
const done = tasks.filter((t) => t.status === "done" || t.status === "failed" || t.status === "cancelled");
|
|
72
|
+
const nextStep = plan.status === "awaiting_approval"
|
|
73
|
+
? "Plan is awaiting user approval — call request_approval again to regenerate the prompt, then confirm."
|
|
74
|
+
: pending.length > 0
|
|
75
|
+
? `Plan has ${pending.length} pending task(s). Call \`next_task\` to dispatch the next one.`
|
|
76
|
+
: running.length > 0
|
|
77
|
+
? `${running.length} agent(s) still running. Poll \`status\` and \`poll_notes\` periodically.`
|
|
78
|
+
: done.length === plan.tasks.length
|
|
79
|
+
? "All agents have terminated. Call `collect` for final results, then `review_prs` if available."
|
|
80
|
+
: "Inspect via `status`.";
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: JSON.stringify({
|
|
86
|
+
plan_id,
|
|
87
|
+
root_task: plan.rootTask,
|
|
88
|
+
status: plan.status,
|
|
89
|
+
created_at: plan.createdAt,
|
|
90
|
+
approved_at: plan.approvedAt ?? null,
|
|
91
|
+
budget_cap_tokens: plan.budgetCapTokens,
|
|
92
|
+
tasks,
|
|
93
|
+
pending_count: pending.length,
|
|
94
|
+
running_count: running.length,
|
|
95
|
+
done_count: done.length,
|
|
96
|
+
next_step: nextStep,
|
|
97
|
+
}, null, 2),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}, ({ plan_id }) => ({ planId: plan_id })));
|
|
102
|
+
// ---- list_plans -----------------------------------------------------------
|
|
103
|
+
server.tool("list_plans", "List every plan known to orqlaude in this project. Active plans first. Useful at the start of a fresh session to see what's in flight.", {
|
|
104
|
+
include_collected: z.boolean().default(false).describe("Include plans whose status is `collected` (finished and reviewed)."),
|
|
105
|
+
}, audit.wrap("list_plans", async ({ include_collected }) => {
|
|
106
|
+
const plans = await store.read((state) => {
|
|
107
|
+
return Object.values(state.plans)
|
|
108
|
+
.filter((p) => include_collected || p.status !== "collected")
|
|
109
|
+
.map((p) => ({
|
|
110
|
+
plan_id: p.id,
|
|
111
|
+
root_task: p.rootTask,
|
|
112
|
+
status: p.status,
|
|
113
|
+
created_at: p.createdAt,
|
|
114
|
+
task_count: p.tasks.length,
|
|
115
|
+
tasks_done: p.tasks.filter((t) => t.status === "done").length,
|
|
116
|
+
tasks_running: p.tasks.filter((t) => t.status === "running" || t.status === "dispatched").length,
|
|
117
|
+
budget_cap_tokens: p.budgetCapTokens,
|
|
118
|
+
}))
|
|
119
|
+
.sort((a, b) => b.created_at - a.created_at);
|
|
120
|
+
});
|
|
121
|
+
return { content: [{ type: "text", text: JSON.stringify({ plans }, null, 2) }] };
|
|
122
|
+
}, () => ({})));
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/tools/lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerPing(server) {
|
|
3
|
+
server.tool("ping", "Health check — returns pong with server version and the cwd it was launched in. Use this once after installing orqlaude to confirm the MCP wiring works.", {
|
|
4
|
+
echo: z.string().optional().describe("Optional string echoed back in the response"),
|
|
5
|
+
}, async ({ echo }) => {
|
|
6
|
+
const payload = {
|
|
7
|
+
ok: true,
|
|
8
|
+
server: "orqlaude",
|
|
9
|
+
version: "0.1.0",
|
|
10
|
+
cwd: process.cwd(),
|
|
11
|
+
node: process.version,
|
|
12
|
+
pid: process.pid,
|
|
13
|
+
echo: echo ?? null,
|
|
14
|
+
time: new Date().toISOString(),
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: JSON.stringify(payload, null, 2),
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=ping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ping.js","sourceRoot":"","sources":["../../src/tools/ping.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC5C,MAAM,CAAC,IAAI,CACT,MAAM,EACN,0JAA0J,EAC1J;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;KACpF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACjB,MAAM,OAAO,GAAG;YACd,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,OAAO;YAChB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,IAAI,EAAE,OAAO,CAAC,OAAO;YACrB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,IAAI,EAAE,IAAI,IAAI,IAAI;YAClB,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SAC/B,CAAC;QACF,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;iBACvC;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StateStore } from "../lib/state.js";
|
|
3
|
+
import type { AuditLog } from "../lib/audit.js";
|
|
4
|
+
export declare function registerPlanning(server: McpServer, store: StateStore, audit: AuditLog): void;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { newPlan, findPlan } from "../lib/state.js";
|
|
4
|
+
import { estimateAgent, readDailyTokenUsage } from "../lib/budgeting.js";
|
|
5
|
+
/**
|
|
6
|
+
* Planning-phase tools: create_plan, estimate, request_approval, confirm.
|
|
7
|
+
*
|
|
8
|
+
* v0.2.0: budgets are now token-based (Max-plan friendly). USD is tracked
|
|
9
|
+
* informationally. The Desktop app's daily-token tally is surfaced in
|
|
10
|
+
* `request_approval` so the user sees their remaining quota.
|
|
11
|
+
*/
|
|
12
|
+
const TaskInputSchema = z.object({
|
|
13
|
+
title: z.string().min(1).max(60).describe("Imperative action phrase, <60 chars. Becomes the spawned session's chip label."),
|
|
14
|
+
prompt: z.string().min(1).describe("Self-contained prompt for the spawned agent. Must include file paths, scope, and the directive to commit + open a PR. The agent has no memory of this conversation."),
|
|
15
|
+
tldr: z.string().min(1).describe("1-2 sentence plain-English summary shown to the user as a tooltip."),
|
|
16
|
+
scope: z.array(z.string()).optional().describe("Optional list of files/dirs this task touches. Informational only; if you want enforcement use claim_files from the broker."),
|
|
17
|
+
branchHint: z.string().optional().describe("Optional suggested branch name. The spawned agent decides the actual name."),
|
|
18
|
+
});
|
|
19
|
+
const DEFAULT_BUDGET_TOKENS = 500_000;
|
|
20
|
+
export function registerPlanning(server, store, audit) {
|
|
21
|
+
// ---- create_plan ----------------------------------------------------------
|
|
22
|
+
server.tool("create_plan", "Register a fleet plan with orqlaude. Pass the user's root task description and the decomposed subtasks. Returns a plan_id used in all subsequent calls. Budget is in tokens (Max-plan friendly). Call this AFTER you've decided the work is parallelizable and written self-contained prompts.", {
|
|
23
|
+
root_task: z.string().min(1).describe("The user's original task description, for audit and history."),
|
|
24
|
+
tasks: z.array(TaskInputSchema).min(1).max(12).describe("The decomposed subtasks. Each becomes one spawned agent. Keep under ~6 unless you've discussed a larger fleet."),
|
|
25
|
+
budget_cap_tokens: z.number().int().positive().default(DEFAULT_BUDGET_TOKENS).describe("Hard ceiling for the whole fleet in tokens. Per-agent cap is derived as budget_cap_tokens / tasks.length. Default 500k tokens (~5 agents × 100k each)."),
|
|
26
|
+
model_for_estimate: z.string().default("claude-sonnet-4-6").describe("Model assumed for cost estimation (informational USD only; doesn't control what spawn_task uses)."),
|
|
27
|
+
effort_multiplier: z.number().positive().default(1.0).describe("Rough difficulty multiplier. 0.5 trivial, 1 moderate, 2+ heavy refactors."),
|
|
28
|
+
}, audit.wrap("create_plan", async ({ root_task, tasks, budget_cap_tokens, model_for_estimate, effort_multiplier }) => {
|
|
29
|
+
const plan = await store.update((state) => {
|
|
30
|
+
const p = newPlan(root_task, budget_cap_tokens, tasks);
|
|
31
|
+
const est = estimateAgent(model_for_estimate, effort_multiplier);
|
|
32
|
+
p.estimatedTokens = est.tokens.totalEffective * p.tasks.length;
|
|
33
|
+
p.estimatedCostUsd = est.costUsd * p.tasks.length;
|
|
34
|
+
p.budgetCapUsd = p.budgetCapTokens / 25_000; // rough USD shadow
|
|
35
|
+
p.perAgentCapUsd = p.budgetCapUsd / p.tasks.length;
|
|
36
|
+
p.modelForEstimate = model_for_estimate;
|
|
37
|
+
p.effortMultiplier = effort_multiplier;
|
|
38
|
+
p.estimatedDurationSec = 60 * Math.max(2, Math.ceil(4 * effort_multiplier));
|
|
39
|
+
state.plans[p.id] = p;
|
|
40
|
+
return p;
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
content: [
|
|
44
|
+
{
|
|
45
|
+
type: "text",
|
|
46
|
+
text: JSON.stringify({
|
|
47
|
+
plan_id: plan.id,
|
|
48
|
+
task_count: plan.tasks.length,
|
|
49
|
+
budget_cap_tokens: plan.budgetCapTokens,
|
|
50
|
+
per_agent_cap_tokens: plan.perAgentCapTokens,
|
|
51
|
+
estimated_tokens: plan.estimatedTokens,
|
|
52
|
+
estimated_cost_usd: plan.estimatedCostUsd,
|
|
53
|
+
estimated_duration_sec: plan.estimatedDurationSec,
|
|
54
|
+
tasks: plan.tasks.map((t) => ({ id: t.id, title: t.title, tldr: t.tldr })),
|
|
55
|
+
next_step: "Call `request_approval` to build the user prompt.",
|
|
56
|
+
}, null, 2),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}, (_args, result) => ({ planId: tryGetPlanId(result) })));
|
|
61
|
+
// ---- estimate -------------------------------------------------------------
|
|
62
|
+
server.tool("estimate", "Refresh cost/time estimates for a plan with a different model or effort multiplier.", {
|
|
63
|
+
plan_id: z.string(),
|
|
64
|
+
model: z.string().optional(),
|
|
65
|
+
effort_multiplier: z.number().positive().optional(),
|
|
66
|
+
}, audit.wrap("estimate", async ({ plan_id, model, effort_multiplier }) => {
|
|
67
|
+
const result = await store.update((state) => {
|
|
68
|
+
const plan = findPlan(state, plan_id);
|
|
69
|
+
const m = model ?? plan.modelForEstimate ?? "claude-sonnet-4-6";
|
|
70
|
+
const e = effort_multiplier ?? plan.effortMultiplier ?? 1;
|
|
71
|
+
const est = estimateAgent(m, e);
|
|
72
|
+
plan.estimatedTokens = est.tokens.totalEffective * plan.tasks.length;
|
|
73
|
+
plan.estimatedCostUsd = est.costUsd * plan.tasks.length;
|
|
74
|
+
plan.estimatedDurationSec = 60 * Math.max(2, Math.ceil(4 * e));
|
|
75
|
+
plan.modelForEstimate = m;
|
|
76
|
+
plan.effortMultiplier = e;
|
|
77
|
+
return {
|
|
78
|
+
model: m,
|
|
79
|
+
effort_multiplier: e,
|
|
80
|
+
per_agent_tokens: est.tokens.totalEffective,
|
|
81
|
+
per_agent_cost_usd: est.costUsd,
|
|
82
|
+
total_tokens: plan.estimatedTokens,
|
|
83
|
+
total_cost_usd: plan.estimatedCostUsd,
|
|
84
|
+
budget_cap_tokens: plan.budgetCapTokens,
|
|
85
|
+
fits_budget: plan.estimatedTokens <= plan.budgetCapTokens,
|
|
86
|
+
estimated_duration_sec: plan.estimatedDurationSec,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
90
|
+
}, ({ plan_id }) => ({ planId: plan_id })));
|
|
91
|
+
// ---- request_approval -----------------------------------------------------
|
|
92
|
+
server.tool("request_approval", "Generate an approval payload for the primary Claude to relay via AskUserQuestion. Returns an approval_token to pass back to `confirm` once the user OKs. Also surfaces remaining daily-token quota from the Desktop app.", { plan_id: z.string() }, audit.wrap("request_approval", async ({ plan_id }) => {
|
|
93
|
+
const daily = await readDailyTokenUsage();
|
|
94
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
95
|
+
const usedToday = daily && daily.date === today ? daily.tokens : 0;
|
|
96
|
+
const result = await store.update((state) => {
|
|
97
|
+
const plan = findPlan(state, plan_id);
|
|
98
|
+
plan.status = "awaiting_approval";
|
|
99
|
+
plan.approvalToken = randomUUID();
|
|
100
|
+
const tokenCostSummary = `~${Math.round((plan.estimatedTokens ?? 0) / 1000)}k tokens (cap ${Math.round(plan.budgetCapTokens / 1000)}k)`;
|
|
101
|
+
const usdShadow = plan.estimatedCostUsd ? ` (informational: ~$${plan.estimatedCostUsd.toFixed(2)})` : "";
|
|
102
|
+
const quotaLine = usedToday > 0
|
|
103
|
+
? `\nYou've used ~${Math.round(usedToday / 1000)}k tokens today across all Claude Code sessions.`
|
|
104
|
+
: "";
|
|
105
|
+
return {
|
|
106
|
+
approval_token: plan.approvalToken,
|
|
107
|
+
summary: `Spawn ${plan.tasks.length} parallel agents, estimated ${tokenCostSummary}${usdShadow}, ~${Math.round((plan.estimatedDurationSec ?? 240) / 60)} min wall-time.`,
|
|
108
|
+
daily_token_usage: { date: today, used: usedToday },
|
|
109
|
+
tasks: plan.tasks.map((t) => ({ title: t.title, tldr: t.tldr })),
|
|
110
|
+
ask_user_question: {
|
|
111
|
+
question: `Approve spawning ${plan.tasks.length} parallel agents? Estimated ${tokenCostSummary}.${quotaLine}`,
|
|
112
|
+
header: "Spawn fleet",
|
|
113
|
+
options: [
|
|
114
|
+
{ label: "Approve and spawn", description: `${plan.tasks.length} agents. Each opens its own session/worktree/PR.` },
|
|
115
|
+
{ label: "Cancel", description: "Don't spawn. The plan stays draft so you can revise." },
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
next_step: "Show the user the question via AskUserQuestion. If they approve, call `confirm` with this approval_token.",
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
122
|
+
}, ({ plan_id }) => ({ planId: plan_id })));
|
|
123
|
+
// ---- confirm --------------------------------------------------------------
|
|
124
|
+
server.tool("confirm", "Confirm an approved plan, locking it for dispatch. Call this AFTER the user explicitly approved the spawn. The approval_token from request_approval is required and is consumed on use.", {
|
|
125
|
+
plan_id: z.string(),
|
|
126
|
+
approval_token: z.string(),
|
|
127
|
+
}, audit.wrap("confirm", async ({ plan_id, approval_token }) => {
|
|
128
|
+
const result = await store.update((state) => {
|
|
129
|
+
const plan = findPlan(state, plan_id);
|
|
130
|
+
if (plan.status !== "awaiting_approval") {
|
|
131
|
+
throw new Error(`Plan ${plan_id} is not awaiting approval (status=${plan.status}).`);
|
|
132
|
+
}
|
|
133
|
+
if (plan.approvalToken !== approval_token) {
|
|
134
|
+
throw new Error("Approval token mismatch.");
|
|
135
|
+
}
|
|
136
|
+
plan.status = "approved";
|
|
137
|
+
plan.approvedAt = Date.now();
|
|
138
|
+
plan.approvalToken = undefined;
|
|
139
|
+
return {
|
|
140
|
+
plan_id,
|
|
141
|
+
status: plan.status,
|
|
142
|
+
approved_at: plan.approvedAt,
|
|
143
|
+
next_step: "Loop: `next_task` → `mcp__ccd_session__spawn_task` (with the returned title/prompt/tldr) → the spawned agent will self-register on first turn via checkin (no register_spawn needed unless that doesn't happen).",
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
147
|
+
}, ({ plan_id }) => ({ planId: plan_id })));
|
|
148
|
+
}
|
|
149
|
+
function tryGetPlanId(result) {
|
|
150
|
+
try {
|
|
151
|
+
const text = result?.content?.[0]?.text;
|
|
152
|
+
if (typeof text !== "string")
|
|
153
|
+
return undefined;
|
|
154
|
+
return JSON.parse(text).plan_id;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=planning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planning.js","sourceRoot":"","sources":["../../src/tools/planning.ts"],"names":[],"mappings":"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"}
|