teapot-coding-agent 0.29.16 → 0.29.18

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.
@@ -10,7 +10,7 @@ import { promises as fs, existsSync, readFileSync, readdirSync, statSync } from
10
10
  import path from "node:path";
11
11
  import { EventLog, readEvents } from "../log/events.js";
12
12
  import { chat, chatStream } from "./llm.js";
13
- import { executeTool, toolSpecs, currentSkills, safeJoin, isContextOverflow, hasRunningBgShells } from "./tools.js";
13
+ import { executeTool, toolSpecs, currentSkills, safeJoin, isContextOverflow, hasRunningBgShells, killBgShells } from "./tools.js";
14
14
  import { effortChoosable } from "./llm.js";
15
15
  import { skillRootsFingerprint, foreignSkillRoots } from "./skills.js";
16
16
  import { bus } from "../bus.js";
@@ -685,6 +685,15 @@ export class Agent {
685
685
  this.pendingPrompts.length > 0 ||
686
686
  this.compactPhase !== "" ||
687
687
  this.retrySleeping ||
688
+ // #158: a BACKGROUND job is work in flight, and `stop()` now ends it. The
689
+ // round that started it deliberately ended (the loop breaks rather than
690
+ // nagging — see hasRunningBgShells in loop()), so the status reads "idle"
691
+ // while a dev server / watcher / long build is still running. Without this
692
+ // the UI offered "▶ start" and there was no stop control for the one thing
693
+ // the operator most needs to stop. Note this is checked AFTER stopRequested:
694
+ // once stopped, the jobs are already dead, so `live` must go false or the
695
+ // button would offer stop forever (#133).
696
+ hasRunningBgShells(this.toolCtx.cwd) ||
688
697
  // ask_user: the loop is parked on the operator's answer — stoppable work
689
698
  // in flight, not idleness. Without this the UI offered "start", and
690
699
  // pressing it cleared awaitingUser, silently discarding the open question
@@ -1677,6 +1686,13 @@ export class Agent {
1677
1686
  // without aborting toolCtx.signal the park ran to its full timeout
1678
1687
  this.abort?.abort();
1679
1688
  this.toolAbort.abort();
1689
+ // #158: and the BACKGROUND shells, which neither signal above reaches. A job
1690
+ // spawned with `background: true` is `detached` and its spawning tool returned
1691
+ // long ago, so `toolAbort` has nothing on the stack to abort: the agent parked
1692
+ // in its "wait for the job" idle state, the job kept running, and `stop()` —
1693
+ // the control the operator pressed for exactly this — ended nothing. MEASURED:
1694
+ // `sleep 300` outlived both stop() and dispose().
1695
+ killBgShells(this.toolCtx.cwd);
1680
1696
  this.wake?.();
1681
1697
  // #86: the status write is DEFERRED onto the run chain, so it can land
1682
1698
  // AFTER a start() that followed it. Observed: `idle -> stopped -> running ->
@@ -3666,6 +3682,29 @@ export class Agent {
3666
3682
  onDelta
3667
3683
  ? (s) => onDelta(s.text)
3668
3684
  : undefined);
3685
+ // Record what the summarizer itself cost, especially `cachedInputTokens`.
3686
+ //
3687
+ // This is the ONLY way to observe the property the whole replay-the-prefix shape
3688
+ // exists for: the summarizer is supposed to be served from the provider's KV
3689
+ // cache because its input is a byte-identical prefix of the request the
3690
+ // conversation just made. Without this line the claim was UNVERIFIABLE — a
3691
+ // compaction event reported tokensBefore/tokensAfter (the CONVERSATION's size)
3692
+ // and nothing at all about the call that produced the summary, so a shape that
3693
+ // silently stopped sharing the prefix would look identical in the log.
3694
+ //
3695
+ // MEASURED on the session that prompted this: the 12k-line history had 2342
3696
+ // usage events, none of them the summarizer's.
3697
+ if (res.usage) {
3698
+ const u = res.usage;
3699
+ this.stats.inputTokens += u.inputTokens ?? 0;
3700
+ this.stats.outputTokens += u.outputTokens ?? 0;
3701
+ this.stats.cachedInputTokens += u.cachedInputTokens ?? 0;
3702
+ await this.log.append("usage", this.currentSession, this.currentBranch, {
3703
+ ...u,
3704
+ // distinguishes it from a conversation turn when reading the log back
3705
+ kind: "summarize",
3706
+ });
3707
+ }
3669
3708
  return this.formatCompactSummary(res.message.content ?? "");
3670
3709
  }
3671
3710
  /** strip the <analysis> scratchpad — it improves thinking but must not
@@ -301,6 +301,34 @@ export function hasRunningBgShells(cwd) {
301
301
  }
302
302
  return false;
303
303
  }
304
+ /**
305
+ * Kill every background shell for one workspace and forget the jobs (#158).
306
+ *
307
+ * `stop()` left these running. They are spawned `detached: true` for group-killing
308
+ * and are NOT covered by `toolAbort` (that signal only reaches a tool that is
309
+ * still on the stack), so pressing stop on an agent that had parked its round on
310
+ * a background job killed nothing: the loop sat in its deliberate "wait for the
311
+ * job" idle state while the job kept burning CPU, and no control could end it.
312
+ *
313
+ * MEASURED: `sleep 300` outlived both `stop()` and `dispose()`.
314
+ *
315
+ * Returns how many were actually killed, so a caller can report it.
316
+ */
317
+ export function killBgShells(cwd) {
318
+ const m = bgShellsByWs.get(cwd);
319
+ if (!m)
320
+ return 0;
321
+ let killed = 0;
322
+ for (const [id, sh] of m) {
323
+ if (!sh.exited) {
324
+ describeKill(killTree(sh.child));
325
+ killed++;
326
+ }
327
+ bgReadCursors.delete(id);
328
+ }
329
+ m.clear();
330
+ return killed;
331
+ }
304
332
  /** Run a command in its own process group; kill the whole group on timeout. */
305
333
  function runShell(cmd, ctx, timeoutMs) {
306
334
  return new Promise((resolve) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teapot-coding-agent",
3
- "version": "0.29.16",
3
+ "version": "0.29.18",
4
4
  "description": "A lightweight, always-on multi-agent harness for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",