teapot-coding-agent 0.27.0 → 0.27.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.
@@ -1664,11 +1664,14 @@ export const TOOLS = [
1664
1664
  },
1665
1665
  {
1666
1666
  name: "message_agent",
1667
- description: "Send a message to a specific sub-agent (steer it mid-flight or answer its ask_user question).",
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
- if (status === "idle" || status === "stopped" || status === "waiting" || status === "error")
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
- const cfg = this.config.agents.find((a) => a.id === targetId);
814
- if (!cfg || cfg.parent !== parentId) {
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teapot-coding-agent",
3
- "version": "0.27.0",
3
+ "version": "0.27.1",
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",