pi-orchestrator-config 4.4.1 → 4.6.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 (115) hide show
  1. package/README.md +12 -2
  2. package/extensions/coms/coms-p2p.ts +51 -39
  3. package/extensions/coms/coms-shared.ts +7 -5
  4. package/extensions/openai-compatible-discovery/index.ts +131 -122
  5. package/extensions/orchestrator/agents.ts +9 -1
  6. package/extensions/orchestrator/cron-store.ts +92 -37
  7. package/extensions/orchestrator/cron.ts +27 -6
  8. package/extensions/orchestrator/extended-autocomplete.ts +30 -1
  9. package/extensions/orchestrator/project-settings.ts +9 -29
  10. package/extensions/orchestrator/rules.ts +4 -1
  11. package/extensions/orchestrator/session-validation.ts +45 -24
  12. package/extensions/orchestrator/settings-source.ts +139 -0
  13. package/extensions/orchestrator/utils.ts +1 -1
  14. package/extensions/pidash/event-projection.ts +19 -0
  15. package/extensions/pidash/pidash-ui/dist/assets/{index-C1VH6cPk.js → index-CI6UL4g4.js} +1 -1
  16. package/extensions/pidash/pidash-ui/dist/index.html +1 -1
  17. package/extensions/pidash/pidash.ts +9 -3
  18. package/extensions/pidiff/pidiff-ui/dist/assets/{angular-html-ghA4VuM0.js → angular-html-Bfyy3ooe.js} +1 -1
  19. package/extensions/pidiff/pidiff-ui/dist/assets/{angular-ts-BiEwt-Qs.js → angular-ts-nVgE7bZq.js} +1 -1
  20. package/extensions/pidiff/pidiff-ui/dist/assets/{apl-DmYAGNOL.js → apl-tvfwqHpz.js} +1 -1
  21. package/extensions/pidiff/pidiff-ui/dist/assets/{astro-Bju7L81l.js → astro-l8TnD3AA.js} +1 -1
  22. package/extensions/pidiff/pidiff-ui/dist/assets/{blade-C_YtSRJN.js → blade-BdHBbmKC.js} +1 -1
  23. package/extensions/pidiff/pidiff-ui/dist/assets/{c-DxcP9pmN.js → c-D1CypT9z.js} +1 -1
  24. package/extensions/pidiff/pidiff-ui/dist/assets/{chapel-BjVM8zvB.js → chapel-D56VAXz2.js} +1 -1
  25. package/extensions/pidiff/pidiff-ui/dist/assets/{cobol-CyAifE-8.js → cobol-BwRRZSIY.js} +1 -1
  26. package/extensions/pidiff/pidiff-ui/dist/assets/{coffee-BNW4Zttb.js → coffee-Cet_1pvq.js} +1 -1
  27. package/extensions/pidiff/pidiff-ui/dist/assets/{cpp-De6s9lN-.js → cpp-CZ8C9S-h.js} +1 -1
  28. package/extensions/pidiff/pidiff-ui/dist/assets/{crystal-JzhjW33_.js → crystal-EOxgVG_1.js} +1 -1
  29. package/extensions/pidiff/pidiff-ui/dist/assets/{css-DAnI1EKT.js → css-lK_4bfX4.js} +1 -1
  30. package/extensions/pidiff/pidiff-ui/dist/assets/{edge-DlbkxEj4.js → edge-C7pTOYjM.js} +1 -1
  31. package/extensions/pidiff/pidiff-ui/dist/assets/{elixir-BXz6vpDX.js → elixir-D1OnpXXN.js} +1 -1
  32. package/extensions/pidiff/pidiff-ui/dist/assets/{elm-CJcSKM6_.js → elm-DPOsZNNm.js} +1 -1
  33. package/extensions/pidiff/pidiff-ui/dist/assets/{erb-VHOaTR5g.js → erb-D_E9Tmw3.js} +1 -1
  34. package/extensions/pidiff/pidiff-ui/dist/assets/{git-rebase-PAYwCVk-.js → git-rebase-FRK9CwsX.js} +1 -1
  35. package/extensions/pidiff/pidiff-ui/dist/assets/{glimmer-js-CspJJiU_.js → glimmer-js-BNQDK9N_.js} +1 -1
  36. package/extensions/pidiff/pidiff-ui/dist/assets/{glimmer-ts-CSMIai5o.js → glimmer-ts-TWnnEP-7.js} +1 -1
  37. package/extensions/pidiff/pidiff-ui/dist/assets/{glsl-Cw9yKL2B.js → glsl-C90MQFKT.js} +1 -1
  38. package/extensions/pidiff/pidiff-ui/dist/assets/{graphql-D0OMgGsM.js → graphql-hToq3IB7.js} +1 -1
  39. package/extensions/pidiff/pidiff-ui/dist/assets/{hack-DfiAACVA.js → hack-B9KIVyIF.js} +1 -1
  40. package/extensions/pidiff/pidiff-ui/dist/assets/{haml-DU8pAbRK.js → haml-CP3pGCyN.js} +1 -1
  41. package/extensions/pidiff/pidiff-ui/dist/assets/{handlebars-CoaiK3xT.js → handlebars-D1bq27yj.js} +1 -1
  42. package/extensions/pidiff/pidiff-ui/dist/assets/{html-B4C3aTRB.js → html-DDPkOyoL.js} +1 -1
  43. package/extensions/pidiff/pidiff-ui/dist/assets/{html-derivative-C_NJUsxF.js → html-derivative-BR4J_BCH.js} +1 -1
  44. package/extensions/pidiff/pidiff-ui/dist/assets/{http-Cr9w59E3.js → http-BmExgdIq.js} +1 -1
  45. package/extensions/pidiff/pidiff-ui/dist/assets/{hurl-BEScLvfg.js → hurl-UvOw6UjF.js} +1 -1
  46. package/extensions/pidiff/pidiff-ui/dist/assets/index-Bo8LhGN_.js +2796 -0
  47. package/extensions/pidiff/pidiff-ui/dist/assets/{java-hb-3TTCg.js → java-CFjffq9z.js} +1 -1
  48. package/extensions/pidiff/pidiff-ui/dist/assets/{javascript-Bc0DeQtz.js → javascript-D6jF2sRD.js} +1 -1
  49. package/extensions/pidiff/pidiff-ui/dist/assets/{jinja-DoEIYL2y.js → jinja-_MAtAn3c.js} +1 -1
  50. package/extensions/pidiff/pidiff-ui/dist/assets/{jison-C2OZYR2e.js → jison-CzjG9woH.js} +1 -1
  51. package/extensions/pidiff/pidiff-ui/dist/assets/{json-DKeabECb.js → json-Cq0awPRn.js} +1 -1
  52. package/extensions/pidiff/pidiff-ui/dist/assets/{jsx-Dadu-wAB.js → jsx-iffPQUDY.js} +1 -1
  53. package/extensions/pidiff/pidiff-ui/dist/assets/{julia-CpUr0nh2.js → julia-BJh5g1V6.js} +1 -1
  54. package/extensions/pidiff/pidiff-ui/dist/assets/{just-BysHL69Z.js → just-BiplQU-x.js} +1 -1
  55. package/extensions/pidiff/pidiff-ui/dist/assets/{latex-iNrlK36R.js → latex-C9i8ER6l.js} +1 -1
  56. package/extensions/pidiff/pidiff-ui/dist/assets/{liquid-g8x_A5Ru.js → liquid-CJFKQVVC.js} +1 -1
  57. package/extensions/pidiff/pidiff-ui/dist/assets/{lua-B4BSWsUC.js → lua-CQPggDzM.js} +1 -1
  58. package/extensions/pidiff/pidiff-ui/dist/assets/{marko-CHSFXjEi.js → marko-d7_vCT_U.js} +1 -1
  59. package/extensions/pidiff/pidiff-ui/dist/assets/{mdc-js2C5rVG.js → mdc-5MqUEkF6.js} +1 -1
  60. package/extensions/pidiff/pidiff-ui/dist/assets/{nginx-BblTQvRl.js → nginx-BGYrJESG.js} +1 -1
  61. package/extensions/pidiff/pidiff-ui/dist/assets/{nim-ChxMAPPZ.js → nim-BT5d3bbB.js} +1 -1
  62. package/extensions/pidiff/pidiff-ui/dist/assets/{org-CHoj3SWZ.js → org-Dcsoek_G.js} +1 -1
  63. package/extensions/pidiff/pidiff-ui/dist/assets/{perl-3zusLAIc.js → perl-Dzum8ON5.js} +1 -1
  64. package/extensions/pidiff/pidiff-ui/dist/assets/{php-DVx3dbu3.js → php-DMd6jR7H.js} +1 -1
  65. package/extensions/pidiff/pidiff-ui/dist/assets/{pug-DITNjLsK.js → pug-zSUb8M7G.js} +1 -1
  66. package/extensions/pidiff/pidiff-ui/dist/assets/{qml-BYmD788c.js → qml-B-xKh7qQ.js} +1 -1
  67. package/extensions/pidiff/pidiff-ui/dist/assets/{r-Dp_qVPOX.js → r-Bjwskoo1.js} +1 -1
  68. package/extensions/pidiff/pidiff-ui/dist/assets/{razor-DT4CEDjF.js → razor-CXf_WKrJ.js} +1 -1
  69. package/extensions/pidiff/pidiff-ui/dist/assets/{regexp-DVFGbBOe.js → regexp-DT6fzMFy.js} +1 -1
  70. package/extensions/pidiff/pidiff-ui/dist/assets/{rst-njZM0E3u.js → rst-9nW3vkMx.js} +1 -1
  71. package/extensions/pidiff/pidiff-ui/dist/assets/{ruby-gu2hDDCx.js → ruby-qIjs3ahE.js} +1 -1
  72. package/extensions/pidiff/pidiff-ui/dist/assets/{sas-Bo6NwyH7.js → sas-C5jUuuyr.js} +1 -1
  73. package/extensions/pidiff/pidiff-ui/dist/assets/{scss-COdGMqPA.js → scss-DDLNCEB-.js} +1 -1
  74. package/extensions/pidiff/pidiff-ui/dist/assets/{shellscript-DSg7qNC1.js → shellscript-BhivxeP8.js} +1 -1
  75. package/extensions/pidiff/pidiff-ui/dist/assets/{shellsession-CD1RmUpx.js → shellsession-n_duFdfp.js} +1 -1
  76. package/extensions/pidiff/pidiff-ui/dist/assets/{soy-C0QWCEMl.js → soy-Dpt5nO7G.js} +1 -1
  77. package/extensions/pidiff/pidiff-ui/dist/assets/{sql-VoWQdTUw.js → sql-BtjLhqvp.js} +1 -1
  78. package/extensions/pidiff/pidiff-ui/dist/assets/{stata-BEj75h4q.js → stata-D4GbpMWg.js} +1 -1
  79. package/extensions/pidiff/pidiff-ui/dist/assets/{surrealql-Cj1XbW6j.js → surrealql-Cn3MlekA.js} +1 -1
  80. package/extensions/pidiff/pidiff-ui/dist/assets/{svelte-HcwCBvjC.js → svelte-DkuizfXa.js} +1 -1
  81. package/extensions/pidiff/pidiff-ui/dist/assets/{templ-D775vyzu.js → templ-Bu3VqwV1.js} +1 -1
  82. package/extensions/pidiff/pidiff-ui/dist/assets/{tex-DNmK_71Q.js → tex-BmuNCLCK.js} +1 -1
  83. package/extensions/pidiff/pidiff-ui/dist/assets/{ts-tags-KERjFDJj.js → ts-tags-BYWWTBIT.js} +1 -1
  84. package/extensions/pidiff/pidiff-ui/dist/assets/{tsx-CMT8AiHA.js → tsx-DNTVrA8s.js} +1 -1
  85. package/extensions/pidiff/pidiff-ui/dist/assets/{twig-B5uxCA6R.js → twig-CxSsuKw_.js} +1 -1
  86. package/extensions/pidiff/pidiff-ui/dist/assets/{typescript-5zwf3bJw.js → typescript-nBJ-jOMj.js} +1 -1
  87. package/extensions/pidiff/pidiff-ui/dist/assets/{typst-xx02l63O.js → typst-CJ8ooa07.js} +1 -1
  88. package/extensions/pidiff/pidiff-ui/dist/assets/{vue-BvvQRkFj.js → vue-CFh-25xK.js} +1 -1
  89. package/extensions/pidiff/pidiff-ui/dist/assets/{vue-html-BrBDvgBy.js → vue-html-C0o6Mt8F.js} +1 -1
  90. package/extensions/pidiff/pidiff-ui/dist/assets/{vue-vine-1CllPWUz.js → vue-vine-CdvaqX2M.js} +1 -1
  91. package/extensions/pidiff/pidiff-ui/dist/assets/worker-1fjNO6T8.js +164 -0
  92. package/extensions/pidiff/pidiff-ui/dist/assets/{xml-C3nsfg4x.js → xml-DgTZJwkL.js} +1 -1
  93. package/extensions/pidiff/pidiff-ui/dist/assets/{xsl-DDOShcMh.js → xsl-DpEWsE0p.js} +1 -1
  94. package/extensions/pidiff/pidiff-ui/dist/assets/{yaml-BGNXenqm.js → yaml-DnMT_m8N.js} +1 -1
  95. package/extensions/pidiff/pidiff-ui/dist/index.html +1 -1
  96. package/extensions/pidiff/pidiff-ui/package.json +1 -1
  97. package/extensions/providers/acpx-driver.ts +136 -69
  98. package/extensions/providers/claude-driver.ts +20 -18
  99. package/extensions/providers/cursor-acpx-driver.ts +136 -70
  100. package/extensions/providers/cursor-cli-driver.ts +22 -17
  101. package/extensions/providers/gemini-driver.ts +20 -17
  102. package/extensions/providers/index.ts +10 -7
  103. package/extensions/providers/last-thinking-level.ts +336 -70
  104. package/extensions/shared/build-system-prompt.ts +12 -5
  105. package/extensions/shared/create-runtime-provider.ts +7 -1
  106. package/extensions/shared/file-logger.ts +17 -16
  107. package/extensions/shared/logger.ts +7 -3
  108. package/extensions/shared/openai-compatible-discovery.ts +17 -7
  109. package/package.json +10 -2
  110. package/prompts/qodo-review.md +93 -0
  111. package/scripts/daemon-shared.ts +11 -0
  112. package/scripts/pidash-server.ts +2 -2
  113. package/scripts/pidiff-server.ts +2 -2
  114. package/extensions/pidiff/pidiff-ui/dist/assets/index-CisAus9r.js +0 -2801
  115. package/extensions/pidiff/pidiff-ui/dist/assets/worker-BX5VNpEv.js +0 -161
