@tekmidian/pai 0.42.6 → 0.43.0

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.
Files changed (42) hide show
  1. package/dist/{chain-BbcfP77K.mjs → chain-CEC1a5mX.mjs} +2 -2
  2. package/dist/{chain-BbcfP77K.mjs.map → chain-CEC1a5mX.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +5 -5
  4. package/dist/cli/program.mjs +5 -5
  5. package/dist/daemon/index.mjs +4 -4
  6. package/dist/{daemon-BppziPi6.mjs → daemon-BhldpFvO.mjs} +6 -6
  7. package/dist/{daemon-BppziPi6.mjs.map → daemon-BhldpFvO.mjs.map} +1 -1
  8. package/dist/{daemon-BNjbVsQU.mjs → daemon-QIvCXB5f.mjs} +4 -4
  9. package/dist/daemon-mcp/index.mjs +3 -3
  10. package/dist/{fallback-Cwt4hmh5.mjs → fallback-BQJ8PbCK.mjs} +6 -4
  11. package/dist/fallback-BQJ8PbCK.mjs.map +1 -0
  12. package/dist/hooks/load-project-context.mjs +51 -48
  13. package/dist/hooks/load-project-context.mjs.map +4 -4
  14. package/dist/hooks/whisper-reinject.mjs +8 -0
  15. package/dist/hooks/whisper-reinject.mjs.map +3 -3
  16. package/dist/hooks/worker-status-line.mjs +108 -41
  17. package/dist/hooks/worker-status-line.mjs.map +3 -3
  18. package/dist/{planner-pXRc-8yt.mjs → planner-BlKGgpWI.mjs} +3 -3
  19. package/dist/{planner-pXRc-8yt.mjs.map → planner-BlKGgpWI.mjs.map} +1 -1
  20. package/dist/{program-LLNPpxlj.mjs → program-C5jRQ1kg.mjs} +749 -25
  21. package/dist/program-C5jRQ1kg.mjs.map +1 -0
  22. package/dist/{run-DDdT9teG.mjs → run-ByBKowN5.mjs} +472 -59
  23. package/dist/run-ByBKowN5.mjs.map +1 -0
  24. package/dist/{tools-BradfQk7.mjs → tools-CAG2sUjf.mjs} +2 -2
  25. package/dist/{tools-UrSHIe29.mjs → tools-DiRC5glA.mjs} +2 -2
  26. package/dist/{tools-UrSHIe29.mjs.map → tools-DiRC5glA.mjs.map} +1 -1
  27. package/dist/{wakeup-TnGjJY3I.mjs → wakeup-BfElduEU.mjs} +21 -8
  28. package/dist/wakeup-BfElduEU.mjs.map +1 -0
  29. package/docs/commands/README.md +3 -1
  30. package/docs/commands/audit.md +27 -2
  31. package/docs/commands/worker.md +2 -0
  32. package/package.json +1 -1
  33. package/scripts/build-hooks.mjs +2 -0
  34. package/src/hooks/ts/post-tool-use/whisper-reinject.test.ts +55 -0
  35. package/src/hooks/ts/post-tool-use/whisper-reinject.ts +3 -0
  36. package/src/hooks/ztk-auto.sh +28 -0
  37. package/src/hooks/ztk-auto.test.ts +72 -0
  38. package/src/hooks/ztk-passthrough.mjs +71 -0
  39. package/dist/fallback-Cwt4hmh5.mjs.map +0 -1
  40. package/dist/program-LLNPpxlj.mjs.map +0 -1
  41. package/dist/run-DDdT9teG.mjs.map +0 -1
  42. package/dist/wakeup-TnGjJY3I.mjs.map +0 -1
@@ -7,6 +7,13 @@ if (!process.env.PAI_QUIET_NOTICES) process.env.PAI_QUIET_NOTICES = '1';
7
7
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
8
  import { dirname, join } from "node:path";
9
9
  import { tmpdir } from "node:os";
10
+
11
+ // src/hooks/ts/lib/worker-session.ts
12
+ function isWorkerSession(env = process.env) {
13
+ return env.PAI_WORKER === "1";
14
+ }
15
+
16
+ // src/hooks/ts/post-tool-use/whisper-reinject.ts
10
17
  var QUIET_BELOW = 6;
11
18
  var EVERY = 8;
12
19
  function counterFile(sessionId) {
@@ -43,6 +50,7 @@ function main() {
43
50
  if (raw.trim()) sessionId = JSON.parse(raw).session_id ?? "";
44
51
  } catch {
45
52
  }
53
+ if (isWorkerSession()) return;
46
54
  const n = bump(counterFile(sessionId));
47
55
  if (n < QUIET_BELOW || n % EVERY !== 0) return;
48
56
  const lines = [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/hooks/ts/post-tool-use/whisper-reinject.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * whisper-reinject \u2014 put the output-shape rules back in view mid-turn.\n *\n * The whisper rules are injected once per user message, by the UserPromptSubmit\n * hook. That is the wrong place for the rules that govern how a turn is\n * *written*, because a long turn does not write its answer next to that\n * injection: it runs twenty tool calls first. Attention follows the tool\n * output, the injected block scrolls out of working memory, and by the time the\n * answer is composed the rules are gone. Nothing in the loop re-checks them, so\n * compliance decays with turn length \u2014 exactly the turns where it matters most.\n *\n * This fires after a tool call instead, so the reminder lands where the decay\n * happens. It stays quiet for short sequences (nothing has decayed yet) and\n * emits a compressed set \u2014 not all of them \u2014 every few calls after that. The\n * full rule set is deliberately not repeated: a wall of text re-read every few\n * calls is skimmed, and it costs context that the actual work needs.\n *\n * Only rules about the shape of the reply belong here. Rules about actions\n * (what may be sent, published, or committed) are enforced at the point of\n * action, not by reminding.\n */\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\n\n/** Stay silent below this many tool calls \u2014 a short turn has not drifted yet. */\nconst QUIET_BELOW = 6;\n\n/** Re-emit every N calls once past QUIET_BELOW. */\nconst EVERY = 8;\n\nfunction counterFile(sessionId: string): string {\n const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, \"\") || \"nosession\";\n return join(tmpdir(), \"pai-whisper-reinject\", `${safe}.count`);\n}\n\nfunction bump(path: string): number {\n try {\n mkdirSync(dirname(path), { recursive: true });\n const n = existsSync(path) ? Number.parseInt(readFileSync(path, \"utf-8\").trim(), 10) || 0 : 0;\n const next = n + 1;\n writeFileSync(path, String(next));\n return next;\n } catch {\n return 0;\n }\n}\n\nfunction localTime(): string {\n const d = new Date();\n const p = (n: number) => String(n).padStart(2, \"0\");\n return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;\n}\n\nfunction readStdin(): string {\n try {\n return readFileSync(0, \"utf-8\");\n } catch {\n return \"\";\n }\n}\n\nfunction main(): void {\n let sessionId = \"\";\n try {\n const raw = readStdin();\n if (raw.trim()) sessionId = (JSON.parse(raw) as { session_id?: string }).session_id ?? \"\";\n } catch { /* no session id \u2014 fall back to a shared counter */ }\n\n const n = bump(counterFile(sessionId));\n if (n < QUIET_BELOW || n % EVERY !== 0) return;\n\n const lines = [\n `CURRENT LOCAL TIME: ${localTime()} \u2014 use verbatim for any [YYYY-MM-DD HH:MM] stamp. Never estimate it, never increment a previous one.`,\n `You are ${n} tool calls into this turn. Before the next one, check:`,\n \"- One command at a time: say what you are about to run, run it, say what came back. Do not batch several steps into one call.\",\n \"- Report what CHANGED, not what you learned. A finding is not a deliverable.\",\n \"- Prove it: show the reading that says it failed before and works now.\",\n \"- Found a fault while working? Fix it. Filing it is not fixing it.\",\n \"- Corrections stay to one line. No re-litigating your own mistakes.\",\n ];\n\n console.log(`<system-reminder>\\n${lines.join(\"\\n\")}\\n</system-reminder>`);\n}\n\nmain();\n"],
5
- "mappings": ";;;;;;AAuBA,SAAyB,YAAY,WAAW,cAAc,qBAAqB;AACnF,SAAS,SAAS,YAAY;AAC9B,SAAS,cAAc;AAGvB,IAAM,cAAc;AAGpB,IAAM,QAAQ;AAEd,SAAS,YAAY,WAA2B;AAC9C,QAAM,OAAO,UAAU,QAAQ,mBAAmB,EAAE,KAAK;AACzD,SAAO,KAAK,OAAO,GAAG,wBAAwB,GAAG,IAAI,QAAQ;AAC/D;AAEA,SAAS,KAAK,MAAsB;AAClC,MAAI;AACF,cAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,UAAM,IAAI,WAAW,IAAI,IAAI,OAAO,SAAS,aAAa,MAAM,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI;AAC5F,UAAM,OAAO,IAAI;AACjB,kBAAc,MAAM,OAAO,IAAI,CAAC;AAChC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAoB;AAC3B,QAAM,IAAI,oBAAI,KAAK;AACnB,QAAM,IAAI,CAAC,MAAc,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AAClD,SAAO,GAAG,EAAE,YAAY,CAAC,IAAI,EAAE,EAAE,SAAS,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,CAAC,CAAC;AAC5G;AAEA,SAAS,YAAoB;AAC3B,MAAI;AACF,WAAO,aAAa,GAAG,OAAO;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,OAAa;AACpB,MAAI,YAAY;AAChB,MAAI;AACF,UAAM,MAAM,UAAU;AACtB,QAAI,IAAI,KAAK,EAAG,aAAa,KAAK,MAAM,GAAG,EAA8B,cAAc;AAAA,EACzF,QAAQ;AAAA,EAAsD;AAE9D,QAAM,IAAI,KAAK,YAAY,SAAS,CAAC;AACrC,MAAI,IAAI,eAAe,IAAI,UAAU,EAAG;AAExC,QAAM,QAAQ;AAAA,IACZ,uBAAuB,UAAU,CAAC;AAAA,IAClC,WAAW,CAAC;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,UAAQ,IAAI;AAAA,EAAsB,MAAM,KAAK,IAAI,CAAC;AAAA,mBAAsB;AAC1E;AAEA,KAAK;",
3
+ "sources": ["../../../src/hooks/ts/post-tool-use/whisper-reinject.ts", "../../../src/hooks/ts/lib/worker-session.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * whisper-reinject \u2014 put the output-shape rules back in view mid-turn.\n *\n * The whisper rules are injected once per user message, by the UserPromptSubmit\n * hook. That is the wrong place for the rules that govern how a turn is\n * *written*, because a long turn does not write its answer next to that\n * injection: it runs twenty tool calls first. Attention follows the tool\n * output, the injected block scrolls out of working memory, and by the time the\n * answer is composed the rules are gone. Nothing in the loop re-checks them, so\n * compliance decays with turn length \u2014 exactly the turns where it matters most.\n *\n * This fires after a tool call instead, so the reminder lands where the decay\n * happens. It stays quiet for short sequences (nothing has decayed yet) and\n * emits a compressed set \u2014 not all of them \u2014 every few calls after that. The\n * full rule set is deliberately not repeated: a wall of text re-read every few\n * calls is skimmed, and it costs context that the actual work needs.\n *\n * Only rules about the shape of the reply belong here. Rules about actions\n * (what may be sent, published, or committed) are enforced at the point of\n * action, not by reminding.\n */\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { isWorkerSession } from \"../lib/worker-session.js\";\n\n/** Stay silent below this many tool calls \u2014 a short turn has not drifted yet. */\nconst QUIET_BELOW = 6;\n\n/** Re-emit every N calls once past QUIET_BELOW. */\nconst EVERY = 8;\n\nfunction counterFile(sessionId: string): string {\n const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, \"\") || \"nosession\";\n return join(tmpdir(), \"pai-whisper-reinject\", `${safe}.count`);\n}\n\nfunction bump(path: string): number {\n try {\n mkdirSync(dirname(path), { recursive: true });\n const n = existsSync(path) ? Number.parseInt(readFileSync(path, \"utf-8\").trim(), 10) || 0 : 0;\n const next = n + 1;\n writeFileSync(path, String(next));\n return next;\n } catch {\n return 0;\n }\n}\n\nfunction localTime(): string {\n const d = new Date();\n const p = (n: number) => String(n).padStart(2, \"0\");\n return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;\n}\n\nfunction readStdin(): string {\n try {\n return readFileSync(0, \"utf-8\");\n } catch {\n return \"\";\n }\n}\n\nfunction main(): void {\n let sessionId = \"\";\n try {\n const raw = readStdin();\n if (raw.trim()) sessionId = (JSON.parse(raw) as { session_id?: string }).session_id ?? \"\";\n } catch { /* no session id \u2014 fall back to a shared counter */ }\n\n if (isWorkerSession()) return;\n\n const n = bump(counterFile(sessionId));\n if (n < QUIET_BELOW || n % EVERY !== 0) return;\n\n const lines = [\n `CURRENT LOCAL TIME: ${localTime()} \u2014 use verbatim for any [YYYY-MM-DD HH:MM] stamp. Never estimate it, never increment a previous one.`,\n `You are ${n} tool calls into this turn. Before the next one, check:`,\n \"- One command at a time: say what you are about to run, run it, say what came back. Do not batch several steps into one call.\",\n \"- Report what CHANGED, not what you learned. A finding is not a deliverable.\",\n \"- Prove it: show the reading that says it failed before and works now.\",\n \"- Found a fault while working? Fix it. Filing it is not fixing it.\",\n \"- Corrections stay to one line. No re-litigating your own mistakes.\",\n ];\n\n console.log(`<system-reminder>\\n${lines.join(\"\\n\")}\\n</system-reminder>`);\n}\n\nmain();\n", "/**\n * Worker-session detection.\n *\n * A disposable headless worker (a `claude -p` run started by an orchestrating\n * session, possibly against a different model provider with a different\n * context window) shares the project directory and the hook configuration\n * with the real session that spawned it. Left alone, the hooks treat it as a\n * session in its own right: they inject project context into it, create and\n * rename a numbered session note for it, autosave it, and enqueue a\n * model-written handover for it. Its compactions also land in the project's\n * transcript folder, where they are indistinguishable from a real session's\n * and drag the measured compaction trigger down (observed 2026-09-17: two\n * workers compacting at ~151k tokens pulled a project's trigger from ~784k\n * to ~151k, and the real session's handover fired at ~50k tokens).\n *\n * The launcher marks such sessions with `PAI_WORKER=1`. Every hook that does\n * per-session bookkeeping returns immediately when this predicate is true.\n * Deliberately NOT guarded: the security validator (a worker's shell\n * commands must still be checked) and observability capture.\n */\nexport function isWorkerSession(env: NodeJS.ProcessEnv = process.env): boolean {\n return env.PAI_WORKER === \"1\";\n}\n"],
5
+ "mappings": ";;;;;;AAuBA,SAAyB,YAAY,WAAW,cAAc,qBAAqB;AACnF,SAAS,SAAS,YAAY;AAC9B,SAAS,cAAc;;;ACLhB,SAAS,gBAAgB,MAAyB,QAAQ,KAAc;AAC7E,SAAO,IAAI,eAAe;AAC5B;;;ADOA,IAAM,cAAc;AAGpB,IAAM,QAAQ;AAEd,SAAS,YAAY,WAA2B;AAC9C,QAAM,OAAO,UAAU,QAAQ,mBAAmB,EAAE,KAAK;AACzD,SAAO,KAAK,OAAO,GAAG,wBAAwB,GAAG,IAAI,QAAQ;AAC/D;AAEA,SAAS,KAAK,MAAsB;AAClC,MAAI;AACF,cAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,UAAM,IAAI,WAAW,IAAI,IAAI,OAAO,SAAS,aAAa,MAAM,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI;AAC5F,UAAM,OAAO,IAAI;AACjB,kBAAc,MAAM,OAAO,IAAI,CAAC;AAChC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAoB;AAC3B,QAAM,IAAI,oBAAI,KAAK;AACnB,QAAM,IAAI,CAAC,MAAc,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AAClD,SAAO,GAAG,EAAE,YAAY,CAAC,IAAI,EAAE,EAAE,SAAS,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,CAAC,CAAC;AAC5G;AAEA,SAAS,YAAoB;AAC3B,MAAI;AACF,WAAO,aAAa,GAAG,OAAO;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,OAAa;AACpB,MAAI,YAAY;AAChB,MAAI;AACF,UAAM,MAAM,UAAU;AACtB,QAAI,IAAI,KAAK,EAAG,aAAa,KAAK,MAAM,GAAG,EAA8B,cAAc;AAAA,EACzF,QAAQ;AAAA,EAAsD;AAE9D,MAAI,gBAAgB,EAAG;AAEvB,QAAM,IAAI,KAAK,YAAY,SAAS,CAAC;AACrC,MAAI,IAAI,eAAe,IAAI,UAAU,EAAG;AAExC,QAAM,QAAQ;AAAA,IACZ,uBAAuB,UAAU,CAAC;AAAA,IAClC,WAAW,CAAC;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,UAAQ,IAAI;AAAA,EAAsB,MAAM,KAAK,IAAI,CAAC;AAAA,mBAAsB;AAC1E;AAEA,KAAK;",
6
6
  "names": []
7
7
  }
@@ -8288,49 +8288,116 @@ function recordSessionMapEntry(logDir, cwd2, session, term2, now = Date.now()) {
8288
8288
  }
8289
8289
  }
8290
8290
 
8291
+ // src/workers/agentish.ts
8292
+ import { execFileSync } from "node:child_process";
8293
+ var AG2_SPEC_FALLBACK = "AG2. msg=kind line+k=v lines. kinds T R S Q A X. keys i id g goal o own n forbid d steps p proof u out l limits r res c changes t tests G gate I inst m images # nums w worst x next z note(<200ch). sep |. outcomes + - ~ ? !. @n=path declared once then reused; @n:12=file:line. tests as Name+ Name-. no prose, no articles, never restate, unknown=?. r=+ only if all t +.";
8294
+ var AG2_EXTENSIONS_FALLBACK = "y why(\u2264600ch)";
8295
+ var cachedSpec = null;
8296
+ function ag2Spec() {
8297
+ if (cachedSpec) return cachedSpec;
8298
+ try {
8299
+ const out = execFileSync("aibroker", ["agentish", "spec"], {
8300
+ timeout: 3e3,
8301
+ stdio: ["ignore", "pipe", "ignore"],
8302
+ encoding: "utf8"
8303
+ });
8304
+ const lines = out.split("\n").filter((l) => l.trim());
8305
+ if (lines[0]?.startsWith("AG2.")) {
8306
+ cachedSpec = { spec: lines[0], extensions: lines[1] ?? AG2_EXTENSIONS_FALLBACK, source: "aibroker" };
8307
+ return cachedSpec;
8308
+ }
8309
+ } catch {
8310
+ }
8311
+ cachedSpec = { spec: AG2_SPEC_FALLBACK, extensions: AG2_EXTENSIONS_FALLBACK, source: "builtin" };
8312
+ return cachedSpec;
8313
+ }
8314
+
8291
8315
  // src/workers/report.ts
8292
8316
  var OPERATOR_MARK = "[operator]";
8293
- var WORKER_CONTRACT_PROMPT = [
8294
- "You are a headless implementation worker, run non-interactively by an orchestrating session.",
8295
- "You are the worker: this task is yours to finish in this process. Never hand your whole task to a",
8296
- "single child worker and step back \u2014 that is delegation, not work. Child workers are for two things",
8297
- "only: (1) PARALLELISING independent parts of your task, each part a self-contained spec; (2) CHEAPER",
8298
- "bounded sub-tasks \u2014 probes, renders, test runs, lookups \u2014 run one tier down with --class spotcheck or",
8299
- "--class simple. Always pin --provider anthropic on children.",
8300
- "You own your children's results: run them in the foreground or poll until each has finished, read",
8301
- "their result, verify it, and fold it into your own report. When your turn ends this run ends and any",
8302
- "running child is killed, so never end with a child still running.",
8303
- "Ending your turn ends this run. There is no later wake-up: never call ScheduleWakeup, never say you",
8304
- "will pick something up later, never leave a background command as your final action. Finish, then",
8305
- "report.",
8306
- "No narration, no timestamps, no greetings, no summaries of what you read; act, verify, then stop.",
8307
- "Your ONE final message is a JSON object and nothing else \u2014 no prose before or after, no code fence:",
8308
- '{"changed":[{"path":"\u2026","summary":"\u2026"}],"commands":["\u2026"],"checks":[{"name":"\u2026","ok":true,"detail":"\u2026"}],"open":["\u2026"],"notes":"one line"}',
8309
- "changed: files you touched (path + one-line summary). commands: the commands that verify the work.",
8310
- "checks: each with ok true/false and the evidence in detail. open: what you could not finish, if anything.",
8311
- "notes: one line, the headline a reviewer reads first.",
8312
- "NEVER put a multi-line or quoted payload inline in a shell command \u2014 no heredocs (<<EOF), no long",
8313
- "-p '\u2026' specs for child workers, no inline JSON, no inline scripts. ALWAYS write it to a file with",
8314
- 'the Write tool first and pass the file: -p "$(cat spec.txt)", --file spec.txt, or < spec.txt.',
8315
- "Inline quoting fails silently or dies with a parse error and burns a worker launch; there are no",
8316
- "exceptions.",
8317
- "Never inline code one-liners (python3 -c, node -e, ruby -e and friends) \u2014 the permission layer",
8318
- 'denies them ("ztk: command denied by permission rules"); write the script to a file with the',
8319
- "Write tool and run it (python3 script.py), or use jq for JSON inspection.",
8320
- "",
8321
- `Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
8322
- "run (the prompt of a resumed run is the operator's too). Answer it FIRST, in one or two plain lines \u2014",
8323
- "a question gets its answer, an instruction gets one line naming what will change \u2014 then continue the",
8324
- "task you were on.",
8325
- "",
8326
- "Sub-workers: you may start your own workers with `pai worker run --class <class> -p '<prompt>'`",
8327
- "(Bash tool). Your children run on their own provider and report back to you automatically when",
8328
- "they finish. Send a handoff UP instead of doing work yourself when a cheaper provider would",
8329
- "suffice, the task is out of your scope, or a decision is needed:",
8330
- '`pai worker handoff \'{"kind":"proposal","text":"\u2026","data":{\u2026}}\'` (kinds: proposal,',
8331
- "question, blocker; results are sent for you when you finish). Sibling workers are not a",
8332
- "coordination path \u2014 there is no sideways channel; everything goes up to your parent."
8333
- ].join("\n");
8317
+ function ag2ReportBlock() {
8318
+ const spec = ag2Spec();
8319
+ return [
8320
+ spec.spec,
8321
+ spec.extensions,
8322
+ "Final message: one AG2 `R` message only \u2014 no prose, no code fence, no markdown.",
8323
+ "R requires i (id), r (+ - ~ ? !), t (tests, Name+ Name-), c (path summary|path summary).",
8324
+ "r=+ requires G=+ (gate) and p non-empty (proof command), and every t entry +. z= the answer/note,",
8325
+ "one line, always present. x= what is left (omit if nothing). y= one line why when r is not +.",
8326
+ "Example:",
8327
+ "R",
8328
+ "i=count-audit-ts",
8329
+ "r=+",
8330
+ "G=+",
8331
+ "c=src/audit summary of files counted",
8332
+ "t=Count+",
8333
+ "p=wc -l src/audit/*.ts",
8334
+ "z=19 files, 2523 lines"
8335
+ ];
8336
+ }
8337
+ function jsonReportBlock(lead) {
8338
+ return [
8339
+ lead,
8340
+ '{"changed":[{"path":"\u2026","summary":"\u2026"}],"commands":["\u2026"],"checks":[{"name":"\u2026","ok":true,"detail":"\u2026"}],"open":["\u2026"],"notes":"one line"}'
8341
+ ];
8342
+ }
8343
+ function fileNotInlineBlock() {
8344
+ return [
8345
+ "NEVER put a multi-line or quoted payload inline in a shell command \u2014 no heredocs (<<EOF), no long",
8346
+ "-p '\u2026' specs for child workers, no inline JSON, no inline scripts. ALWAYS write it to a file with",
8347
+ 'the Write tool first and pass the file: -p "$(cat spec.txt)", --file spec.txt, or < spec.txt.',
8348
+ "Inline quoting fails silently or dies with a parse error and burns a worker launch; there are no",
8349
+ "exceptions.",
8350
+ "Never inline code one-liners (python3 -c, node -e, ruby -e and friends) \u2014 the permission layer",
8351
+ 'denies them ("ztk: command denied by permission rules"); write the script to a file with the',
8352
+ "Write tool and run it (python3 script.py), or use jq for JSON inspection."
8353
+ ];
8354
+ }
8355
+ function fullContractPrompt(format) {
8356
+ const lines = [
8357
+ "You are a headless implementation worker, run non-interactively by an orchestrating session.",
8358
+ "You are the worker: this task is yours to finish in this process. Never hand your whole task to a",
8359
+ "single child worker and step back \u2014 that is delegation, not work. Child workers are for two things",
8360
+ "only: (1) PARALLELISING independent parts of your task, each part a self-contained spec; (2) CHEAPER",
8361
+ "bounded sub-tasks \u2014 probes, renders, test runs, lookups \u2014 run one tier down with --class spotcheck or",
8362
+ "--class simple. Always pin --provider anthropic on children.",
8363
+ "You own your children's results: run them in the foreground or poll until each has finished, read",
8364
+ "their result, verify it, and fold it into your own report. When your turn ends this run ends and any",
8365
+ "running child is killed, so never end with a child still running.",
8366
+ "Ending your turn ends this run. There is no later wake-up: never call ScheduleWakeup, never say you",
8367
+ "will pick something up later, never leave a background command as your final action. Finish, then",
8368
+ "report.",
8369
+ "No narration, no timestamps, no greetings, no summaries of what you read; act, verify, then stop.",
8370
+ ...fileNotInlineBlock(),
8371
+ "",
8372
+ `Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
8373
+ "run (the prompt of a resumed run is the operator's too). Answer it FIRST, in one or two plain lines \u2014",
8374
+ "a question gets its answer, an instruction gets one line naming what will change \u2014 then continue the",
8375
+ "task you were on.",
8376
+ "",
8377
+ "Sub-workers: you may start your own workers with `pai worker run --class <class> -p '<prompt>'`",
8378
+ "(Bash tool). Your children run on their own provider and report back to you automatically when",
8379
+ "they finish. Send a handoff UP instead of doing work yourself when a cheaper provider would",
8380
+ "suffice, the task is out of your scope, or a decision is needed:",
8381
+ '`pai worker handoff \'{"kind":"proposal","text":"\u2026","data":{\u2026}}\'` (kinds: proposal,',
8382
+ "question, blocker; results are sent for you when you finish). Sibling workers are not a",
8383
+ "coordination path \u2014 there is no sideways channel; everything goes up to your parent.",
8384
+ ""
8385
+ ];
8386
+ lines.push(
8387
+ ...format === "ag2" ? ag2ReportBlock() : jsonReportBlock(
8388
+ "Your ONE final message is a JSON object and nothing else \u2014 no prose before or after, no code fence:"
8389
+ )
8390
+ );
8391
+ if (format === "json") {
8392
+ lines.push(
8393
+ "changed: files you touched (path + one-line summary). commands: the commands that verify the work.",
8394
+ "checks: each with ok true/false and the evidence in detail. open: what you could not finish, if anything.",
8395
+ "notes: one line, the headline a reviewer reads first."
8396
+ );
8397
+ }
8398
+ return lines.join("\n");
8399
+ }
8400
+ var WORKER_CONTRACT_PROMPT = fullContractPrompt("ag2");
8334
8401
 
8335
8402
  // src/workers/render.ts
8336
8403
  function shortModel(model) {