agentlas 1.0.46 → 1.0.48

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +7 -7
  3. package/engine/acp/server.cjs +279 -0
  4. package/engine/agentlas-capabilities.cjs +4 -2
  5. package/engine/agentlas-core-harness.cjs +18 -0
  6. package/engine/agentlas-i18n.cjs +8 -8
  7. package/engine/agentlas-input.cjs +3 -2
  8. package/engine/agentlas-native-host.cjs +130 -11
  9. package/engine/agentlas-onboard.cjs +9 -3
  10. package/engine/agentlas-permissions.cjs +5 -1
  11. package/engine/agentlas-workforce.cjs +81 -24
  12. package/engine/agents/router.cjs +4 -2
  13. package/engine/architecture.data.json +6 -30
  14. package/engine/automation/daemon.cjs +3 -7
  15. package/engine/bootstrap-schema.sql +216 -191
  16. package/engine/browser/cdp.cjs +10 -4
  17. package/engine/cloud-assets/commands.cjs +1 -1
  18. package/engine/cloud-assets/package.cjs +161 -45
  19. package/engine/commands/acp.cjs +45 -0
  20. package/engine/commands/billing.cjs +2 -2
  21. package/engine/commands/call.cjs +4 -0
  22. package/engine/commands/context.cjs +14 -3
  23. package/engine/commands/doctor.cjs +8 -4
  24. package/engine/commands/graph.cjs +46 -54
  25. package/engine/commands/index.cjs +2 -0
  26. package/engine/commands/search.cjs +2 -2
  27. package/engine/commands/workforce.cjs +11 -0
  28. package/engine/core/desktop-core.cjs +93 -1
  29. package/engine/firms/orchestrate.cjs +32 -1
  30. package/engine/graph/interview.cjs +2 -11
  31. package/engine/graph/vocabulary.generated.cjs +1 -1
  32. package/engine/hephaestus/runtime.cjs +2 -6
  33. package/engine/project/memory-context.cjs +20 -7
  34. package/engine/project/seed.cjs +46 -31
  35. package/engine/project/state.cjs +8 -1
  36. package/engine/runtimes/acp-driver.cjs +96 -0
  37. package/engine/runtimes/auth-evidence.cjs +6 -0
  38. package/engine/runtimes/detect.cjs +3 -13
  39. package/engine/runtimes/kinds.cjs +84 -0
  40. package/engine/runtimes/resolve.cjs +67 -14
  41. package/engine/sessions/prompt.cjs +2 -2
  42. package/engine/ui/commands-catalog.cjs +2 -0
  43. package/engine/ui/palette.cjs +2 -1
  44. package/engine/ui/repl.cjs +4 -3
  45. package/engine/ui/shell.cjs +43 -5
  46. package/engine/vendor/desktop-core.manifest.json +5 -5
  47. package/engine/workforce/capture.cjs +55 -10
  48. package/engine/workforce/deps.cjs +2 -2
  49. package/engine/workforce/local-core-transport.cjs +13 -19
  50. package/package.json +2 -1
  51. package/engine/project/super-ontology-seed.json +0 -3288
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ - `agentlas acp` — Agentlas as an Agent Client Protocol agent (Phase B-3). Zed,
6
+ JetBrains IDEs and other ACP clients can run the project controller (or an
7
+ installed agent via `_meta.agentlas.agent`) on the runtime you subscribe to,
8
+ with tool calls and streamed text projected onto ACP. `--info` prints the
9
+ registry-style descriptor. CLI-only (stdout is the wire).
10
+ - kimi, grok, and cursor run through the desktop core's generic ACP runner
11
+ (`engine/runtimes/acp-driver.cjs`) instead of being refused as "no v2
12
+ streaming driver". Same file, same tool-call vocabulary as Desktop; an old
13
+ core without `electron/runtime/acp.js` still refuses honestly with a repair
14
+ hint. `agentlas doctor` lists the newly executable runtimes.
15
+ - `npm test` is the smoke gate; the stale `.internal` copy of the architecture
16
+ sync script is gone (one canonical script under `scripts/`).
17
+
18
+
19
+ ## 1.0.47 — 2026-08-14
20
+
21
+ Antigravity and the shared runtime contracts now reach the independent
22
+ Terminal surface.
23
+
24
+ - `agy` is recognized as a first-class runtime in capabilities, detection,
25
+ resolution, permissions, and the interactive shell.
26
+ - Terminal model and runtime choices use the same runtime contract as Desktop,
27
+ including honest no-runtime stops and project-scoped execution.
28
+ - Workforce capture, local Core transport, graph commands, and the native host
29
+ preserve the synchronized Agentlas architecture without copying Desktop-only
30
+ SaaS state into Terminal.
31
+
32
+ Verification: the local smoke suite passed 100 cases with 0 failures. The npm
33
+ publication gate remains the authority for whether this tag is visible in the
34
+ registry.
35
+
3
36
  ## 1.0.46 — 2026-08-11