package/README.md CHANGED
@@ -65,6 +65,7 @@ Single extension that provides:
65
65
  | `/issue-review` | Review a GitHub issue spec and fix it |
66
66
  | `/release [flags]` | Create GitHub release with changelog and version bumping |
67
67
  | `/review-local [branch]` | Review local uncommitted or branch changes |
68
+ | `/qodo-review [--autofix] [--fast\|--deep] [--ticket <url>] [path ...]` | Review local changes with Qodo, attach session context, and optionally autofix (max 3 cycles) |
68
69
  | `/review-handler [url] [--autorabbit] [--autoqodo]` | Process PR review comments, fix approved items |
69
70
  | `/domain-model [focus area]` | Scan codebase and build/update a CONTEXT.md domain glossary for consistent AI vocabulary |
70
71
  | `/refine-review <url>` | Refine and improve existing PR review comments |
@@ -76,7 +77,7 @@ Single extension that provides:
76
77
  | `/dream` | Run memory consolidation — extract, deduplicate, maintain topic-based memory |
77
78
  | `/remember <what>` | Save a memory for future sessions |
78
79
  | `/dream-auto` | Toggle automatic memory dreaming (every 3h + session end) |
79
- | `/cron add\|list\|list-all\|remove [--persist]` | Schedule recurring tasks. Session jobs end with the session; `--persist` keeps a job across Pi sessions in this project. Add/remove mutations still work when leader election fails closed on an unsupported platform. For example: `/cron add every 2h check for new issues --persist` or `/cron remove persist:<uuid>`. `/cron list` opens an overlay for the selected jobs (or both when omitted). Persistent delivery is local at-least-once: a crash or leader recovery can repeat a run. |
80
+ | `/cron add\|list\|list-all\|remove [--persist]` | Schedule recurring tasks. Session jobs end with the session; `--persist` keeps a job across Pi sessions in this project. Add/remove mutations still work when leader election fails closed on an unsupported platform. For example: `/cron add every 2h check for new issues --persist` or `/cron remove persist:<uuid>`. `/cron list` opens an overlay for the selected jobs (or both when omitted). Persistent delivery is local at-least-once: a crash or leader recovery can repeat a run. Project cron writes use a stable OS `flock` (included in Docker via `util-linux`) across the entire transaction; native installations need `flock` and a shared filesystem that supports advisory locks. A busy or unavailable lock fails closed after two seconds. **Upgrade gate (enforced):** stop and drain every old Pi process writing crons on the shared mount before starting a version with this kernel lock; old directory-lock writers do not honor `flock` and must not resume. If a legacy `crons.json.mutation.lock` directory exists, new writes fail closed even if its PID is invisible or its heartbeat expired. Only after verifying every old writer cannot resume may an operator remove that stale directory (not `crons.json` or cron tasks). Never remove it merely because a write timed out. |
80
81
  | `/async-kill [name\|id\|all]` | Kill async agents (overlay picker or by name/id) |
