teapot-coding-agent 0.16.2 → 0.17.1
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/agent/agent.js +10 -0
- package/dist/agent/tools.js +112 -0
- package/package.json +1 -1
- package/public/assets/{index-DXTAUylp.js → index-BPIU7nDd.js} +8 -8
- package/public/assets/{index-DXTAUylp.js.map → index-BPIU7nDd.js.map} +1 -1
- package/public/assets/{index-NiA8e42l.css → index-KCd15dRm.css} +1 -1
- package/public/assets/{shiki-uS2Hy0mb.js → shiki-CONfUTgk.js} +2 -2
- package/public/assets/{shiki-uS2Hy0mb.js.map → shiki-CONfUTgk.js.map} +1 -1
- package/public/index.html +2 -2
package/dist/agent/agent.js
CHANGED
|
@@ -1451,6 +1451,16 @@ export class Agent {
|
|
|
1451
1451
|
const after = this.estimateTokens();
|
|
1452
1452
|
this.stats.compactions++;
|
|
1453
1453
|
this.compactedAtLen = this.messages.length;
|
|
1454
|
+
// the operator can inspect WHAT was remembered — a dedicated typed event
|
|
1455
|
+
// (not an agent message) keeps it out of the model's own voice
|
|
1456
|
+
await this.log.append("compaction", this.currentSession, this.currentBranch, {
|
|
1457
|
+
tokensBefore: before,
|
|
1458
|
+
tokensAfter: after,
|
|
1459
|
+
summarized: summarizedCount,
|
|
1460
|
+
dropped: droppedCount,
|
|
1461
|
+
mode,
|
|
1462
|
+
...(summary ? { summary: String(summary).slice(0, 20_000) } : {}),
|
|
1463
|
+
});
|
|
1454
1464
|
await this.log.append("system_note", this.currentSession, this.currentBranch, {
|
|
1455
1465
|
event: "context-compacted",
|
|
1456
1466
|
tokensBefore: before,
|
package/dist/agent/tools.js
CHANGED
|
@@ -43,6 +43,50 @@ export function safeJoin(cwd, p) {
|
|
|
43
43
|
}
|
|
44
44
|
return abs;
|
|
45
45
|
}
|
|
46
|
+
/** per-workspace background shells (key: workspace path) */
|
|
47
|
+
const bgShellsByWs = new Map();
|
|
48
|
+
let bgSeq = 0;
|
|
49
|
+
/** how much of each job's output has already been handed to the model */
|
|
50
|
+
const bgReadCursors = new Map();
|
|
51
|
+
function bgMapFor(cwd) {
|
|
52
|
+
let m = bgShellsByWs.get(cwd);
|
|
53
|
+
if (!m) {
|
|
54
|
+
m = new Map();
|
|
55
|
+
bgShellsByWs.set(cwd, m);
|
|
56
|
+
}
|
|
57
|
+
return m;
|
|
58
|
+
}
|
|
59
|
+
function startBackgroundShell(cmd, ctx) {
|
|
60
|
+
const map = bgMapFor(ctx.cwd);
|
|
61
|
+
// opportunistic cleanup of long-dead jobs so the map can't grow forever
|
|
62
|
+
for (const [id, sh] of map) {
|
|
63
|
+
if (sh.exited && Date.now() - sh.startedAt > 30 * 60_000 && !map.delete(id))
|
|
64
|
+
void id;
|
|
65
|
+
}
|
|
66
|
+
const id = `bg${++bgSeq}`;
|
|
67
|
+
const child = spawn("/bin/bash", ["-lc", cmd], {
|
|
68
|
+
cwd: ctx.cwd,
|
|
69
|
+
detached: true,
|
|
70
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
71
|
+
env: { ...process.env, TERM: "dumb", GIT_PAGER: "cat", PAGER: "cat" },
|
|
72
|
+
});
|
|
73
|
+
const sh = {
|
|
74
|
+
id, cmd, child,
|
|
75
|
+
out: "", truncated: false,
|
|
76
|
+
startedAt: Date.now(), exited: false, code: null,
|
|
77
|
+
};
|
|
78
|
+
const collect = (chunk) => {
|
|
79
|
+
if (sh.out.length < 200_000)
|
|
80
|
+
sh.out += chunk.toString("utf8");
|
|
81
|
+
else
|
|
82
|
+
sh.truncated = true;
|
|
83
|
+
};
|
|
84
|
+
child.stdout?.on("data", collect);
|
|
85
|
+
child.stderr?.on("data", collect);
|
|
86
|
+
child.on("close", (code) => { sh.exited = true; sh.code = code; });
|
|
87
|
+
map.set(id, sh);
|
|
88
|
+
return id;
|
|
89
|
+
}
|
|
46
90
|
/** Run a command in its own process group; kill the whole group on timeout. */
|
|
47
91
|
function runShell(cmd, ctx, timeoutMs) {
|
|
48
92
|
return new Promise((resolve) => {
|
|
@@ -652,13 +696,81 @@ export const TOOLS = [
|
|
|
652
696
|
properties: {
|
|
653
697
|
command: { type: "string" },
|
|
654
698
|
timeout_ms: { type: "number", description: `default ${DEFAULT_TIMEOUT_MS}` },
|
|
699
|
+
background: {
|
|
700
|
+
type: "boolean",
|
|
701
|
+
description: "true = start the command and return IMMEDIATELY with a job id. Poll its output later via " +
|
|
702
|
+
"bash_output. Use for dev servers, watchers, long builds — anything you don't need to wait for.",
|
|
703
|
+
},
|
|
655
704
|
},
|
|
656
705
|
required: ["command"],
|
|
657
706
|
},
|
|
658
707
|
async run(args, ctx) {
|
|
708
|
+
if (args.background === true) {
|
|
709
|
+
const id = startBackgroundShell(str(args.command), ctx);
|
|
710
|
+
return {
|
|
711
|
+
ok: true,
|
|
712
|
+
result: `started in background (job ${id}). Continue with other work; read its output with ` +
|
|
713
|
+
`bash_output(job_id="${id}"). Kill it with bash_output(job_id="${id}", action="kill").`,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
659
716
|
return runShell(str(args.command), ctx, Math.min(num(args.timeout_ms, ctx.defaultTimeoutMs), 600_000));
|
|
660
717
|
},
|
|
661
718
|
},
|
|
719
|
+
{
|
|
720
|
+
name: "bash_output",
|
|
721
|
+
description: "Read or manage a background shell started via bash(background=true). " +
|
|
722
|
+
"action 'read' returns new output since the last read (plus exit status when done); " +
|
|
723
|
+
"'kill' terminates the whole process group. Use this instead of re-running long commands.",
|
|
724
|
+
parameters: {
|
|
725
|
+
type: "object",
|
|
726
|
+
properties: {
|
|
727
|
+
job_id: { type: "string", description: "the job id returned by bash(background=true)" },
|
|
728
|
+
action: {
|
|
729
|
+
type: "string",
|
|
730
|
+
enum: ["read", "kill"],
|
|
731
|
+
description: "default 'read'",
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
required: ["job_id"],
|
|
735
|
+
},
|
|
736
|
+
async run(args, ctx) {
|
|
737
|
+
const map = bgMapFor(ctx.cwd);
|
|
738
|
+
const sh = map.get(str(args.job_id));
|
|
739
|
+
if (!sh)
|
|
740
|
+
return {
|
|
741
|
+
ok: false,
|
|
742
|
+
result: `unknown background job "${str(args.job_id)}" in this workspace — ` +
|
|
743
|
+
`jobs are per-workspace and forgotten on harness restart`,
|
|
744
|
+
};
|
|
745
|
+
if (str(args.action) === "kill") {
|
|
746
|
+
if (!sh.exited) {
|
|
747
|
+
try {
|
|
748
|
+
if (sh.child.pid)
|
|
749
|
+
process.kill(-sh.child.pid, "SIGKILL");
|
|
750
|
+
}
|
|
751
|
+
catch { /* already gone */ }
|
|
752
|
+
}
|
|
753
|
+
const note = sh.exited ? "(already exited)" : "killed";
|
|
754
|
+
map.delete(sh.id);
|
|
755
|
+
return { ok: true, result: `job ${sh.id} ${note}. Output so far:\n${clip(sh.out, 4000)}` };
|
|
756
|
+
}
|
|
757
|
+
// action === read (default): hand back NEW bytes and remember the cursor
|
|
758
|
+
const fresh = sh.out.slice(bgReadCursors.get(sh.id) ?? 0);
|
|
759
|
+
bgReadCursors.set(sh.id, sh.out.length);
|
|
760
|
+
const status = sh.exited
|
|
761
|
+
? `\n[exited with code ${sh.code ?? "?"}]`
|
|
762
|
+
: `\n[still running, ${(Math.round((Date.now() - sh.startedAt) / 100) / 10)}s]`;
|
|
763
|
+
const body = (fresh.length ? fresh : "(no new output)") +
|
|
764
|
+
(sh.truncated ? "\n… (output truncated at 200KB)" : "") +
|
|
765
|
+
status;
|
|
766
|
+
// finished jobs are removed once fully drained
|
|
767
|
+
if (sh.exited && bgReadCursors.get(sh.id) >= sh.out.length) {
|
|
768
|
+
map.delete(sh.id);
|
|
769
|
+
bgReadCursors.delete(sh.id);
|
|
770
|
+
}
|
|
771
|
+
return { ok: true, result: clip(body, ctx.maxOutputBytes) };
|
|
772
|
+
},
|
|
773
|
+
},
|
|
662
774
|
{
|
|
663
775
|
name: "read_url",
|
|
664
776
|
description: "Fetch a web page and return its main readable content (title + plain text, boilerplate stripped via " +
|