teapot-coding-agent 0.37.58 → 0.37.60
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/dist/acp/adapter.js +4 -72
- package/dist/agent/agent.js +52 -2246
- package/dist/agent/compact-rules.js +0 -53
- package/dist/agent/editorconfig.js +4 -56
- package/dist/agent/goal-audit.js +0 -34
- package/dist/agent/key-pool.js +0 -52
- package/dist/agent/kill.js +1 -13
- package/dist/agent/llm.js +8 -309
- package/dist/agent/mcp-oauth-manager.js +2 -255
- package/dist/agent/mcp-registry.js +1 -188
- package/dist/agent/mcp.js +1 -85
- package/dist/agent/meta-tools.js +0 -26
- package/dist/agent/plugins.js +1 -81
- package/dist/agent/provider-headers.js +1 -28
- package/dist/agent/skills.js +8 -94
- package/dist/agent/tools.js +16 -809
- package/dist/bus.js +1 -2
- package/dist/config-schema.js +0 -40
- package/dist/home.js +1 -19
- package/dist/index.js +0 -136
- package/dist/log/events.js +3 -195
- package/dist/master.js +31 -1176
- package/dist/migrations/index.js +0 -55
- package/dist/migrations/providers.js +0 -13
- package/dist/migrations/registry.js +0 -9
- package/dist/migrations/session-titles.js +3 -43
- package/dist/migrations/sessions.js +2 -20
- package/dist/model-meta.js +2 -74
- package/dist/scheduler/cron.js +2 -17
- package/dist/server/api.js +12 -778
- package/dist/server/image-resize.js +1 -64
- package/dist/server/term-size.js +1 -120
- package/dist/server/terminals.js +0 -159
- package/dist/session-title.js +2 -35
- package/package.json +1 -1
- package/public/assets/index-DhJx7VDq.js +36 -0
- package/public/assets/index-DhJx7VDq.js.map +1 -0
- package/public/assets/{shiki-BaLUHSNL.js → shiki-LBMkaWYb.js} +2 -2
- package/public/assets/shiki-LBMkaWYb.js.map +1 -0
- package/public/index.html +1 -1
- package/public/assets/index-D_f1aal3.js +0 -36
- package/public/assets/index-D_f1aal3.js.map +0 -1
- package/public/assets/shiki-BaLUHSNL.js.map +0 -1
package/dist/acp/adapter.js
CHANGED
|
@@ -1,29 +1,12 @@
|
|
|
1
1
|
import { readEvents } from "../log/events.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export const ACP_PROTOCOL_VERSION = 1;
|
|
4
|
-
/**
|
|
5
|
-
* How often a running turn is drained for new events.
|
|
6
|
-
*
|
|
7
|
-
* ACP has no push channel, so the turn is streamed by polling the agent's own
|
|
8
|
-
* log. 150ms is well under the ~60ms render tick a human notices, and the read is
|
|
9
|
-
* a tail of a file this process is already appending to.
|
|
10
|
-
*/
|
|
11
4
|
const STREAM_POLL_MS = 150;
|
|
12
5
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
13
|
-
/** JSON-RPC error codes we use (the standard set plus JSON-RPC's own). */
|
|
14
6
|
const PARSE_ERROR = -32700;
|
|
15
7
|
const METHOD_NOT_FOUND = -32601;
|
|
16
8
|
const INVALID_PARAMS = -32602;
|
|
17
9
|
const INTERNAL_ERROR = -32603;
|
|
18
|
-
/* ---------- streaming: log events → session/update notifications ---------- */
|
|
19
|
-
/**
|
|
20
|
-
* ACP `ToolCall.kind` for a teapot tool name.
|
|
21
|
-
*
|
|
22
|
-
* The schema's own enum (verified against schema/v1/schema.json):
|
|
23
|
-
* read, edit, delete, move, search, execute, fetch, think, switch_mode, other,
|
|
24
|
-
* description. Editors use this to pick an icon and to decide whether the call
|
|
25
|
-
* mutates anything, so mapping our tools onto it is worth doing properly.
|
|
26
|
-
*/
|
|
27
10
|
const TOOL_KINDS = {
|
|
28
11
|
read_file: "read",
|
|
29
12
|
list_dir: "read",
|
|
@@ -40,7 +23,6 @@ const TOOL_KINDS = {
|
|
|
40
23
|
memory: "edit",
|
|
41
24
|
get_memory: "read",
|
|
42
25
|
};
|
|
43
|
-
/** Filesystem tools whose `path` argument is worth surfacing as a location. */
|
|
44
26
|
const LOCATED_TOOLS = new Set([
|
|
45
27
|
"read_file",
|
|
46
28
|
"edit_file",
|
|
@@ -49,7 +31,6 @@ const LOCATED_TOOLS = new Set([
|
|
|
49
31
|
"file_edit",
|
|
50
32
|
"list_dir",
|
|
51
33
|
]);
|
|
52
|
-
/** a one-line title for a tool call, the way an editor shows it in a list */
|
|
53
34
|
function toolTitle(name, args) {
|
|
54
35
|
const first = (k) => {
|
|
55
36
|
const v = args[k];
|
|
@@ -61,7 +42,6 @@ function toolTitle(name, args) {
|
|
|
61
42
|
return first("pattern") ? `grep /${first("pattern")}/` : "grep";
|
|
62
43
|
return first("path") ?? name;
|
|
63
44
|
}
|
|
64
|
-
/** Absolute path for a tool's location, so an editor can open the file. */
|
|
65
45
|
function toolLocation(name, args, workspace) {
|
|
66
46
|
if (!LOCATED_TOOLS.has(name))
|
|
67
47
|
return [];
|
|
@@ -72,19 +52,10 @@ function toolLocation(name, args, workspace) {
|
|
|
72
52
|
const line = Number(args.line ?? args.line_number ?? NaN);
|
|
73
53
|
return [{ path: abs, ...(Number.isFinite(line) && line > 0 ? { line } : {}) }];
|
|
74
54
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Turn one logged event into the session/update payload for it, or null when the
|
|
77
|
-
* event has no ACP equivalent (and must therefore NOT be invented into one).
|
|
78
|
-
*
|
|
79
|
-
* Exported so the mapping is testable without a transport.
|
|
80
|
-
*/
|
|
81
55
|
export function updateForEvent(e, opts) {
|
|
82
56
|
const d = (e.data ?? {});
|
|
83
57
|
switch (e.type) {
|
|
84
58
|
case "message": {
|
|
85
|
-
// The completion AUDIT's verdict and the operator-facing summary are
|
|
86
|
-
// timeline content, not something the agent said to the model; an editor
|
|
87
|
-
// must not be shown an agent speaking a harness message.
|
|
88
59
|
if (d.final === true || d.operatorFacing === true)
|
|
89
60
|
return null;
|
|
90
61
|
const text = String(d.content ?? "");
|
|
@@ -124,9 +95,6 @@ export function updateForEvent(e, opts) {
|
|
|
124
95
|
};
|
|
125
96
|
}
|
|
126
97
|
default:
|
|
127
|
-
// state transitions, goals, todos, compactions, notes: all internal. The
|
|
128
|
-
// schema HAS update kinds for some of this (plan, usage_update), but
|
|
129
|
-
// emitting them with guessed payloads would be worse than silence.
|
|
130
98
|
return null;
|
|
131
99
|
}
|
|
132
100
|
}
|
|
@@ -136,9 +104,7 @@ export class AcpAdapter {
|
|
|
136
104
|
output;
|
|
137
105
|
defaultCwd;
|
|
138
106
|
sessions = new Map();
|
|
139
|
-
/** id -> notification params, so a prompt turn can stream updates */
|
|
140
107
|
turnUpdates = new Map();
|
|
141
|
-
/** sessions cancelled during the CURRENT turn, cleared when it starts (#15) */
|
|
142
108
|
cancelled = new Set();
|
|
143
109
|
buffer = "";
|
|
144
110
|
nextSession = 1;
|
|
@@ -149,7 +115,6 @@ export class AcpAdapter {
|
|
|
149
115
|
this.output = opts.output ?? process.stdout;
|
|
150
116
|
this.defaultCwd = opts.defaultCwd ?? process.cwd();
|
|
151
117
|
}
|
|
152
|
-
/** Attach to the stream. Resolves when stdin ends. */
|
|
153
118
|
listen() {
|
|
154
119
|
return new Promise((resolve) => {
|
|
155
120
|
this.input.on("data", (chunk) => this.onData(String(chunk)));
|
|
@@ -159,10 +124,8 @@ export class AcpAdapter {
|
|
|
159
124
|
this.input.resume();
|
|
160
125
|
});
|
|
161
126
|
}
|
|
162
|
-
/* ---------- transport ---------- */
|
|
163
127
|
onData(chunk) {
|
|
164
128
|
this.buffer += chunk;
|
|
165
|
-
// NDJSON: one JSON object per line
|
|
166
129
|
let nl;
|
|
167
130
|
while ((nl = this.buffer.indexOf("\n")) >= 0) {
|
|
168
131
|
const line = this.buffer.slice(0, nl).trim();
|
|
@@ -177,7 +140,6 @@ export class AcpAdapter {
|
|
|
177
140
|
msg = JSON.parse(line);
|
|
178
141
|
}
|
|
179
142
|
catch {
|
|
180
|
-
// a parse error has no id to answer
|
|
181
143
|
this.send({ jsonrpc: "2.0", id: null, error: { code: PARSE_ERROR, message: "invalid JSON" } });
|
|
182
144
|
return;
|
|
183
145
|
}
|
|
@@ -195,7 +157,6 @@ export class AcpAdapter {
|
|
|
195
157
|
fail(id, code, message) {
|
|
196
158
|
this.send({ jsonrpc: "2.0", id, error: { code, message } });
|
|
197
159
|
}
|
|
198
|
-
/* ---------- dispatch ---------- */
|
|
199
160
|
async dispatch(msg) {
|
|
200
161
|
const { method, id, params } = msg;
|
|
201
162
|
const isNotification = id === undefined || id === null;
|
|
@@ -217,7 +178,6 @@ export class AcpAdapter {
|
|
|
217
178
|
return this.ok(id ?? null, {});
|
|
218
179
|
return;
|
|
219
180
|
default:
|
|
220
|
-
// notifications for unknown methods get NO response at all
|
|
221
181
|
if (isNotification)
|
|
222
182
|
return;
|
|
223
183
|
return this.fail(id ?? null, METHOD_NOT_FOUND, `unknown method: ${method}`);
|
|
@@ -225,7 +185,7 @@ export class AcpAdapter {
|
|
|
225
185
|
}
|
|
226
186
|
catch (err) {
|
|
227
187
|
if (isNotification)
|
|
228
|
-
return;
|
|
188
|
+
return;
|
|
229
189
|
return this.fail(id ?? null, INTERNAL_ERROR, err.message);
|
|
230
190
|
}
|
|
231
191
|
}
|
|
@@ -233,13 +193,10 @@ export class AcpAdapter {
|
|
|
233
193
|
this.initialized = true;
|
|
234
194
|
return {
|
|
235
195
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
236
|
-
// Only capabilities we actually implement: a client calls what we
|
|
237
|
-
// advertise, so a partial implementation would be worse than omission.
|
|
238
196
|
agentCapabilities: { loadSession: true, promptCapabilities: { image: false, audio: false } },
|
|
239
197
|
authMethods: [],
|
|
240
198
|
};
|
|
241
199
|
}
|
|
242
|
-
/* ---------- sessions ---------- */
|
|
243
200
|
async newSession(params) {
|
|
244
201
|
const p = (params ?? {});
|
|
245
202
|
const cwd = p.cwd ? path.resolve(String(p.cwd)) : this.defaultCwd;
|
|
@@ -253,9 +210,8 @@ export class AcpAdapter {
|
|
|
253
210
|
const id = String(p.sessionId ?? "");
|
|
254
211
|
const existing = this.sessions.get(id);
|
|
255
212
|
if (existing)
|
|
256
|
-
return null;
|
|
213
|
+
return null;
|
|
257
214
|
const cwd = p.cwd ? path.resolve(String(p.cwd)) : this.defaultCwd;
|
|
258
|
-
// resuming means the agent's own log is restored, not a fresh session
|
|
259
215
|
const agent = await this.master.addAgent({ id, workspace: cwd }, { persist: false, fresh: false });
|
|
260
216
|
this.sessions.set(id, { id, cwd, agent });
|
|
261
217
|
return null;
|
|
@@ -265,12 +221,9 @@ export class AcpAdapter {
|
|
|
265
221
|
const s = p.sessionId ? this.sessions.get(String(p.sessionId)) : undefined;
|
|
266
222
|
if (!s)
|
|
267
223
|
return;
|
|
268
|
-
// The Agent's own "stopped" flag is private, and this adapter is the
|
|
269
|
-
// component that answered the cancel notification anyway.
|
|
270
224
|
this.cancelled.add(s.id);
|
|
271
225
|
s.agent.stop("cancelled via ACP");
|
|
272
226
|
}
|
|
273
|
-
/* ---------- prompt turn ---------- */
|
|
274
227
|
async prompt(id, params) {
|
|
275
228
|
const p = (params ?? {});
|
|
276
229
|
const s = p.sessionId ? this.sessions.get(String(p.sessionId)) : undefined;
|
|
@@ -283,8 +236,7 @@ export class AcpAdapter {
|
|
|
283
236
|
if (!text)
|
|
284
237
|
return this.fail(id, INVALID_PARAMS, "prompt must contain text");
|
|
285
238
|
this.turnUpdates.set(id, { sessionId: s.id });
|
|
286
|
-
this.cancelled.delete(s.id);
|
|
287
|
-
// Without streaming, the client sees NOTHING until the whole turn finishes.
|
|
239
|
+
this.cancelled.delete(s.id);
|
|
288
240
|
const abort = new AbortController();
|
|
289
241
|
const stream = this.streamTurn(s.id, abort.signal).catch(() => { });
|
|
290
242
|
try {
|
|
@@ -292,11 +244,8 @@ export class AcpAdapter {
|
|
|
292
244
|
if (s.agent.status !== "running")
|
|
293
245
|
s.agent.start("acp prompt");
|
|
294
246
|
await s.agent.settled();
|
|
295
|
-
// One last drain, or the final tool result and closing message can be
|
|
296
|
-
// missed: `settled()` resolves the moment the agent stops, and the last
|
|
297
|
-
// append may not have landed yet.
|
|
298
247
|
abort.abort();
|
|
299
|
-
await stream;
|
|
248
|
+
await stream;
|
|
300
249
|
return this.ok(id, { stopReason: this.cancelled.has(s.id) ? "cancelled" : "end_turn" });
|
|
301
250
|
}
|
|
302
251
|
catch (err) {
|
|
@@ -307,7 +256,6 @@ export class AcpAdapter {
|
|
|
307
256
|
this.turnUpdates.delete(id);
|
|
308
257
|
}
|
|
309
258
|
}
|
|
310
|
-
/** drain a session's log tail into session/update notifications */
|
|
311
259
|
async drainOnce(sessionId, afterSeq = 0) {
|
|
312
260
|
const s = this.sessions.get(sessionId);
|
|
313
261
|
if (!s)
|
|
@@ -317,28 +265,13 @@ export class AcpAdapter {
|
|
|
317
265
|
if (e.seq <= afterSeq)
|
|
318
266
|
continue;
|
|
319
267
|
const update = updateForEvent(e, { workspace: s.cwd });
|
|
320
|
-
// null means "no ACP equivalent" — state flips, goals, notes
|
|
321
268
|
if (update)
|
|
322
269
|
this.notify("session/update", { sessionId, update });
|
|
323
270
|
}
|
|
324
|
-
// Advance past EVERY event read, emitted or not, so nothing is reconsidered
|
|
325
|
-
// on the next poll.
|
|
326
271
|
return events.reduce((m, e) => Math.max(m, e.seq), afterSeq);
|
|
327
272
|
}
|
|
328
|
-
/**
|
|
329
|
-
* Poll one session until it settles, streaming each batch as it appears.
|
|
330
|
-
*
|
|
331
|
-
* The JSONL log rather than the bus is deliberate: a bus listener attached at
|
|
332
|
-
* the wrong moment can miss the start of a reply, and after a crash mid-turn
|
|
333
|
-
* the file is the one complete account of what happened. It also means a
|
|
334
|
-
* resumed session replays exactly the same updates.
|
|
335
|
-
*/
|
|
336
273
|
async streamTurn(sessionId, signal) {
|
|
337
|
-
// Everything before this prompt belongs to earlier turns.
|
|
338
274
|
let cursor = await this.logTail(sessionId);
|
|
339
|
-
// The drain happens BEFORE the abort check: a turn can finish inside one poll
|
|
340
|
-
// interval, so a loop that slept and then bailed on `signal.aborted` would
|
|
341
|
-
// exit having drained NOTHING and the editor would receive silence.
|
|
342
275
|
for (;;) {
|
|
343
276
|
cursor = await this.drainOnce(sessionId, cursor);
|
|
344
277
|
if (signal.aborted)
|
|
@@ -346,7 +279,6 @@ export class AcpAdapter {
|
|
|
346
279
|
await sleep(STREAM_POLL_MS);
|
|
347
280
|
}
|
|
348
281
|
}
|
|
349
|
-
/** highest seq currently in a session's log, or 0 */
|
|
350
282
|
async logTail(sessionId) {
|
|
351
283
|
const s = this.sessions.get(sessionId);
|
|
352
284
|
if (!s)
|