81
82
  | `/pi-config-settings [project\|global]` | Interactive TUI settings editor — browse all pi-config settings with scope indicators, smart pickers for providers/models, and immediate save |
82
83
  | `/status` | Unified session snapshot — async agents, cron tasks, git branch, context usage |
@@ -466,6 +467,13 @@ settings; it reads the project locally and stores its regenerable graph in `./gr
466
467
  Native installations require Node.js 22+, npm 12+, Python 3, `make`, and G++; the
467
468
  installer uses the same audited strict installation and failure remains non-fatal.
468
469
 
470
+ **Persistent cron locking:** The image includes `flock` from `util-linux` for kernel-backed,
471
+ cross-container store mutations on shared mounts. The shared filesystem must support
472
+ advisory locks. Before deploying this version alongside old Pi containers, stop and
473
+ drain every legacy cron writer. An existing legacy mutation-lock directory blocks
474
+ new writes until an operator verifies old writers cannot resume and removes only
475
+ that directory. Do not stop running containers merely to test this PR.
476
+
469
477
  **CLI provider binaries (optional `cli_agents`):** The image installs the CLIs used by
470
478
  `cli-*` providers — `claude` (Claude Code), `gemini` (`@google/gemini-cli`), and
471
479
  `agent` (Cursor Agent CLI). Enable with `cli_agents` in settings (e.g.
@@ -710,6 +718,7 @@ PI_PIDIFF_ENABLE=false pi
710
718
  | `-v "$HOME/.claude/credentials.json":"$HOME/.claude/credentials.json":ro` | Claude CLI auth (for acpx-claude / cli-claude models) |
711
719
  | `-v "$HOME/.config/glab-cli":"$HOME/.config/glab-cli":ro` | GitLab CLI config (auth tokens, host settings) |
712
720
  | `-v "$HOME/.coderabbit":"$HOME/.coderabbit":rw` | CodeRabbit CLI auth and review data |
721
+ | `-v "$HOME/.qodo":"$HOME/.qodo":rw` | Qodo CLI auth directory (mount at the container user's `$HOME/.qodo`; with `PI_HOST_USER`, that is `/home/$PI_HOST_USER/.qodo`). Create it on the host first with `mkdir -p "$HOME/.qodo"`; inside the container run `qodo read whoami` and, if signed out, `qodo login`. Host OS-keychain credentials may not work inside the container. |
713
722
  | `-v "$HOME/screenshots":"$HOME/screenshots"` | Share screenshots/images with the agent |
714
723
  | `-v /var/run/docker.sock:/var/run/docker.sock:ro` + `--group-add $(stat -c '%g' /var/run/docker.sock)` | Docker container inspection via `docker-safe` |
715
724
  | `-v /var/run/podman/podman.sock:/var/run/podman/podman.sock:ro` | Podman container inspection via `docker-safe` |
@@ -740,6 +749,7 @@ PI_PIDIFF_ENABLE=false pi
740
749
  | `jq` | JSON processing |
741
750
  | `rg` (ripgrep) | Fast recursive search |
742
751
  | `cr` | CodeRabbit CLI — local AI code reviews |
752
+ | `qodo` | Qodo CLI — local code reviews (`qodo --version`; mount `~/.qodo` as above and authenticate inside the container if needed) |
743
753
  | `curl` | HTTP requests |
744
754
 
745
755
  #### `myk-pi-tools` subcommands
@@ -886,7 +896,7 @@ See [DEVELOPMENT.md](DEVELOPMENT.md) for tips on testing extensions locally, run
886
896
 
887
897
  ## Prerequisites
888
898
 
889
- - [pi](https://github.com/badlogic/pi-mono) (minimum version: **0.84.4**). Pidash uses Pi's `ui_prompt_start` and `ui_prompt_end` events to show prompt wait separately from agent work.
899
+ - [pi](https://github.com/badlogic/pi-mono) (minimum version: **0.87.0**). Pidash uses Pi's `ui_prompt_start` and `ui_prompt_end` events to show prompt wait separately from agent work.
890
900
  - `gh` CLI (for GitHub operations)
891
901
  - `uv` (for Python execution)
892
902
  - `myk-pi-tools` (optional, for `/pr-review` and `/release`)
@@ -848,10 +848,11 @@ export default function (pi: ExtensionAPI) {
848
848
  ackOk(socket, env.msg_id);
849
849
  }
850
850
 
851
- function buildAgentCard(): AgentCard {
851
+ async function buildAgentCard(): Promise<AgentCard> {
852
852
  const ctx = currentCtx;
853
853
  const ident = identity;
854
854
  const pct = ctx ? Math.round(ctx.getContextUsage()?.percent ?? 0) : 0;
855
+ log.debug("build_agent_card", { hasIdentity: !!ident });
855
856
  return {
856
857
  name: ident?.name ?? "unknown",
857
858
  purpose: ident?.purpose ?? "",
@@ -859,22 +860,24 @@ export default function (pi: ExtensionAPI) {
859
860
  color: ident?.color ?? "#36F9F6",
860
861
  context_used_pct: pct,
861
862
  queue_depth: getPendingInboundCount(),
862
- tasks_summary: readTaskSummary(currentCtx?.cwd ?? process.cwd(), currentCtx?.sessionManager?.getSessionId?.()),
863
+ tasks_summary: await readTaskSummary(currentCtx?.cwd ?? process.cwd(), currentCtx?.sessionManager?.getSessionId?.()),
863
864
  };
864
865
  }
865
866
 
866
867
  /** Push current agent card to ping worker so it can respond independently */
867
- function updatePingWorkerCard(): void {
868
+ async function updatePingWorkerCard(): Promise<void> {
869
+ log.debug("update_ping_worker_card", { ready: pingWorkerReady });
868
870
  if (!pingWorker || !pingWorkerReady) return;
869
871
  try {
870
- const card = buildAgentCard();
872
+ const card = await buildAgentCard();
871
873
  const pong: Pong = { type: "pong", msg_id: "", agent_card: card };
872
874
  pingWorker.postMessage({ type: "update_card", pong });
873
875
  } catch { /* ignore */ }
874
876
  }
875
877
 
876
- function handlePing(socket: net.Socket, env: PingEnvelope): void {
877
- const card = buildAgentCard();
878
+ async function handlePing(socket: net.Socket, env: PingEnvelope): Promise<void> {
879
+ log.debug("handle_ping", { msgId: env.msg_id });
880
+ const card = await buildAgentCard();
878
881
  const pong: Pong = { type: "pong", msg_id: env.msg_id, agent_card: card };
879
882
  try {
880
883
  socket.write(JSON.stringify(pong) + "\n");
@@ -883,7 +886,7 @@ export default function (pi: ExtensionAPI) {
883
886
  }
884
887
  try { socket.end(); } catch { /* ignore */ }
885
888
  // Also update worker with latest card
886
- updatePingWorkerCard();
889
+ await updatePingWorkerCard();
887
890
  }
888
891
 
889
892
  function isValidEnvelope(obj: any): obj is Envelope {
@@ -935,7 +938,7 @@ export default function (pi: ExtensionAPI) {
935
938
  } else if (parsed.type === "response") {
936
939
  handleResponse(socket, parsed as ResponseEnvelope);
937
940
  } else if (parsed.type === "ping") {
938
- handlePing(socket, parsed as PingEnvelope);
941
+ void handlePing(socket, parsed as PingEnvelope).catch(() => nack(socket, parsed.msg_id, "internal error"));
939
942
  } else if (parsed.type === "task_update") {
940
943
  handleTaskUpdate(socket, parsed as TaskUpdateEnvelope);
941
944
  } else if (parsed.type === "queue_manage") {
@@ -1994,7 +1997,7 @@ Do not respond to this message.`;
1994
1997
  // Append sender's active tasks for this peer to the prompt
