agentlas 1.0.47 → 1.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +4 -2
- package/bin/agentlas.cjs +10 -0
- package/engine/acp/server.cjs +279 -0
- package/engine/agentlas-capabilities.cjs +2 -1
- package/engine/agentlas-input.cjs +2 -1
- package/engine/agentlas-native-host.cjs +6 -0
- package/engine/agentlas-onboard.cjs +2 -1
- package/engine/agentlas-sqlite-policy.cjs +9 -0
- package/engine/automation/daemon.cjs +3 -7
- package/engine/bootstrap-schema.sql +1037 -1010
- package/engine/cloud-assets/package.cjs +11 -3
- package/engine/cloud-assets/upload-scan-catalog.generated.cjs +102 -0
- package/engine/commands/acp.cjs +45 -0
- package/engine/commands/billing.cjs +2 -2
- package/engine/commands/call.cjs +4 -0
- package/engine/commands/doctor.cjs +2 -1
- package/engine/commands/index.cjs +2 -0
- package/engine/commands/workforce.cjs +11 -0
- package/engine/core/db.cjs +53 -1
- package/engine/core/desktop-core.cjs +108 -4
- package/engine/core/store-schema.cjs +119 -0
- package/engine/firms/orchestrate.cjs +32 -1
- package/engine/project/memory-context.cjs +7 -0
- package/engine/runtimes/acp-driver.cjs +96 -0
- package/engine/runtimes/detect.cjs +3 -13
- package/engine/runtimes/kinds.cjs +84 -0
- package/engine/runtimes/resolve.cjs +40 -7
- package/engine/ui/commands-catalog.cjs +2 -0
- package/engine/ui/palette.cjs +2 -1
- package/engine/ui/repl.cjs +2 -1
- package/engine/ui/shell.cjs +38 -3
- package/engine/vendor/desktop-core.manifest.json +5 -5
- package/engine/workforce/capture.cjs +4 -8
- package/engine/workforce/deps.cjs +7 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
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
|
+
|
|
3
19
|
## 1.0.47 — 2026-08-14
|
|
4
20
|
|
|
5
21
|
Antigravity and the shared runtime contracts now reach the independent
|
package/README.md
CHANGED
|
@@ -228,7 +228,9 @@ The launcher (`bin/agentlas.cjs`) runs system Node against `engine/`. The defaul
|
|
|
228
228
|
| Windows | `%APPDATA%\Agentlas` |
|
|
229
229
|
| Linux | `$XDG_CONFIG_HOME/Agentlas` (default `~/.config/Agentlas`) |
|
|
230
230
|
|
|
231
|
-
The SQLite database file is `agentlas.sqlite` (`user_version=
|
|
231
|
+
The SQLite database file is `agentlas.sqlite` (`user_version=97`). When launched for the first time without an existing database, it bootstraps schemas using `engine/bootstrap-schema.sql`. Consequently, **projects, installed agents, task history, automation sessions, and MCP registrations are shared across Desktop and Terminal**. The first ordered project agent remains the controller; additional agents are task-scoped and are stored only as execution ledgers, never as global conversations or durable owners.
|
|
232
|
+
|
|
233
|
+
**Single migration authority.** The Desktop app owns the schema migration ladder; the CLI never migrates the shared database. `engine/bootstrap-schema.sql` is generated by running that ladder to completion against an empty database, so a CLI-created store already sits at the ladder head and has nothing left for Desktop to upgrade. If the CLI opens a store older than the version it knows, it refuses with an actionable message instead of migrating or silently proceeding — a second migrator on this lock-free file is what corrupted the store once before. On a machine with no Desktop app, an operator can set `AGENTLAS_STORE_MIGRATION_ROLE=owner` for a single deliberate upgrade run with every other Agentlas process closed.
|
|
232
234
|
|
|
233
235
|
SQLite driver priority: `better-sqlite3` (optional dependency native build), then Node 22+ `node:sqlite` when the first driver is unavailable.
|
|
234
236
|
|
|
@@ -289,7 +291,7 @@ Engine source code resides in `engine/*.cjs`.
|
|
|
289
291
|
```sh
|
|
290
292
|
sh test/smoke.sh # Runs surface tests, guard tests, fresh DB tests, contract tests & parity gates
|
|
291
293
|
npm run smoke # Equivalent to npm run test:release-contracts
|
|
292
|
-
|
|
294
|
+
node scripts/gen-bootstrap-schema.cjs # Regenerates engine/bootstrap-schema.sql from the Desktop ladder (never reads a live store)
|
|
293
295
|
```
|
|
294
296
|
|
|
295
297
|
Smoke tests run isolated inside a temporary `AGENTLAS_USER_DATA_DIR` and do not touch local user data.
|
package/bin/agentlas.cjs
CHANGED
|
@@ -155,12 +155,22 @@ function bootstrapDbIfMissing() {
|
|
|
155
155
|
throw new Error(`Bootstrap schema not found: ${schemaFile}`);
|
|
156
156
|
}
|
|
157
157
|
const sql = fs.readFileSync(schemaFile, "utf8");
|
|
158
|
+
// ★새 저장소는 태어날 때부터 WAL 이어야 한다 (2026-08-18 실측).
|
|
159
|
+
// journal_mode 는 파일에 박히는 값이고, delete→WAL 전환은 **배타 락**을 요구하며
|
|
160
|
+
// busy_timeout 이 걸려 있어도 즉시 SQLITE_BUSY 로 실패할 수 있다. 그래서 터미널이
|
|
161
|
+
// 만든 DB 를 delete 모드로 남기면, 데스크탑과 터미널이 그 파일을 처음 동시에 여는
|
|
162
|
+
// 순간 한쪽이 "database is locked" 로 죽는다 — 첫 부팅에서만 나는, 재현이 어려운
|
|
163
|
+
// 결함이다. 여기서 한 번 켜 두면 그 전환 자체가 존재하지 않는다.
|
|
164
|
+
const WAL_PRAGMA = "PRAGMA journal_mode=WAL;\n";
|
|
158
165
|
// DB를 정식 경로에서 직접 만들면 두 첫 실행이 모두 exists=false를 본 뒤 한 프로세스의
|
|
159
166
|
// 실패 cleanup이 다른 프로세스의 정상 DB까지 지울 수 있다. 각자 같은 볼륨의 임시 DB를
|
|
160
167
|
// 완성하고 hard-link(EEXIST=다른 프로세스 승리)로만 정식 이름을 원자 획득한다.
|
|
161
168
|
const temp = `${p}.bootstrap-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`;
|
|
162
169
|
const db = openSqlite(temp);
|
|
163
170
|
try {
|
|
171
|
+
// 임시 파일에서 켠다. journal_mode 는 DB 헤더에 박히므로 hard-link 로 정식 이름을
|
|
172
|
+
// 얻은 뒤에도 유지된다(닫을 때 -wal 은 체크포인트되고 사라진다).
|
|
173
|
+
db.exec(WAL_PRAGMA);
|
|
164
174
|
db.exec(sql);
|
|
165
175
|
} catch (e) {
|
|
166
176
|
db.close();
|
|
@@ -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 };
|
|
@@ -26,7 +26,8 @@ const RUNTIME_CAPS = {
|
|
|
26
26
|
|
|
27
27
|
// NOTE: grok은 CAPS(멀티모달 능력 인지)만 등록 — 터미널 스폰 러너(RUNTIME_BIN)가 아직 없어
|
|
28
28
|
// CLI_KINDS에 넣으면 repl의 which(RUNTIME_BIN[k]) 탐지가 깨진다. 러너 추가 시 함께 확장할 것.
|
|
29
|
-
|
|
29
|
+
// 목록의 정본은 runtimes/kinds.cjs — 여기서는 네이티브 스폰 러너 4종만 가져다 쓴다.
|
|
30
|
+
const CLI_KINDS = require("./runtimes/kinds.cjs").NATIVE_CLI_KINDS;
|
|
30
31
|
|
|
31
32
|
function capsFor(spec) {
|
|
32
33
|
return RUNTIME_CAPS[spec] || { code: true, image: false, label: spec || "?" };
|
|
@@ -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
|
-
|
|
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 = {
|
|
@@ -19,6 +19,7 @@ const fs = require("node:fs");
|
|
|
19
19
|
const os = require("node:os");
|
|
20
20
|
const path = require("node:path");
|
|
21
21
|
const permissions = require("./agentlas-permissions.cjs");
|
|
22
|
+
const acpDriver = require("./runtimes/acp-driver.cjs");
|
|
22
23
|
const i18n = require("./agentlas-i18n.cjs");
|
|
23
24
|
const { wrapStdioServer } = require("./agentlas-mcp-env.cjs");
|
|
24
25
|
|
|
@@ -915,6 +916,11 @@ function handleGeminiLine(line, st, ui) {
|
|
|
915
916
|
function runNativeTurn(req) {
|
|
916
917
|
const { kind, bin, ui } = req;
|
|
917
918
|
const cwd = req.cwd;
|
|
919
|
+
// kimi/grok/cursor: 손코딩 3번째 대신 벤더 코어의 공용 ACP 러너로 (PRD 2026-08-15 T-2).
|
|
920
|
+
// 같은 결과 계약({text, session, usage, error, errorKind, errorSource})으로 돌아온다.
|
|
921
|
+
if (acpDriver.ACP_KINDS.has(kind)) {
|
|
922
|
+
return acpDriver.runAcpTurn(req);
|
|
923
|
+
}
|
|
918
924
|
let launchReq = req;
|
|
919
925
|
if (
|
|
920
926
|
kind === "gemini" && permissions.normalize(req.permission) === "full" &&
|
|
@@ -133,7 +133,8 @@ async function runOnboard({ ui, rl, helpers, persist }) {
|
|
|
133
133
|
// Step 2 — default runtime
|
|
134
134
|
ui.line("");
|
|
135
135
|
printIndented(ui.t("wiz.runtimeQ"), c.bold);
|
|
136
|
-
|
|
136
|
+
// 위저드 선택지 = 네이티브 스폰 러너 4종 (정본 runtimes/kinds.cjs, 표시 순서 포함).
|
|
137
|
+
const cliKinds = require("./runtimes/kinds.cjs").NATIVE_CLI_KINDS;
|
|
137
138
|
const rtOpts = [{ value: "auto", label: ui.t("wiz.runtimeAuto") }];
|
|
138
139
|
for (const k of cliKinds) {
|
|
139
140
|
const has = !!H.which(H.RUNTIME_BIN[k]);
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
// lock waits bounded, and acquire writer authority before a transaction reads
|
|
5
5
|
// state that it may subsequently update. A deferred read-to-write upgrade can
|
|
6
6
|
// fail immediately with SQLITE_BUSY even when busy_timeout is configured.
|
|
7
|
+
//
|
|
8
|
+
// ★This value must stay identical to `STORE_BUSY_TIMEOUT_MS` in
|
|
9
|
+
// agentlas_desktop/electron/store/db.ts. Until 2026-08-18 the Desktop waited 5s
|
|
10
|
+
// and the terminal 15s on the very same file, so under contention the Desktop
|
|
11
|
+
// was always the first to give up with SQLITE_BUSY — even when the terminal was
|
|
12
|
+
// the slow writer. That asymmetry made shared-file contention look like a
|
|
13
|
+
// Desktop-only bug. 15s is the agreed value: nothing holds a transaction on this
|
|
14
|
+
// file for long (the longest writer is the migration ladder), so it is a ceiling
|
|
15
|
+
// that is essentially never reached rather than added latency.
|
|
7
16
|
const SQLITE_BUSY_TIMEOUT_MS = 15_000;
|
|
8
17
|
|
|
9
18
|
// `foreign_keys` 는 파일이 아니라 **커넥션** 속성이다. 데스크탑은
|
|
@@ -24,13 +24,9 @@ const store = require("./store.cjs");
|
|
|
24
24
|
// getAutomationExecutionContractState 동형) ──────────────────────────────
|
|
25
25
|
// 손상된/미래 계약 값은 절대 조용히 넓혀 실행하지 않는다 — raw-row 게이트로
|
|
26
26
|
// 무인 실행 직전에 검사한다(데스크탑 automation-scheduler.ts:538-549).
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const RUNTIME_BACKENDS = new Set([
|
|
31
|
-
"anthropic", "openai", "google", "ollama", "lmstudio", "mlx", "upstage", "custom", "glm",
|
|
32
|
-
"kimi", "deepseek", "minimax", "xai", "openrouter", "cursor",
|
|
33
|
-
]);
|
|
27
|
+
const { CONTRACT_RUNTIME_KINDS, CONTRACT_RUNTIME_BACKENDS } = require("../runtimes/kinds.cjs");
|
|
28
|
+
const RUNTIME_KINDS = new Set(CONTRACT_RUNTIME_KINDS);
|
|
29
|
+
const RUNTIME_BACKENDS = new Set(CONTRACT_RUNTIME_BACKENDS);
|
|
34
30
|
const RUNTIME_SELECTION_KEYS = new Set(["kind", "backend", "source", "model", "longContext", "effort"]);
|
|
35
31
|
|
|
36
32
|
function decodeRuntimeSelection(raw) {
|