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.
Files changed (43) hide show
  1. package/dist/acp/adapter.js +4 -72
  2. package/dist/agent/agent.js +52 -2246
  3. package/dist/agent/compact-rules.js +0 -53
  4. package/dist/agent/editorconfig.js +4 -56
  5. package/dist/agent/goal-audit.js +0 -34
  6. package/dist/agent/key-pool.js +0 -52
  7. package/dist/agent/kill.js +1 -13
  8. package/dist/agent/llm.js +8 -309
  9. package/dist/agent/mcp-oauth-manager.js +2 -255
  10. package/dist/agent/mcp-registry.js +1 -188
  11. package/dist/agent/mcp.js +1 -85
  12. package/dist/agent/meta-tools.js +0 -26
  13. package/dist/agent/plugins.js +1 -81
  14. package/dist/agent/provider-headers.js +1 -28
  15. package/dist/agent/skills.js +8 -94
  16. package/dist/agent/tools.js +16 -809
  17. package/dist/bus.js +1 -2
  18. package/dist/config-schema.js +0 -40
  19. package/dist/home.js +1 -19
  20. package/dist/index.js +0 -136
  21. package/dist/log/events.js +3 -195
  22. package/dist/master.js +31 -1176
  23. package/dist/migrations/index.js +0 -55
  24. package/dist/migrations/providers.js +0 -13
  25. package/dist/migrations/registry.js +0 -9
  26. package/dist/migrations/session-titles.js +3 -43
  27. package/dist/migrations/sessions.js +2 -20
  28. package/dist/model-meta.js +2 -74
  29. package/dist/scheduler/cron.js +2 -17
  30. package/dist/server/api.js +12 -778
  31. package/dist/server/image-resize.js +1 -64
  32. package/dist/server/term-size.js +1 -120
  33. package/dist/server/terminals.js +0 -159
  34. package/dist/session-title.js +2 -35
  35. package/package.json +1 -1
  36. package/public/assets/index-DhJx7VDq.js +36 -0
  37. package/public/assets/index-DhJx7VDq.js.map +1 -0
  38. package/public/assets/{shiki-BaLUHSNL.js → shiki-LBMkaWYb.js} +2 -2
  39. package/public/assets/shiki-LBMkaWYb.js.map +1 -0
  40. package/public/index.html +1 -1
  41. package/public/assets/index-D_f1aal3.js +0 -36
  42. package/public/assets/index-D_f1aal3.js.map +0 -1
  43. package/public/assets/shiki-BaLUHSNL.js.map +0 -1
@@ -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; // never answer a notification
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; // already live
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); // a cancel is per-turn, not per-session
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; // resolves after its own final drain
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)