agentus 0.0.0-stage → 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 +202 -0
- package/NOTICE +11 -0
- package/README.md +459 -2
- package/bin/agentus.mjs +147 -0
- package/docs/android-notify-contract.md +658 -0
- package/docs/refs/aionui-acp-rules.md +46 -0
- package/docs/refs/hermes-studio-ui-ideas.md +191 -0
- package/docs/transcript-folding.md +139 -0
- package/package.json +75 -5
- package/packages/server/mcp/plan-server.mjs +217 -0
- package/packages/server/mock/agent.mjs +658 -0
- package/packages/server/package.json +22 -0
- package/packages/server/src/acp/backends.ts +196 -0
- package/packages/server/src/acp/errors.ts +46 -0
- package/packages/server/src/acp/registry.ts +579 -0
- package/packages/server/src/acp/session-manager.ts +1868 -0
- package/packages/server/src/auth.ts +688 -0
- package/packages/server/src/dashscope.ts +235 -0
- package/packages/server/src/fs.ts +165 -0
- package/packages/server/src/hotwords.ts +132 -0
- package/packages/server/src/index.ts +1404 -0
- package/packages/server/src/notify/center.ts +994 -0
- package/packages/server/src/notify/types.ts +222 -0
- package/packages/server/src/plan/plan.ts +126 -0
- package/packages/server/src/settings.ts +469 -0
- package/packages/server/src/store/export.ts +209 -0
- package/packages/server/src/store/store.ts +1238 -0
- package/packages/server/src/term.ts +160 -0
- package/packages/server/src/title.ts +109 -0
- package/packages/server/src/voice.ts +251 -0
- package/packages/shared/package.json +14 -0
- package/packages/shared/src/index.ts +271 -0
- package/packages/web/dist/assets/index-B52l55_4.css +1 -0
- package/packages/web/dist/assets/index-BRgC7oYt.js +246 -0
- package/packages/web/dist/coding-agents/hermes.png +0 -0
- package/packages/web/dist/coding-agents/qoder.svg +5 -0
- package/packages/web/dist/icons/agentus-icon-inverse.svg +301 -0
- package/packages/web/dist/icons/agentus-icon.svg +301 -0
- package/packages/web/dist/icons/agentus-mark-inverse.svg +301 -0
- package/packages/web/dist/icons/agentus-mark.svg +301 -0
- package/packages/web/dist/icons/apple-touch-icon-180.png +0 -0
- package/packages/web/dist/icons/favicon.ico +0 -0
- package/packages/web/dist/icons/icon-192.png +0 -0
- package/packages/web/dist/icons/icon-512.png +0 -0
- package/packages/web/dist/icons/icon-maskable-512.png +0 -0
- package/packages/web/dist/index.html +30 -0
- package/packages/web/dist/manifest.webmanifest +17 -0
- package/packages/web/dist/sw.js +61 -0
- package/scripts/make-cert.sh +101 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `agentus-plan` — the cockpit's plan tool, as a stdio MCP server.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS
|
|
6
|
+
* The cockpit owns a session's plan (design-plan-service.md §2-3) and an agent's own todo list
|
|
7
|
+
* dies with its process. So the plan the card renders is the cockpit's OBJECT, and this tool is
|
|
8
|
+
* how the agent keeps it current: every backend is handed it at handshake time (a row can still
|
|
9
|
+
* opt back into rendering its own ACP frames — `nativePlanSource: "acp"`). An agent needs **zero
|
|
10
|
+
* adapter code**: it just has to speak MCP.
|
|
11
|
+
*
|
|
12
|
+
* HOW IT IS REACHED (and why that is safe)
|
|
13
|
+
* Nothing registers this server in an agent's own config. The cockpit only ever hands it over as
|
|
14
|
+
* part of the ACP handshake (`session/new` / `session/load` / `session/resume` → `mcpServers`),
|
|
15
|
+
* which is a per-session parameter, so an agent started any other way (CLI, cron, Studio) does
|
|
16
|
+
* not have it — see design-plan-service.md §4. The same handshake carries the three variables
|
|
17
|
+
* below, including a token minted for THAT ONE session: even someone who copies the command out
|
|
18
|
+
* of `ps` can only write the plan of the session the token was minted for.
|
|
19
|
+
*
|
|
20
|
+
* PROTOCOL NOTES
|
|
21
|
+
* Transport is newline-delimited JSON-RPC 2.0 (what the MCP stdio transport mandates). stdout
|
|
22
|
+
* carries protocol frames ONLY — anything printed there that is not a JSON-RPC message breaks the
|
|
23
|
+
* client, so all diagnostics go to stderr.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const ENDPOINT = (process.env.AGENTUS_PLAN_ENDPOINT ?? "").replace(/\/+$/, "");
|
|
27
|
+
const TOKEN = process.env.AGENTUS_PLAN_TOKEN ?? "";
|
|
28
|
+
const SESSION = process.env.AGENTUS_PLAN_SESSION ?? "";
|
|
29
|
+
|
|
30
|
+
/** MCP revision this server implements. Cheap to keep current: we only use tools/*. */
|
|
31
|
+
const PROTOCOL_VERSION = "2024-11-05";
|
|
32
|
+
|
|
33
|
+
const STATUSES = ["pending", "in_progress", "completed", "cancelled"];
|
|
34
|
+
|
|
35
|
+
const TOOLS = [
|
|
36
|
+
{
|
|
37
|
+
name: "update_plan",
|
|
38
|
+
description:
|
|
39
|
+
"Publish this session's plan — the step list the operator watches in the Agentus cockpit, one " +
|
|
40
|
+
"progress dot per step. PREFER THIS over any checklist/todo tool of your own: this list is the " +
|
|
41
|
+
"one the cockpit DISPLAYS, and it survives your restarts, while a built-in task list dies with " +
|
|
42
|
+
"your process (the cockpit does not read it and will not show it). Call it when you START a " +
|
|
43
|
+
"multi-step task, and again each time a step's status changes — a plan that is not updated is a " +
|
|
44
|
+
"plan that lies. `items` is the WHOLE list as it stands now (a snapshot, not a delta): include " +
|
|
45
|
+
"every step, finished or not, in order. Statuses: pending, in_progress (at most one at a time), " +
|
|
46
|
+
"completed, cancelled.",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
items: {
|
|
51
|
+
type: "array",
|
|
52
|
+
description: "The full plan, in order. Replaces the previous list.",
|
|
53
|
+
items: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
content: { type: "string", description: "What the step is, in the operator's language." },
|
|
57
|
+
status: { type: "string", enum: STATUSES, description: "Where this step stands now." },
|
|
58
|
+
priority: { type: "string", enum: ["high", "medium", "low"], description: "Optional." },
|
|
59
|
+
},
|
|
60
|
+
required: ["content", "status"],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
explanation: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description:
|
|
66
|
+
"Optional one-line remark shown under the list: why the plan changed, or how far the " +
|
|
67
|
+
"scope reaches. Max 1000 characters.",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ["items"],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "read_plan",
|
|
75
|
+
description:
|
|
76
|
+
"Read the plan the cockpit currently holds for this session. Use it after a restart, or when " +
|
|
77
|
+
"the operator asks what is still outstanding, instead of trusting your own memory of it.",
|
|
78
|
+
inputSchema: { type: "object", properties: {} },
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
/** POST one operation to the cockpit, which owns the plan object. */
|
|
83
|
+
async function callCockpit(op, payload) {
|
|
84
|
+
if (!ENDPOINT || !TOKEN) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"this plan tool was started without AGENTUS_PLAN_ENDPOINT/AGENTUS_PLAN_TOKEN — it only works " +
|
|
87
|
+
"inside an Agentus session; keep your own todo list instead",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const res = await fetch(`${ENDPOINT}/api/plan/mcp`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: { "content-type": "application/json", "x-agentus-plan-token": TOKEN },
|
|
93
|
+
body: JSON.stringify({ session: SESSION, op, ...payload }),
|
|
94
|
+
});
|
|
95
|
+
const body = await res.json().catch(() => null);
|
|
96
|
+
if (!res.ok) throw new Error(body?.error ?? `the cockpit answered HTTP ${res.status}`);
|
|
97
|
+
return body;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Render the plan for the agent: compact, and honest about what is not there. */
|
|
101
|
+
function renderPlan(plan) {
|
|
102
|
+
if (!plan || !Array.isArray(plan.items) || plan.items.length === 0) {
|
|
103
|
+
return "This session has no plan yet. Publish one with update_plan when the work has steps.";
|
|
104
|
+
}
|
|
105
|
+
const lines = plan.items.map((i) => `- [${i.status}] ${i.content}`);
|
|
106
|
+
const head = `Plan (revision ${plan.revision ?? 0}${plan.source ? `, source ${plan.source}` : ""}${plan.terminal ? `, ${plan.terminal}` : ""}):`;
|
|
107
|
+
return [head, ...lines, plan.explanation ? `\nRemark: ${plan.explanation}` : ""].filter(Boolean).join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function callTool(name, args) {
|
|
111
|
+
if (name === "read_plan") {
|
|
112
|
+
const body = await callCockpit("read", {});
|
|
113
|
+
return renderPlan(body?.plan);
|
|
114
|
+
}
|
|
115
|
+
if (name === "update_plan") {
|
|
116
|
+
const items = Array.isArray(args?.items) ? args.items : null;
|
|
117
|
+
if (!items) throw new Error("`items` must be an array of steps");
|
|
118
|
+
const body = await callCockpit("update", {
|
|
119
|
+
items,
|
|
120
|
+
...(args?.explanation !== undefined ? { explanation: args.explanation } : {}),
|
|
121
|
+
});
|
|
122
|
+
const plan = body?.plan;
|
|
123
|
+
const done = plan?.items?.filter((i) => i.status === "completed").length ?? 0;
|
|
124
|
+
const total = plan?.items?.length ?? 0;
|
|
125
|
+
// Say something the model can act on, including the case where the cockpit IGNORED the write:
|
|
126
|
+
// a session already fed by native ACP frames has one writer, and this tool is not it. Silently
|
|
127
|
+
// succeeding there would leave the model believing it owns a list it does not.
|
|
128
|
+
if (body?.accepted === false) {
|
|
129
|
+
return `${body.reason ?? "ignored: this session's plan comes from the agent's own frames"}. The plan on screen is unchanged (${done}/${total} done).`;
|
|
130
|
+
}
|
|
131
|
+
return `Plan saved (${done}/${total} done${plan?.explanation ? `; remark kept` : ""}). It survives your restarts.`;
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`unknown tool: ${name}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---- the stdio loop -----------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
function send(msg) {
|
|
139
|
+
process.stdout.write(`${JSON.stringify(msg)}\n`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function reply(id, result) {
|
|
143
|
+
send({ jsonrpc: "2.0", id, result });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function replyError(id, code, message) {
|
|
147
|
+
send({ jsonrpc: "2.0", id, error: { code, message } });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function handle(msg) {
|
|
151
|
+
const { id, method, params } = msg;
|
|
152
|
+
// Notifications carry no id and must never be answered.
|
|
153
|
+
const isNotification = id === undefined || id === null;
|
|
154
|
+
switch (method) {
|
|
155
|
+
case "initialize":
|
|
156
|
+
reply(id, {
|
|
157
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
158
|
+
capabilities: { tools: {} },
|
|
159
|
+
serverInfo: { name: "agentus-plan", version: "1.0.0" },
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
case "notifications/initialized":
|
|
163
|
+
case "notifications/cancelled":
|
|
164
|
+
return;
|
|
165
|
+
case "ping":
|
|
166
|
+
reply(id, {});
|
|
167
|
+
return;
|
|
168
|
+
case "tools/list":
|
|
169
|
+
reply(id, { tools: TOOLS });
|
|
170
|
+
return;
|
|
171
|
+
case "tools/call": {
|
|
172
|
+
const name = String(params?.name ?? "");
|
|
173
|
+
try {
|
|
174
|
+
const text = await callTool(name, params?.arguments ?? {});
|
|
175
|
+
reply(id, { content: [{ type: "text", text }] });
|
|
176
|
+
} catch (e) {
|
|
177
|
+
// isError (not a JSON-RPC error): the model should see the failure as tool output and
|
|
178
|
+
// react — retrying a transport failure as if it were a protocol error helps nobody.
|
|
179
|
+
reply(id, {
|
|
180
|
+
content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }],
|
|
181
|
+
isError: true,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
if (!isNotification) replyError(id, -32601, `method not found: ${String(method)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let buffer = "";
|
|
192
|
+
process.stdin.setEncoding("utf8");
|
|
193
|
+
process.stdin.on("data", (chunk) => {
|
|
194
|
+
buffer += chunk;
|
|
195
|
+
for (;;) {
|
|
196
|
+
const nl = buffer.indexOf("\n");
|
|
197
|
+
if (nl < 0) break;
|
|
198
|
+
const line = buffer.slice(0, nl).trim();
|
|
199
|
+
buffer = buffer.slice(nl + 1);
|
|
200
|
+
if (!line) continue;
|
|
201
|
+
let msg;
|
|
202
|
+
try {
|
|
203
|
+
msg = JSON.parse(line);
|
|
204
|
+
} catch {
|
|
205
|
+
process.stderr.write(`agentus-plan: ignoring a line that is not JSON\n`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
handle(msg).catch((e) => {
|
|
209
|
+
process.stderr.write(`agentus-plan: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
210
|
+
if (msg?.id !== undefined && msg?.id !== null) replyError(msg.id, -32603, "internal error");
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
process.stdin.on("end", () => process.exit(0));
|
|
215
|
+
process.stderr.write(
|
|
216
|
+
`agentus-plan ready (session ${SESSION || "?"}, endpoint ${ENDPOINT || "unset"})\n`,
|
|
217
|
+
);
|