teapot-coding-agent 0.27.0 → 0.27.2
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 +49 -4
- package/dist/agent/tools.js +5 -2
- package/dist/master.js +56 -3
- package/package.json +1 -1
- package/public/assets/{index-CaKIcOkZ.js → index-NorOxnzt.js} +22 -22
- package/public/assets/index-NorOxnzt.js.map +1 -0
- package/public/assets/{shiki-B6zAhgc_.js → shiki-DYylGhua.js} +2 -2
- package/public/assets/{shiki-B6zAhgc_.js.map → shiki-DYylGhua.js.map} +1 -1
- package/public/index.html +1 -1
- package/public/assets/index-CaKIcOkZ.js.map +0 -1
package/dist/agent/agent.js
CHANGED
|
@@ -439,7 +439,26 @@ export class Agent {
|
|
|
439
439
|
* (#59) — the same trap #38 documented for editing history.
|
|
440
440
|
*/
|
|
441
441
|
isLive() {
|
|
442
|
-
|
|
442
|
+
// #127: a MANUAL compaction on an idle agent was rewriting `messages` — a
|
|
443
|
+
// large, non-cancellable operation — while `isLive()` reported false, so the
|
|
444
|
+
// UI offered "start".
|
|
445
|
+
//
|
|
446
|
+
// #133: and the reverse — after a STOP with prompts still queued, `live` stayed
|
|
447
|
+
// true forever, so the controls kept offering "stop" for an agent that was
|
|
448
|
+
// already stopped, and no amount of reloading helped. Measured:
|
|
449
|
+
//
|
|
450
|
+
// after stop : status = stopped live = true pendingPrompts = 1
|
|
451
|
+
//
|
|
452
|
+
// A queued prompt is not work in progress: it is waiting for the operator to
|
|
453
|
+
// press start. #9 settled that a stop must neither deliver nor silently
|
|
454
|
+
// DISCARD those prompts, so they are kept — but keeping them must not make the
|
|
455
|
+
// agent read as running. A stop outranks them.
|
|
456
|
+
if (this.stopRequested)
|
|
457
|
+
return false;
|
|
458
|
+
return (this.status === "running" ||
|
|
459
|
+
this.parkedByTool ||
|
|
460
|
+
this.pendingPrompts.length > 0 ||
|
|
461
|
+
this.compactPhase !== "");
|
|
443
462
|
}
|
|
444
463
|
/**
|
|
445
464
|
* A background shell (bash background=true) finished. Two channels:
|
|
@@ -2927,14 +2946,40 @@ export class Agent {
|
|
|
2927
2946
|
}
|
|
2928
2947
|
/** Ask the model for dense continuation notes over the compacted range. */
|
|
2929
2948
|
async summarize(old, onDelta) {
|
|
2930
|
-
const
|
|
2949
|
+
const full = old
|
|
2931
2950
|
.map((m) => {
|
|
2932
2951
|
const who = m.role === "tool" ? "tool" : m.role;
|
|
2933
2952
|
const tc = m.tool_calls?.map((t) => `\n[calls ${t.function.name}(${t.function.arguments})]`).join("");
|
|
2934
2953
|
return `${who}: ${m.content ?? ""}${tc}`;
|
|
2935
2954
|
})
|
|
2936
|
-
.join("\n\n")
|
|
2937
|
-
|
|
2955
|
+
.join("\n\n");
|
|
2956
|
+
// #127: this was a bare `.slice(-120_000)` — the TAIL only.
|
|
2957
|
+
//
|
|
2958
|
+
// The question the issue raises is the right one: when a compaction starts,
|
|
2959
|
+
// the context window opens dramatically, and the summarizer saw only the last
|
|
2960
|
+
// 120k characters of what it was asked to summarise. Anything older was not
|
|
2961
|
+
// summarised and not kept — it was silently DISCARDED. So the original user
|
|
2962
|
+
// requirement, which sits at the very front of the history, was the single
|
|
2963
|
+
// most likely thing to be lost, and losing it is the one failure compaction
|
|
2964
|
+
// cannot recover from: the summary is now the only record.
|
|
2965
|
+
//
|
|
2966
|
+
// Head + tail, with the seam marked so the model can tell a gap from a
|
|
2967
|
+
// boundary. The head is where the goal, the constraints and the original ask
|
|
2968
|
+
// live; the tail is where the recent state and any in-flight error do.
|
|
2969
|
+
const BUDGET = 120_000;
|
|
2970
|
+
const HEAD = Math.floor(BUDGET * 0.35);
|
|
2971
|
+
const TAIL = BUDGET - HEAD;
|
|
2972
|
+
let transcript;
|
|
2973
|
+
if (full.length <= BUDGET) {
|
|
2974
|
+
transcript = full;
|
|
2975
|
+
}
|
|
2976
|
+
else {
|
|
2977
|
+
transcript = [
|
|
2978
|
+
full.slice(0, HEAD),
|
|
2979
|
+
`\n\n[... ${full.length - BUDGET} characters elided from the middle of this history ...]\n\n`,
|
|
2980
|
+
full.slice(full.length - TAIL),
|
|
2981
|
+
].join("");
|
|
2982
|
+
}
|
|
2938
2983
|
const fn = this.opts.chatFn ?? chat;
|
|
2939
2984
|
// Structured sections (Claude Code style): fixed headings survive MULTIPLE
|
|
2940
2985
|
// successive compactions far better than free-form prose — each pass knows
|
package/dist/agent/tools.js
CHANGED
|
@@ -1664,11 +1664,14 @@ export const TOOLS = [
|
|
|
1664
1664
|
},
|
|
1665
1665
|
{
|
|
1666
1666
|
name: "message_agent",
|
|
1667
|
-
description: "Send a message to a
|
|
1667
|
+
description: "Send a message to another agent. Use it to steer a sub-agent mid-flight, to answer its " +
|
|
1668
|
+
"ask_user question, or to message your OWN PARENT (pass its id) — a sub-agent may ask its " +
|
|
1669
|
+
"parent a question or hand back a partial result, which is how you report progress without " +
|
|
1670
|
+
"ending your turn. You may NOT message a sibling or an unrelated agent.",
|
|
1668
1671
|
parameters: {
|
|
1669
1672
|
type: "object",
|
|
1670
1673
|
properties: {
|
|
1671
|
-
id: { type: "string", description: "sub-agent id" },
|
|
1674
|
+
id: { type: "string", description: "sub-agent id, or your parent's id" },
|
|
1672
1675
|
text: { type: "string" },
|
|
1673
1676
|
},
|
|
1674
1677
|
required: ["id", "text"],
|
package/dist/master.js
CHANGED
|
@@ -16,6 +16,8 @@ import { bus } from "./bus.js";
|
|
|
16
16
|
import { fetchModelList, contextLengthFor, isReasoningEffort, } from "./model-meta.js";
|
|
17
17
|
/** how deep sub-agent spawning may nest (parent=0, its subs=1, …) */
|
|
18
18
|
const MAX_SPAWN_DEPTH = 3;
|
|
19
|
+
/** statuses that mean a child has finished working, for #130 */
|
|
20
|
+
const SETTLED = new Set(["idle", "stopped", "waiting", "error"]);
|
|
19
21
|
/**
|
|
20
22
|
* Skills shipped with the package — resolved relative to this module so it
|
|
21
23
|
* works from a global `npm install -g` install, an npx cache, or a repo
|
|
@@ -259,6 +261,14 @@ export class Master {
|
|
|
259
261
|
replacementPid = null;
|
|
260
262
|
tasks = [];
|
|
261
263
|
childWaiters = new Map();
|
|
264
|
+
/**
|
|
265
|
+
* Children that have actually started, for #130.
|
|
266
|
+
*
|
|
267
|
+
* A child is `idle` between `addAgent` and `start()`, and that is its FIRST
|
|
268
|
+
* status transition — so without this the parent is told a brand-new sub-agent is
|
|
269
|
+
* idle before it has done anything.
|
|
270
|
+
*/
|
|
271
|
+
reportedRunning = new Set();
|
|
262
272
|
startedAt = Date.now();
|
|
263
273
|
config;
|
|
264
274
|
configPath;
|
|
@@ -543,8 +553,27 @@ export class Master {
|
|
|
543
553
|
// the waiter re-checks whether ITS targets are all settled, so waking on
|
|
544
554
|
// every movement is correct and cheap
|
|
545
555
|
this.wakeParentWaiters(ac.parent);
|
|
546
|
-
|
|
556
|
+
// #130: only report a SETTLED child, never its birth.
|
|
557
|
+
//
|
|
558
|
+
// A freshly spawned child is `idle` for a moment before `start()` runs, and
|
|
559
|
+
// the FIRST status transition it ever makes is therefore idle->running via
|
|
560
|
+
// that idle. Reporting it told the parent "[harness] @kid is now idle" the
|
|
561
|
+
// instant the child existed — before it had done anything at all. Measured:
|
|
562
|
+
// transitions after a spawn are ["idle","running","idle"], so the first is
|
|
563
|
+
// always the spurious one.
|
|
564
|
+
//
|
|
565
|
+
// So the message waits for a child that has actually STARTED. A real
|
|
566
|
+
// completion still reports (it goes running -> idle), and the wake above is
|
|
567
|
+
// unconditional, so `wait_children` is unaffected either way.
|
|
568
|
+
// #130: a spawned child is `idle` until start() runs, and that lazy-load
|
|
569
|
+
// flip is its FIRST transition — so requiring a prior `running` is what stops
|
|
570
|
+
// the parent being told the child settled before it began.
|
|
571
|
+
if (this.reportedRunning.has(agentId) && SETTLED.has(status))
|
|
547
572
|
parent.enqueuePrompt(`[harness] @${agentId} is now ${status}. Continue your task, or finish() if the work is done.`, "harness");
|
|
573
|
+
if (status === "running")
|
|
574
|
+
this.reportedRunning.add(agentId);
|
|
575
|
+
else if (SETTLED.has(status))
|
|
576
|
+
this.reportedRunning.delete(agentId);
|
|
548
577
|
};
|
|
549
578
|
// sub-agent management hooks — only when this agent can legally spawn
|
|
550
579
|
if (myDepth < this.maxSpawnDepth()) {
|
|
@@ -810,8 +839,32 @@ When this task is done (or truly blocked), call finish() with a summary for @${p
|
|
|
810
839
|
const owned = this.sessionById(childId);
|
|
811
840
|
if (owned)
|
|
812
841
|
targetId = owned.agentId;
|
|
813
|
-
|
|
814
|
-
|
|
842
|
+
// #132: a sub-agent must be able to message its OWN PARENT.
|
|
843
|
+
//
|
|
844
|
+
// The rule below only ever permitted parent -> child, so a child asking its
|
|
845
|
+
// parent a question was refused with the actively misleading
|
|
846
|
+
//
|
|
847
|
+
// not your sub-agent: p1. no such sub-agent exists.
|
|
848
|
+
//
|
|
849
|
+
// — naming the parent as nonexistent, when it is the caller's own parent and
|
|
850
|
+
// plainly listed in config.agents. The parent link is the trust boundary: a
|
|
851
|
+
// child may address its parent, a parent may address its children, and
|
|
852
|
+
// NOTHING may address a sibling or an unrelated agent.
|
|
853
|
+
const caller = this.config.agents.find((a) => a.id === parentId);
|
|
854
|
+
const target = this.config.agents.find((a) => a.id === targetId);
|
|
855
|
+
const childToParent = target?.parent === parentId;
|
|
856
|
+
if (target && caller?.parent === targetId) {
|
|
857
|
+
const p = this.agents.get(targetId);
|
|
858
|
+
if (!p)
|
|
859
|
+
throw new Error(`sub-agent not running: ${targetId}`);
|
|
860
|
+
// mirrored wording, so the recipient knows who is writing
|
|
861
|
+
p.enqueuePrompt(`[harness] Message from sub-agent @${parentId}:\n\n${text}`, "harness");
|
|
862
|
+
if (p.status !== "running")
|
|
863
|
+
p.start(`message from sub-agent ${parentId}`);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const cfg = target;
|
|
867
|
+
if (!cfg || (!childToParent && cfg.parent !== parentId)) {
|
|
815
868
|
// Scoping is correct — only the DIRECT parent may message a child, so a
|
|
816
869
|
// grandchild is rightly refused. But "not your sub-agent: <id>" reads
|
|
817
870
|
// like a bug when the agent simply guessed the wrong level of the tree.
|