teapot-coding-agent 0.14.7 → 0.14.8

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.
@@ -493,6 +493,54 @@ export class Agent {
493
493
  await fs.writeFile(this.todoFile, text, "utf8");
494
494
  await this.log.append("todo", this.currentSession, this.currentBranch, { event: "set", by });
495
495
  }
496
+ /**
497
+ * Update SPECIFIC checkbox items without rewriting the whole list — the
498
+ * full-replacement path made agents lazy (a mid-task progress tick meant
499
+ * re-emitting the entire markdown, so they skipped updates) and risky (one
500
+ * sloppy regeneration mangled unrelated sections). Items are matched by
501
+ * normalized text; unknown items are reported back instead of silently
502
+ * dropped. Returns a short human-readable summary for the tool result.
503
+ */
504
+ async updateTodoItems(updates, by = "agent") {
505
+ const norm = (s) => s.replace(/\s+/g, " ").trim().toLowerCase();
506
+ const lines = this.todo.split("\n");
507
+ let matched = 0;
508
+ const unmatched = [];
509
+ for (const u of updates) {
510
+ const target = norm(u.item);
511
+ // find the LAST matching checkbox line (later duplicates win)
512
+ let idx = -1;
513
+ for (let i = lines.length - 1; i >= 0; i--) {
514
+ const m = lines[i].match(/^(\s*[-*+] \[)([ xX])(\].*)$/);
515
+ if (!m)
516
+ continue;
517
+ if (norm(m[3].slice(1)) === target) {
518
+ idx = i;
519
+ break;
520
+ }
521
+ }
522
+ if (idx === -1) {
523
+ unmatched.push(u.item);
524
+ continue;
525
+ }
526
+ lines[idx] = lines[idx].replace(/^(.* \[)[ xX](\].*)$/, (_s, a, b) => `${a}${u.done ? "x" : " "}${b}`);
527
+ matched++;
528
+ }
529
+ if (matched > 0) {
530
+ this.todo = lines.join("\n");
531
+ await fs.writeFile(this.todoFile, this.todo, "utf8");
532
+ await this.log.append("todo", this.currentSession, this.currentBranch, {
533
+ event: "items",
534
+ by,
535
+ matched,
536
+ ...(unmatched.length ? { unknown: unmatched } : {}),
537
+ });
538
+ }
539
+ const parts = [`${matched} item(s) updated`];
540
+ if (unmatched.length)
541
+ parts.push(`NOT FOUND in todo.md: ${unmatched.map((s) => `"${s}"`).join(", ")} — check get_todo and retry with the exact wording`);
542
+ return parts.join(". ");
543
+ }
496
544
  async setGoalStatus(status) {
497
545
  this.goal = { ...this.goal, status, updatedAt: new Date().toISOString() };
498
546
  await this.writeGoalFile();
@@ -856,9 +904,28 @@ export class Agent {
856
904
  }
857
905
  if (call.function.name === "set_todo") {
858
906
  const a = safeParse(call.function.arguments);
907
+ // Preferred: surgical checkbox updates — no full rewrite, no risk
908
+ // of mangling unrelated sections, cheap enough to do mid-task.
909
+ const updates = Array.isArray(a.updates)
910
+ ? a.updates
911
+ .map((u) => {
912
+ const o = (u ?? {});
913
+ return { item: String(o.item ?? ""), done: o.done !== false };
914
+ })
915
+ .filter((u) => u.item.trim())
916
+ : [];
917
+ if (updates.length > 0) {
918
+ const note = await this.updateTodoItems(updates, "agent");
919
+ await this.answerMeta(call, `${note} (visible to the operator)`);
920
+ continue;
921
+ }
859
922
  const content = String(a.content ?? "").slice(0, 32_000);
923
+ if (!content.trim()) {
924
+ await this.answerMeta(call, "nothing to do: pass updates:[{item,done}] to check items off, or content to replace the whole list");
925
+ continue;
926
+ }
860
927
  await this.setTodo(content, "agent");
861
- await this.answerMeta(call, "task list updated (visible to the operator)");
928
+ await this.answerMeta(call, "task list replaced (visible to the operator)");
862
929
  continue;
863
930
  }
864
931
  if (call.function.name === "get_feedback") {
@@ -1450,11 +1517,29 @@ function allToolSpecs() {
1450
1517
  type: "function",
1451
1518
  function: {
1452
1519
  name: "set_todo",
1453
- description: "Replace the operator-visible task list (todo.md) — e.g. check off finished items or restate what remains. Keep it terse.",
1520
+ description: "Update the operator-visible task list (todo.md). PREFERRED: pass updates to check specific " +
1521
+ "items off as you finish them — cheap, surgical, no risk of mangling other sections. Only pass " +
1522
+ "content to rewrite the whole list when restructuring it. Call this promptly on every item you complete.",
1454
1523
  parameters: {
1455
1524
  type: "object",
1456
- properties: { content: { type: "string" } },
1457
- required: ["content"],
1525
+ properties: {
1526
+ updates: {
1527
+ type: "array",
1528
+ description: "surgical checkbox flips — match items by their exact text in todo.md",
1529
+ items: {
1530
+ type: "object",
1531
+ properties: {
1532
+ item: { type: "string", description: "checkbox item text (without the \"- [ ]\" marker)" },
1533
+ done: { type: "boolean", description: "true = check off, false = reopen (default true)" },
1534
+ },
1535
+ required: ["item"],
1536
+ },
1537
+ },
1538
+ content: {
1539
+ type: "string",
1540
+ description: "full replacement markdown — only for restructures; prefer updates",
1541
+ },
1542
+ },
1458
1543
  },
1459
1544
  },
1460
1545
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teapot-coding-agent",
3
- "version": "0.14.7",
3
+ "version": "0.14.8",
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",