4
37
 
5
38
  Pick from a list instead of retyping a slug.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝
10
10
  ```
11
11
 
12
- **Agentlas Terminal CLI** — Independent AI agent runtime installed via `npm install -g agentlas` and launched with `agentlas`. Run exact installed agents, teams, and parallel sessions directly from your shell without keeping Desktop open. When Desktop is installed, Terminal reads the same local project, agent, and runtime state. Bring your own model — use your existing Claude Code, Codex, or Gemini CLI subscriptions or BYOK API keys.
12
+ **Agentlas Terminal CLI** — Independent AI agent runtime installed via `npm install -g agentlas` and launched with `agentlas`. Run exact installed agents, teams, and parallel sessions directly from your shell without keeping Desktop open. When Desktop is installed, Terminal reads the same local project, agent, and runtime state. Bring your own model — use Antigravity (`agy`), Claude Code, Codex, legacy Gemini CLI, or BYOK API keys.
13
13
 
14
14
  Use this existing independent Terminal as the command-line runtime: inspect owner-private packages with `agentlas cloud list`, restore one with `agentlas cloud restore <slug>`, or install an eligible public package with `agentlas install <slug>`.
15
15
 
@@ -24,10 +24,10 @@ Agent Trust is our product principle: agent packages are treated as portable, ow
24
24
  | Category | Requirement |
25
25
  | --- | --- |
26
26
  | **Node.js** | **Node 22+ recommended.** `package.json` specifies `engines: ">=20"`, but if native optional dependency `better-sqlite3` build fails, the launcher falls back strictly to `node:sqlite` in Node 22+. Node 20 works when `better-sqlite3` native build succeeds. |
27
- | **Agent CLI** | Requires at least one supported runtime CLI (`claude`, `codex`, or `gemini`) in your `PATH`. Halts honestly with `no_runtime` if none are found (no fake model responses). |
27
+ | **Agent CLI** | Requires at least one supported runtime CLI: `agy` (Antigravity, preferred), `claude`, `codex`, or legacy `gemini` in your `PATH`. Halts honestly with `no_runtime` if none are found (no fake model responses). |
28
28
  | **OS** | macOS is verified by the current local release gate. Linux is covered by the public adapter/CI contract. A Windows launcher is provided, but this release does not claim independent end-to-end Windows verification. |
29
29
 
30
- *Note: `kimi`, `grok`, and `cursor-agent` are detected by diagnostics (`doctor`) but not yet executable (`has no v2 streaming driver yet`). Supported active runtimes are `claude-code`, `codex`, and `gemini`.*
30
+ *Note: `kimi`, `grok`, and `cursor-agent` are detected by diagnostics (`doctor`) but not yet executable (`has no v2 streaming driver yet`). Supported active runtimes are `claude-code`, `codex`, `agy` (Antigravity), and legacy `gemini`.*
31
31
 
32
32
  ## Installation
33
33
 
@@ -143,7 +143,7 @@ agentlas netadmin <sub> # Local network administration (init|status|re
143
143
  agentlas version | help
144
144
  ```
145
145
 
146
- Common flags across subcommands: `-p|--print`, `--runtime claude-code|codex|gemini`, `--permission read|write|full`.
146
+ Common flags across subcommands: `-p|--print`, `--runtime claude-code|codex|agy|gemini`, `--permission read|write|full`.
147
147
 
148
148
  ### Typo Guard & Desktop Surface Redirection
149
149
 
@@ -183,7 +183,7 @@ Executing `agentlas` with no arguments resolves the current folder to an Agentla
183
183
  | `/agents` · `/list` | List installed agents |
184
184
  | `/mcp` | List active MCP servers |
185
185
  | `/doctor` | Run runtime & database diagnostics |
186
- | `/runtime <kind>` | Set runtime for new sessions (`claude-code` \| `codex` \| `gemini`) |
186
+ | `/runtime <kind>` | Set runtime for new sessions (`claude-code` \| `codex` \| `agy` (Antigravity) \| legacy `gemini`) |
187
187
  | `/permission <level>` | Set permission level for new sessions (`read` \| `write` \| `full`) |
188
188
  | `/quit` · `/exit` | Exit REPL |
189
189
 
@@ -276,8 +276,8 @@ agentlas doctor # Checks database, PATH runtimes, active CLI drivers, and c
276
276
  agentlas --where # Outputs JSON diagnostic of launcher, engine, DB paths, driver, and Node version