1995
1998
  let promptText = params.prompt;
1996
1999
  try {
1997
- const { listTasksForSession } = require("../pitasks/index.js");
2000
+ const { listTasksForSession } = await import("../pitasks/index.js");
1998
2001
  const targetPiSessionId = target.pi_session_id || target.coms_session_id;
1999
2002
  const allTasks = listTasksForSession(targetPiSessionId, target.cwd) || [];
2000
2003
  const myTasks = allTasks.filter((t: any) =>
@@ -2021,27 +2024,8 @@ Do not respond to this message.`;
2021
2024
  tasks: params.tasks ?? null,
2022
2025
  };
2023
2026
 
2024
- // Send the envelope synchronously and wait for the receiver's ack.
2025
- await sendEnvelope(target.endpoint, env);
2026
- comsSendCalledThisTurn.add(target.coms_session_id);
2027
-
2028
- // Auto-create tasks on the target's task store (sender-side, instant)
2029
- if (params.tasks && params.tasks.length > 0 && target.coms_session_id) {
2030
- try {
2031
- const count = createComsInboundTasks(
2032
- params.tasks,
2033
- { sender_session: identity.coms_session_id, sender_name: identity.name, sender_endpoint: identity.endpoint },
2034
- target.pi_session_id || target.coms_session_id,
2035
- target.cwd,
2036
- );
2037
- if (count > 0) {
2038
- log.info("tasks_auto_created", msg_id, "to", target.name, "count", count);
2039
- }
2040
- } catch { /* best-effort */ }
2041
- }
2042
-
2043
- // Register a pending entry whose promise the receiver-side handleResponse
2044
- // (or the timeout below) will settle.
2027
+ // Register before sending. A peer may acknowledge and respond before
2028
+ // task creation's dynamic import completes.
2045
2029
  let resolveFn!: (v: { response?: any; error?: string | null }) => void;
2046
2030
  let rejectFn!: (e: Error) => void;
2047
2031
  const promise = new Promise<{ response?: any; error?: string | null }>((res, rej) => {
@@ -2069,13 +2053,37 @@ Do not respond to this message.`;
2069
2053
  try { (entry.timer as any).unref?.(); } catch { /* ignore */ }
2070
2054
  pendingReplies.set(msg_id, entry);
2071
2055
 
2072
- log.debug("outbound_prompt", msg_id, "to", target.name, "hops", hops);
2073
-
2074
- // Track pending outbound
2075
2056
  const targetKey = target.name;
2076
2057
  if (!pendingOutbound.has(targetKey)) pendingOutbound.set(targetKey, new Set());
2077
2058
  pendingOutbound.get(targetKey)!.add(msg_id);
2078
2059
 
2060
+ try {
2061
+ await sendEnvelope(target.endpoint, env);
2062
+ } catch (error) {
2063
+ pendingReplies.delete(msg_id);
2064
+ pendingOutbound.get(targetKey)?.delete(msg_id);
2065
+ if (pendingOutbound.get(targetKey)?.size === 0) pendingOutbound.delete(targetKey);
2066
+ if (entry.timer) clearTimeout(entry.timer);
2067
+ throw error;
2068
+ }
2069
+ comsSendCalledThisTurn.add(target.coms_session_id);
2070
+
2071
+ if (params.tasks && params.tasks.length > 0 && target.coms_session_id) {
2072
+ try {
2073
+ const count = await createComsInboundTasks(
2074
+ params.tasks,
2075
+ { sender_session: identity.coms_session_id, sender_name: identity.name, sender_endpoint: identity.endpoint },
2076
+ target.pi_session_id || target.coms_session_id,
2077
+ target.cwd,
2078
+ );
2079
+ if (count > 0) log.info("tasks_auto_created", msg_id, "to", target.name, "count", count);
2080
+ } catch (error) {
2081
+ log.warn("tasks_auto_create_failed", { msg_id, target: target.name, error: error instanceof Error ? error.message : String(error) });
2082
+ }
2083
+ }
2084
+
2085
+ log.debug("outbound_prompt", msg_id, "to", target.name, "hops", hops);
2086
+
2079
2087
  // Peer status from peerCards
2080
2088
  const peerCard = peerCards.get(target.coms_session_id);
2081
2089
  const peer_queue_depth = peerCard?.queue_depth ?? 0;
@@ -2305,6 +2313,7 @@ Do not respond to this message.`;
2305
2313
  }), { description: "Tasks to create on the peer" }),
2306
2314
  }),
2307
2315
  async execute(_callId, params) {
2316
+ log.debug("coms_tasks_create_start", { target: params.target, count: params.tasks?.length ?? 0 });
2308
2317
  if (!identity) throw new Error("coms not initialised");
2309
2318
  if (!params.tasks || params.tasks.length === 0) throw new Error("coms_tasks_create requires at least one task");
2310
2319
  const target = await resolveTarget(params.target);
@@ -2320,7 +2329,7 @@ Do not respond to this message.`;
2320
2329
  if (identity.endpoint) meta.sender_endpoint = identity.endpoint;
2321
2330
 
2322
2331
  try {
2323
- const { createTasksForSession, listTasksForSession, updateTaskForSession } = require("../pitasks/index.js");
2332
+ const { createTasksForSession, listTasksForSession, updateTaskForSession } = await import("../pitasks/index.js");
2324
2333
  const targetPiSessionId = target.pi_session_id || target.coms_session_id;
2325
2334
 
2326
2335
  // Create all tasks in one batch
@@ -2401,12 +2410,13 @@ Do not respond to this message.`;
2401
2410
  task_id: Type.String({ description: "Task ID to delete." }),
2402
2411
  }),
2403
2412
  async execute(_callId, params) {
2413
+ log.debug("coms_task_delete_start", { target: params.target, taskId: params.task_id });
2404
2414
  if (!identity) throw new Error("coms not initialised");
2405
2415
  const target = await resolveTarget(params.target);
2406
2416
  if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
2407
2417
 
2408
2418
  try {
2409
- const { getTaskForSession, deleteTaskForSession } = require("../pitasks/index.js");
2419
+ const { getTaskForSession, deleteTaskForSession } = await import("../pitasks/index.js");
2410
2420
  const targetPiSessionId = target.pi_session_id || target.coms_session_id;
2411
2421
  const task = getTaskForSession(targetPiSessionId, params.task_id, target.cwd);
2412
2422
  if (!task) throw new Error(`Task #${params.task_id} not found`);
@@ -2440,12 +2450,13 @@ Do not respond to this message.`;
2440
2450
  target: Type.String({ description: "Peer name or session_id." }),
2441
2451
  }),
2442
2452
  async execute(_callId, params) {
2453
+ log.debug("coms_task_list_start", { target: params.target });
2443
2454
  if (!identity) throw new Error("coms not initialised");
2444
2455
  const target = await resolveTarget(params.target);
2445
2456
  if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
2446
2457
 
2447
2458
  try {
2448
- const { listTasksForSession } = require("../pitasks/index.js");
2459
+ const { listTasksForSession } = await import("../pitasks/index.js");
2449
2460
  const targetPiSessionId = target.pi_session_id || target.coms_session_id;
2450
2461
  const tasks = listTasksForSession(targetPiSessionId, target.cwd);
2451
2462
  log.debug("coms_task_list", target.name, "tasks", tasks?.length ?? 0);
@@ -2494,12 +2505,13 @@ Do not respond to this message.`;
2494
2505
  task_id: Type.String({ description: "Task ID to retrieve." }),
2495
2506
  }),
2496
2507
  async execute(_callId, params) {
2508
+ log.debug("coms_task_get_start", { target: params.target, taskId: params.task_id });
2497
2509
  if (!identity) throw new Error("coms not initialised");
2498
2510
  const target = await resolveTarget(params.target);
2499
2511
  if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
2500
2512
 
2501
2513
  try {
2502
- const { getTaskForSession } = require("../pitasks/index.js");
2514
+ const { getTaskForSession, listTasksForSession } = await import("../pitasks/index.js");
2503
2515
  const targetPiSessionId = target.pi_session_id || target.coms_session_id;
2504
2516
  const task = getTaskForSession(targetPiSessionId, params.task_id, target.cwd);
2505
2517
  if (!task) throw new Error(`Task #${params.task_id} not found on ${target.name}`);
@@ -2512,7 +2524,6 @@ Do not respond to this message.`;
2512
2524
  if (task.owner) lines.push(`Owner: ${task.owner}`);
2513
2525
  lines.push(`Description: ${desc}`);
2514
2526
  if (task.blockedBy?.length > 0) {
2515
- const { listTasksForSession } = require("../pitasks/index.js");
2516
2527
  const allTasks = listTasksForSession(targetPiSessionId, target.cwd);
2517
2528
  const openBlockers = task.blockedBy.filter((bid: string) => {
2518
2529
  const blocker = allTasks.find((bt: any) => bt.id === bid);
@@ -2549,12 +2560,13 @@ Do not respond to this message.`;
2549
2560
  addBlockedBy: Type.Optional(Type.Array(Type.String(), { description: "Task IDs that block this task" })),
2550
2561
  }),
2551
2562
  async execute(_callId, params) {
2563
+ log.debug("coms_task_update_start", { target: params.target, taskId: params.task_id });
2552
2564
  if (!identity) throw new Error("coms not initialised");
2553
2565
  const target = await resolveTarget(params.target);
2554
2566
  if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
2555
2567
 
2556
2568
  try {
2557
- const { getTaskForSession, updateTaskForSession } = require("../pitasks/index.js");
2569
+ const { getTaskForSession, updateTaskForSession } = await import("../pitasks/index.js");
2558
2570
  const targetPiSessionId = target.pi_session_id || target.coms_session_id;
2559
2571
  const task = getTaskForSession(targetPiSessionId, params.task_id, target.cwd);
2560
2572
  if (!task) throw new Error(`Task #${params.task_id} not found on ${target.name}`);
@@ -461,9 +461,10 @@ export function readFrontmatterFromArgv(argv: string[]): { name?: string; descri
461
461
  }
462
462
  }
463
463
 
464
- export function readTaskSummary(_cwd: string, _sessionId?: string): { total: number; completed: number; in_progress: number } | null {
464
+ export async function readTaskSummary(_cwd: string, _sessionId?: string): Promise<{ total: number; completed: number; in_progress: number } | null> {
465
+ log.debug("read_task_summary", { hasSessionId: !!_sessionId });
465
466
  try {
466
- const { listTasks } = require("../pitasks/index.js");
467
+ const { listTasks } = await import("../pitasks/index.js");
467
468
  const tasks = listTasks();
468
469
  if (!tasks || tasks.length === 0) return null;
469
470
  let total = 0, completed = 0, in_progress = 0;
@@ -650,17 +651,18 @@ const MAX_TASK_FIELD_LEN = 2000;
650
651
  * Runs on the SENDER side — uses createTaskForSession with the target's session ID.
651
652
  * TaskStore handles locking, file format, everything.
652
653
  */
653
- export function createComsInboundTasks(
654
+ export async function createComsInboundTasks(
654
655
  tasks: Array<{ subject: string; description: string }>,
655
656
  origin: ComsOrigin,
656
657
  targetSessionId: string,
657
658
  targetCwd?: string,
658
- ): number {
659
+ ): Promise<number> {
660
+ log.debug("create_coms_inbound_tasks", { targetSessionId, count: tasks?.length ?? 0 });
659
661
  if (!tasks || tasks.length === 0 || !targetSessionId) return 0;
660
662
 
661
663
  let createTaskForSession: any;
662
664
  try {
663
- createTaskForSession = require("../pitasks/index.js").createTaskForSession;
665
+ createTaskForSession = (await import("../pitasks/index.js")).createTaskForSession;
664
666
  } catch { return 0; }
665
667
  if (!createTaskForSession) return 0;
666
668