277
277
  ```
278
278
 
279
- - **`no_runtime: no agent CLI found`**: Install `claude`, `codex`, or `gemini` CLI and add to `PATH`. Note that `kimi`/`grok`/`cursor-agent` are detected by `doctor` but do not yet have streaming execution drivers.
280
- - **`runtime '<kind>' has no v2 streaming driver yet`**: Specified `--runtime` is not supported for active execution. Supported values: `claude-code`, `codex`, `gemini`.
279
+ - **`no_runtime: no agent CLI found`**: Connect Antigravity and put `agy` on `PATH` first, or install `claude`/`codex`; `gemini` remains available as a legacy CLI. Note that `kimi`/`grok`/`cursor-agent` are detected by `doctor` but do not yet have streaming execution drivers.
280
+ - **`runtime '<kind>' has no v2 streaming driver yet`**: Specified `--runtime` is not supported for active execution. Supported values: `claude-code`, `codex`, `agy` (Antigravity), `gemini` (legacy).
281
281
  - **`Node vX — Node 22+ (node:sqlite) is required when better-sqlite3 is unavailable`**: `better-sqlite3` native build failed on Node 20/21. Upgrade to Node 22+ or install build tools for native compilation.
282
282
  - **`storm`/`context`/`hep` halting due to missing runtime**: Agentlas OS core runtime is missing. Install Agentlas OS or set `HEPHAESTUS_BIN=<path>`.
283
283
  - **`'xxx' is not an agentlas command`**: Typo guard intercepted an unrecognized command. Use `agentlas run -p "xxx"` to run it as a prompt.
@@ -0,0 +1,279 @@
1
+ "use strict";
2
+ /*
3
+ * acp/server — Agentlas as an ACP *agent* (PRD 2026-08-15 Phase B-3, plan-acp §2 (B)).
4
+ *
5
+ * `agentlas acp` speaks the Agent Client Protocol v1 on stdio (JSON-RPC 2.0, ndjson)
6
+ * as the AGENT side. Any ACP client — Zed, JetBrains IDEs, VS Code extensions,
7
+ * Emacs/neovim, other Agentlas surfaces — can now run Agentlas' project controller
8
+ * (the agent Desktop/Terminal already run for `agentlas run`) on the runtime the
9
+ * user subscribes to (Claude Code, Codex, Antigravity, …), inside their editor.
10
+ *
11
+ * BYOM stays true: Agentlas has no model of its own. A prompt turn is exactly an
12
+ * `agentlas run` turn (engine/sessions/session.cjs → native-host / ACP driver),
13
+ * projected onto ACP notifications:
14
+ * stream-delta → agent_message_chunk · tool → tool_call(completed) ·
15
+ * tool-result → tool_call_update · status → (dropped) · error → stopReason/refusal
16
+ * Nothing here executes anything a plain `agentlas run` would not; the same
17
+ * permission model (prefs.permission, default read for a remote client) applies.
18
+ *
19
+ * Wire choices, all measured against the registry matrix behaviour:
20
+ * protocolVersion 1 only (v2 is a draft) · authMethods [] (Agentlas login is a
21
+ * separate `agentlas login`; nothing to authenticate over ACP) ·
22
+ * session/cancel kills the child · unknown methods → -32601.
23
+ */
24
+ const readline = require("node:readline");
25
+ const path = require("node:path");
26
+
27
+ const PROTOCOL_VERSION = 1;
28
+
29
+ function pkgVersion() {
30
+ try { return require(path.join(__dirname, "..", "..", "package.json")).version || "0.0.0"; } catch { return "0.0.0"; }
31
+ }
32
+
33
+ function textOfPrompt(blocks) {
34
+ if (!Array.isArray(blocks)) return String(blocks || "");
35
+ return blocks.map((b) => {
36
+ if (!b || typeof b !== "object") return "";
37
+ if (b.type === "text") return String(b.text || "");
38
+ if (b.type === "resource" && b.resource && typeof b.resource.text === "string") return `\n[resource ${b.resource.uri || ""}]\n${b.resource.text}\n`;
39
+ if (b.type === "resource_link") return `[${b.name || "resource"}](${b.uri || ""})`;
40
+ return "";
41
+ }).join("");
42
+ }
43
+
44
+ /**
45
+ * Turn-execution boundary. Production uses engine/sessions (Orchestrator + project
46
+ * controller); contract tests inject a fake `runTurn` so no runtime is spawned.
47
+ *
48
+ * runTurn(ctx, {cwd, prompt, permission, runtimeKind?, sessionKey?, events}) →
49
+ * Promise<{ text, error, errorKind, session }>
50
+ * events: { onDelta(text), onTool(name, summary, id), onToolResult(text, ok, id), onStatus(text) }
51
+ * cancel(sessionKey) → void
52
+ */
53
+ function productionTurnRunner() {
54
+ const sessions = new Map(); // acp sessionId → { session, agent, runtime }
55
+ return {
56
+ async newSession(ctx, { cwd, runtimeKind, agentSlug }) {
57
+ const { projectCwd } = require("../project/paths.cjs");
58
+ const { resolveProjectController, withProjectControllerContext } = require("../project/controller.cjs");
59
+ const { resolveRuntimeForAgent } = require("../runtimes/overrides.cjs");
60
+ const { ensureTerminalProjectForExecutionCli } = require("../project/state.cjs");
61
+ const { findAgent } = require("../agents/registry.cjs");
62
+ const permissions = require("../agentlas-permissions.cjs");
63
+ const db = ctx.db();
64
+ const workdir = cwd || projectCwd();
65
+ // Same ladder as `agentlas run [agent]`: an explicit installed agent
66
+ // (session/new _meta.agentlas.agent) is the advanced direct call; otherwise
67
+ // the folder's project controller — and no silent substitution when neither.
68
+ let agent = null;
69
+ let resolved = null;
70
+ if (agentSlug) {
71
+ agent = findAgent(db, String(agentSlug));
72
+ if (!agent) throw new Error(`unknown agent: ${agentSlug} (agentlas agents lists installed agents)`);
73
+ } else {
74
+ resolved = resolveProjectController(db, workdir);
75
+ agent = withProjectControllerContext(resolved.controller, resolved.project);
76
+ }
77
+ const runtime = resolveRuntimeForAgent({ db, prefs: ctx.prefs, explicit: runtimeKind || null, role: "orchestrator", agentId: agent.id });
78
+ // A remote editor client is unattended: never widen beyond the saved preference, default read.
79
+ const permission = permissions.normalize((ctx.prefs && ctx.prefs.permission) || "read");
80
+ ensureTerminalProjectForExecutionCli(db, workdir, permission, "terminal-acp");
81
+ return { agent, runtime, permission, cwd: workdir, project: resolved ? resolved.project : null };
82
+ },
83
+ async runTurn(ctx, spec, prompt, events) {
84
+ const { Orchestrator } = require("../sessions/orchestrator.cjs");
85
+ const orch = new Orchestrator({ db: ctx.db(), lang: ctx.lang });
86
+ const session = orch.spawn({ agent: spec.agent, runtime: spec.runtime, permission: spec.permission, cwd: spec.cwd, title: prompt.slice(0, 60) });
87
+ sessions.set(spec.acpSessionId, session);
88
+ const listener = (ev) => {
89
+ try {
90
+ if (ev.type === "stream-delta") events.onDelta(ev.text);
91
+ else if (ev.type === "tool") events.onTool(ev.name, ev.summary, ev.id);
92
+ else if (ev.type === "tool-result") events.onToolResult(ev.text, ev.ok, ev.id);
93
+ else if (ev.type === "status") events.onStatus(ev.text);
94
+ } catch { /* a projection failure must not break the turn */ }
95
+ };
96
+ session.on("event", listener);
97
+ try {
98
+ const res = await session.send(prompt);
99
+ return {
100
+ text: (res && (res.finalText || res.text)) || "",
101
+ error: session.status === "failed" ? (session.lastError || (res && res.error) || "failed") : (res && res.error) || null,
102
+ errorKind: res && res.errorKind,
103
+ cancelled: session.status === "killed",
104
+ };
105
+ } finally {
106
+ session.removeListener("event", listener);
107
+ sessions.delete(spec.acpSessionId);
108
+ }
109
+ },
110
+ cancel(acpSessionId) {
111
+ const s = sessions.get(acpSessionId);
112
+ if (s) { try { s.kill(); } catch { /* best effort */ } }
113
+ },
114
+ };
115
+ }
116
+
117
+ class AcpAgentServer {
118
+ /**
119
+ * @param {object} opts { ctx, input?, output?, runner? }
120
+ * ctx: agentlas command ctx (db(), prefs, lang) — may be null in tests with a fake runner
121
+ */
122
+ constructor(opts) {
123
+ this.ctx = opts.ctx || null;
124
+ this.input = opts.input || process.stdin;
125
+ this.output = opts.output || process.stdout;
126
+ this.runner = opts.runner || productionTurnRunner();
127
+ this.sessions = new Map(); // sessionId → spec
128
+ this.initialized = false;
129
+ this.nextSessionSeq = 1;
130
+ this.closed = false;
131
+ }
132
+
133
+ send(obj) {
134
+ if (this.closed) return;
135
+ this.output.write(JSON.stringify(obj) + "\n");
136
+ }
137
+ notify(method, params) { this.send({ jsonrpc: "2.0", method, params }); }
138
+ reply(id, result) { this.send({ jsonrpc: "2.0", id, result }); }
139
+ error(id, code, message, data) { this.send({ jsonrpc: "2.0", id, error: { code, message, ...(data !== undefined ? { data } : {}) } }); }
140
+
141
+ start() {
142
+ const rl = readline.createInterface({ input: this.input, crlfDelay: Infinity });
143
+ rl.on("line", (line) => {
144
+ const trimmed = line.trim();
145
+ if (!trimmed) return;
146
+ let msg;
147
+ try { msg = JSON.parse(trimmed); } catch { return; }
148
+ if (!msg || typeof msg !== "object" || !msg.method) return;
149
+ void this.handle(msg);
150
+ });
151
+ rl.on("close", () => { this.closed = true; });
152
+ return new Promise((resolve) => rl.on("close", resolve));
153
+ }
154
+
155
+ async handle(msg) {
156
+ const { id, method } = msg;
157
+ const params = msg.params || {};
158
+ try {
159
+ switch (method) {
160
+ case "initialize": {
161
+ const requested = Number(params.protocolVersion);
162
+ if (requested !== PROTOCOL_VERSION) {
163
+ return this.error(id, -32602, `unsupported protocolVersion ${params.protocolVersion}; this agent speaks v${PROTOCOL_VERSION}`);
164
+ }
165
+ this.initialized = true;
166
+ return this.reply(id, {
167
+ protocolVersion: PROTOCOL_VERSION,
168
+ agentCapabilities: {
169
+ loadSession: false,
170
+ promptCapabilities: { image: false, audio: false, embeddedContext: true },
171
+ mcpCapabilities: { http: false, sse: false },
172
+ },
173
+ authMethods: [],
174
+ agentInfo: { name: "agentlas", title: "Agentlas", version: pkgVersion() },
175
+ });
176
+ }
177
+ case "authenticate":
178
+ return this.reply(id, {});
179
+ case "session/new": {
180
+ if (!this.initialized) return this.error(id, -32002, "initialize first");
181
+ const sessionId = `agentlas-${process.pid}-${this.nextSessionSeq++}`;
182
+ const meta = (params._meta && params._meta.agentlas) || params._meta || {};
183
+ const spec = await this.runner.newSession(this.ctx, { cwd: params.cwd, runtimeKind: meta.runtime, agentSlug: meta.agent });
184
+ spec.acpSessionId = sessionId;
185
+ this.sessions.set(sessionId, spec);
186
+ const rt = spec.runtime || {};
187
+ return this.reply(id, {
188
+ sessionId,
189
+ configOptions: [
190
+ {
191
+ id: "runtime", category: "mode", type: "select", name: "Runtime",
192
+ currentValue: rt.kind || "",
193
+ options: [{ value: rt.kind || "", name: `${rt.kind || "runtime"}${rt.model ? ` · ${rt.model}` : ""}` }],
194
+ },
195
+ ...(rt.model ? [{ id: "model", category: "model", type: "select", name: "Model", currentValue: rt.model, options: [{ value: rt.model, name: rt.model }] }] : []),
196
+ ],
197
+ _meta: {
198
+ agentlas: {
199
+ controller: spec.agent && (spec.agent.slug || spec.agent.name),
200
+ project: spec.project && spec.project.name,
201
+ permission: spec.permission,
202
+ runtime: rt.kind,
203
+ },
204
+ },
205
+ });
206
+ }
207
+ case "session/prompt": {
208
+ const spec = this.sessions.get(String(params.sessionId));
209
+ if (!spec) return this.error(id, -32602, "unknown sessionId");
210
+ const prompt = textOfPrompt(params.prompt).trim();
211
+ if (!prompt) return this.reply(id, { stopReason: "end_turn" });
212
+ const sid = spec.acpSessionId;
213
+ let streamed = false;
214
+ const events = {
215
+ onDelta: (text) => {
216
+ if (!text) return;
217
+ streamed = true;
218
+ this.notify("session/update", { sessionId: sid, update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text } } });
219
+ },
220
+ onTool: (name, summary, toolId) => {
221
+ this.notify("session/update", { sessionId: sid, update: {
222
+ sessionUpdate: "tool_call", toolCallId: String(toolId || `${name}-${Date.now()}`), title: [name, summary].filter(Boolean).join(" "),
223
+ kind: kindOfTool(name), status: "completed",
224
+ } });
225
+ },
226
+ onToolResult: (text, ok, toolId) => {
227
+ if (!toolId) return;
228
+ this.notify("session/update", { sessionId: sid, update: {
229
+ sessionUpdate: "tool_call_update", toolCallId: String(toolId), status: ok === false ? "failed" : "completed",
230
+ content: text ? [{ type: "content", content: { type: "text", text: String(text).slice(0, 4000) } }] : undefined,
231
+ } });
232
+ },
233
+ onStatus: () => {},
234
+ };
235
+ const res = await this.runner.runTurn(this.ctx, spec, prompt, events);
236
+ if (res && res.cancelled) return this.reply(id, { stopReason: "cancelled" });
237
+ if (res && res.error) {
238
+ const kind = String(res.errorKind || "");
239
+ if (kind === "refused") return this.reply(id, { stopReason: "refusal" });
240
+ if (kind === "auth") return this.error(id, -32000, `auth_required: ${res.error}`);
241
+ // Non-marker failures: surface the runtime's own words as the answer, then end the turn.
242
+ if (!streamed) events.onDelta(String(res.error));
243
+ return this.reply(id, { stopReason: "end_turn", _meta: { agentlas: { error: String(res.error), errorKind: kind || null } } });
244
+ }
245
+ if (!streamed && res && res.text) events.onDelta(res.text);
246
+ return this.reply(id, { stopReason: "end_turn" });
247
+ }
248
+ case "session/cancel": {
249
+ const spec = this.sessions.get(String(params.sessionId));
250
+ if (spec) this.runner.cancel(spec.acpSessionId);
251
+ return; // notification: no reply
252
+ }
253
+ case "session/set_mode":
254
+ case "session/set_model":
255
+ return this.error(id, -32601, `Method not found: ${method}`);
256
+ default:
257
+ if (id !== undefined) return this.error(id, -32601, `Method not found: ${method}`);
258
+ return;
259
+ }
260
+ } catch (e) {
261
+ if (id !== undefined) this.error(id, -32603, (e && e.message) || String(e));
262
+ }
263
+ }
264
+ }
265
+
266
+ function kindOfTool(name) {
267
+ const n = String(name || "").toLowerCase();
268
+ if (/^(bash|shell|exec|command|run)/.test(n)) return "execute";
269
+ if (/read|cat|view|open/.test(n)) return "read";
270
+ if (/edit|write|patch|apply|create/.test(n)) return "edit";
271
+ if (/delete|remove|rm/.test(n)) return "delete";
272
+ if (/move|rename/.test(n)) return "move";
273
+ if (/grep|search|find|glob/.test(n)) return "search";
274
+ if (/fetch|http|web|browse/.test(n)) return "fetch";
275
+ if (/think|plan/.test(n)) return "think";
276
+ return "other";
277
+ }
278
+
279
+ module.exports = { AcpAgentServer, PROTOCOL_VERSION, textOfPrompt, kindOfTool, productionTurnRunner };
@@ -15,6 +15,7 @@ const RUNTIME_CAPS = {
15
15
  "claude-code": { code: true, image: false, label: "claude" },
16
16
  codex: { code: true, image: true, label: "codex" }, // Imagen
17
17
  gemini: { code: true, image: true, label: "gemini" }, // nano-banana
18
+ agy: { code: true, image: true, label: "antigravity" },
18
19
  grok: { code: true, image: true, label: "grok" }, // Grok Imagine (generate_image/generate_video, 구독 키리스)
19
20
  anthropic: { code: true, image: false, label: "anthropic" },
20
21
  openai: { code: true, image: true, label: "openai" }, // gpt-image
@@ -25,7 +26,8 @@ const RUNTIME_CAPS = {
25
26
 
26
27
  // NOTE: grok은 CAPS(멀티모달 능력 인지)만 등록 — 터미널 스폰 러너(RUNTIME_BIN)가 아직 없어
27
28
  // CLI_KINDS에 넣으면 repl의 which(RUNTIME_BIN[k]) 탐지가 깨진다. 러너 추가 시 함께 확장할 것.
28
- const CLI_KINDS = ["claude-code", "codex", "gemini"];
29
+ // 목록의 정본은 runtimes/kinds.cjs 여기서는 네이티브 스폰 러너 4종만 가져다 쓴다.
30
+ const CLI_KINDS = require("./runtimes/kinds.cjs").NATIVE_CLI_KINDS;
29
31
 
30
32
  function capsFor(spec) {
31
33
  return RUNTIME_CAPS[spec] || { code: true, image: false, label: spec || "?" };
@@ -115,7 +117,7 @@ function clearImageJudgments() {
115
117
  function autoRuntimeFor(agent, { installedKinds, activeSpec }) {
116
118
  if (needsImage(agent)) {
117
119
  if (capsFor(activeSpec).image) return activeSpec;
118
- for (const k of ["gemini", "codex"]) if ((installedKinds || []).includes(k)) return k;
120
+ for (const k of ["agy", "gemini", "codex"]) if ((installedKinds || []).includes(k)) return k;
119
121
  }
120
122
  return activeSpec;
121
123
  }
@@ -17,6 +17,10 @@ const CORE_RUNTIME_MARKERS = [
17
17
  ["schemas", "workforce-work-order.schema.json"],
18
18
  ["schemas", "workforce-selection.schema.json"],
19
19
  ];
20
+ const CONTEXT_MAP_V3_RUNTIME_MARKERS = [
21
+ ["agentlas_cloud", "project_bootstrap.py"],
22
+ ["schemas", "context-index-policy.schema.json"],
23
+ ];
20
24
 
21
25
  const PY_BOOTSTRAP =
22
26
  "import os, runpy, sys; " +
@@ -116,6 +120,18 @@ function resolveCoreRuntimeRoot(explicitRoot, requiredMarkers = [], options = {}
116
120
  return resolveCoreRuntimeRootFromCandidates(runtimeRoots(explicitRoot), requiredMarkers, options);
117
121
  }
118
122
 
123
+ // Context Map is a versioned Core capability. Every Terminal caller that
124
+ // injects a slice must use the same marker and minimum-version gate as the
125
+ // explicit `agentlas context` command; otherwise a stale runtime can silently
126
+ // supply a pre-v3 map to prompts, memory, or Workforce.
127
+ function resolveContextMapCoreRoot(explicitRoot) {
128
+ return resolveCoreRuntimeRoot(
129
+ explicitRoot,
130
+ CONTEXT_MAP_V3_RUNTIME_MARKERS,
131
+ { minVersion: CONTEXT_MAP_MIN_CORE_VERSION },
132
+ );
133
+ }
134
+
119
135
  function pythonCandidates() {
120
136
  return [...new Set([
121
137
  process.env.HEPHAESTUS_PYTHON,
@@ -262,10 +278,12 @@ module.exports = {
262
278
  HARNESS_ID,
263
279
  HARNESS_MODE,
264
280
  CONTEXT_MAP_MIN_CORE_VERSION,
281
+ CONTEXT_MAP_V3_RUNTIME_MARKERS,
265
282
  PY_BOOTSTRAP,
266
283
  readCoreRuntimeVersion,
267
284
  resolveCoreRuntimeRootFromCandidates,
268
285
  resolveCoreRuntimeRoot,
286
+ resolveContextMapCoreRoot,
269
287
  resolvePython,
270
288
  spawnCoreModule,
271
289
  parseJsonOutput,
@@ -62,9 +62,9 @@ const STRINGS = {
62
62
  "noKey": "No %s API key. Add one in the app (Settings → BYOK), or switch with /runtime.",
63
63
  "runtimeSet": "runtime → %s",
64
64
  "runtimeNotInstalled": "%s CLI is not installed.",
65
- "runtimeUnknown": "Unknown runtime: %s (claude-code|codex|gemini)",
66
- "runtimeUnavailable": "No runtime is available. Install a CLI (claude/codex/gemini) or configure an API key/Ollama in the app.",
67
- "runtimeUsage": "usage: /runtime claude-code|codex|gemini|anthropic|openai|google|ollama|upstage",
65
+ "runtimeUnknown": "Unknown runtime: %s (claude-code|codex|agy|gemini)",
66
+ "runtimeUnavailable": "No runtime is available. Install a CLI (claude/codex/agy; legacy gemini is supported) or configure an API key/Ollama in the app.",
67
+ "runtimeUsage": "usage: /runtime claude-code|codex|agy|gemini|anthropic|openai|google|ollama|upstage",
68
68
  "modelOnlyApi": "/model only applies to BYOK/Ollama (api) runtimes.",
69
69
  "modelSet": "model → %s",
70
70
  "modelCurrent": "current model: %s",
@@ -166,7 +166,7 @@ const STRINGS = {
166
166
  "help.team": "view/assign each agent's LLM (auto-routed by capability)",
167
167
  "help.agent": "switch to another agent",
168
168
  "help.firms": "list companies / switch to one",
169
- "help.runtime": "switch runtime (claude-code|codex|gemini|anthropic|openai|google|ollama|upstage)",
169
+ "help.runtime": "switch runtime (claude-code|codex|agy|legacy gemini|anthropic|openai|google|ollama|upstage)",
170
170
  "help.model": "set the model (claude/codex/gemini alias or BYOK/Ollama id)",
171
171
  "help.effort": "set reasoning effort (low|medium|high|max|off|auto)",
172
172
  "help.permission": "what it may do (read|write|full)",
@@ -311,9 +311,9 @@ const STRINGS = {
311
311
  "noKey": "%s API 키가 없습니다. 앱 설정 → BYOK에서 등록하거나 /runtime으로 전환하세요.",
312
312
  "runtimeSet": "런타임 → %s",
313
313
  "runtimeNotInstalled": "%s CLI가 설치돼 있지 않습니다.",
314
- "runtimeUnknown": "알 수 없는 런타임: %s (claude-code|codex|gemini)",
315
- "runtimeUnavailable": "사용 가능한 런타임이 없습니다. CLI(claude/codex/gemini)를 설치하거나 앱에서 API 키/Ollama를 설정하세요.",
316
- "runtimeUsage": "사용법: /runtime claude-code|codex|gemini|anthropic|openai|google|ollama|upstage",
314
+ "runtimeUnknown": "알 수 없는 런타임: %s (claude-code|codex|agy|gemini)",
315
+ "runtimeUnavailable": "사용 가능한 런타임이 없습니다. CLI(claude/codex/agy, 레거시 gemini 지원)를 설치하거나 앱에서 API 키/Ollama를 설정하세요.",
316
+ "runtimeUsage": "사용법: /runtime claude-code|codex|agy|gemini|anthropic|openai|google|ollama|upstage",
317
317
  "modelOnlyApi": "/model은 BYOK/Ollama(api) 런타임에서만 적용됩니다.",
318
318
  "modelSet": "모델 → %s",
319
319
  "modelCurrent": "현재 모델: %s",
@@ -414,7 +414,7 @@ const STRINGS = {
414
414
  "help.team": "에이전트별 LLM 보기/지정 (능력 기반 자동)",
415
415
  "help.agent": "다른 에이전트로 전환",
416
416
  "help.firms": "회사 목록 / 전환",
417
- "help.runtime": "런타임 전환 (claude-code|codex|gemini|anthropic|openai|google|ollama|upstage)",
417
+ "help.runtime": "런타임 전환 (claude-code|codex|agy|레거시 gemini|anthropic|openai|google|ollama|upstage)",
418
418
  "help.model": "모델 지정 (claude/codex/gemini 별칭 또는 BYOK/Ollama id)",
419
419
  "help.effort": "추론 강도 지정 (low|medium|high|max|off|auto)",
420
420
  "help.permission": "허용 범위 (read|write|full)",
@@ -270,7 +270,7 @@ const SLASH_COMMAND_META = [
270
270
  { command: "/agent", description: "Switch to another agent", category: "Routing", usage: "/agent <name>", detail: "Switch the current conversation to an installed agent." },
271
271
  { command: "/firms", description: "List installed companies", category: "Routing", usage: "/firms", detail: "Show company CEOs available in this terminal." },
272
272
  { command: "/firm", description: "Switch to a company CEO", category: "Routing", usage: "/firm <name>", detail: "Switch the current conversation to a company CEO agent." },
273
- { command: "/runtime", description: "Switch runtime: claude-code, codex, gemini, BYOK, or Ollama", category: "Settings", usage: "/runtime codex", detail: "Change the engine Agentlas uses for subsequent turns." },
273
+ { command: "/runtime", description: "Switch runtime: claude-code, codex, agy, legacy gemini, BYOK, or Ollama", category: "Settings", usage: "/runtime agy", detail: "Change the engine Agentlas uses for subsequent turns." },
274
274
  { command: "/model", description: "Pin a model or return to auto allocation", category: "Settings", usage: "/model <id|auto>", detail: "An id is an explicit session pin; auto lets the higher-level allocator choose a provider-compatible tier." },
275
275
  { command: "/effort", description: "Pin reasoning effort or return to auto", category: "Settings", usage: "/effort low|medium|high|max|off|auto", detail: "A level is an explicit pin; auto lets the higher-level allocator choose per task; off is an explicit no-effort pin." },
276
276
  { command: "/permission", description: "Set read/write/full permission", category: "Settings", usage: "/permission full", detail: "No argument shows what read, write, and full mean.", aliases: ["/perm"] },
@@ -306,7 +306,8 @@ const SLASH_COMMAND_META = [
306
306
  { command: "/exit", description: "Quit Agentlas", category: "Session", usage: "/exit", detail: "Close the terminal session.", aliases: ["/quit"] },
307
307
  ];
308
308
  const SLASH_COMMANDS = SLASH_COMMAND_META.flatMap((entry) => [entry.command].concat(entry.aliases || []));
309
- const RUNTIME_SPECS = ["claude-code", "codex", "gemini", "anthropic", "openai", "google", "ollama", "upstage"];
309
+ // /runtime 받는 spec 전체(네이티브 CLI + API 백엔드) — 정본은 runtimes/kinds.cjs.
310
+ const RUNTIME_SPECS = require("./runtimes/kinds.cjs").RUNTIME_SPECS;
310
311
  const PERM_LEVELS = ["read", "write", "full"];
311
312
 
312
313
  const HELP_KEY_BY_COMMAND = {