pi-orchestrator-config 4.4.0 → 4.5.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.
- package/README.md +44 -2
- package/extensions/coms/coms-p2p.ts +51 -39
- package/extensions/coms/coms-shared.ts +7 -5
- package/extensions/openai-compatible-discovery/index.ts +29 -1
- package/extensions/orchestrator/agents.ts +9 -1
- package/extensions/orchestrator/async-agents.ts +3 -1
- package/extensions/orchestrator/graft.ts +306 -0
- package/extensions/orchestrator/index.ts +2 -0
- package/extensions/orchestrator/memory-tools.ts +1 -1
- package/extensions/orchestrator/project-settings.ts +15 -29
- package/extensions/orchestrator/rules.ts +4 -1
- package/extensions/orchestrator/session-validation.ts +45 -24
- package/extensions/orchestrator/settings-source.ts +139 -0
- package/extensions/orchestrator/settings-tui-helpers.ts +2 -3
- package/extensions/orchestrator/settings-tui.ts +1 -3
- package/extensions/orchestrator/status-bar.ts +1 -0
- package/extensions/orchestrator/subagent-tool.ts +3 -2
- package/extensions/orchestrator/utils.ts +1 -1
- package/extensions/pidash/event-projection.ts +19 -0
- package/extensions/pidash/model-info.ts +7 -0
- package/extensions/pidash/pidash-ui/dist/assets/{index-Ehfwjr9d.js → index-CI6UL4g4.js} +6 -6
- package/extensions/pidash/pidash-ui/dist/assets/index-hM6gC5Yg.css +2 -0
- package/extensions/pidash/pidash-ui/dist/index.html +2 -2
- package/extensions/pidash/pidash-ui/src/App.tsx +2 -1
- package/extensions/pidash/pidash-ui/src/components/InfoBar.tsx +79 -15
- package/extensions/pidash/pidash-ui/src/lib/format-total.ts +16 -0
- package/extensions/pidash/pidash-ui/src/lib/model-capabilities.ts +11 -0
- package/extensions/pidash/pidash.ts +30 -7
- package/extensions/pidiff/pidiff-ui/dist/assets/{angular-html-ghA4VuM0.js → angular-html-Bfyy3ooe.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{angular-ts-BiEwt-Qs.js → angular-ts-nVgE7bZq.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{apl-DmYAGNOL.js → apl-tvfwqHpz.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{astro-Bju7L81l.js → astro-l8TnD3AA.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{blade-C_YtSRJN.js → blade-BdHBbmKC.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{c-DxcP9pmN.js → c-D1CypT9z.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{chapel-BjVM8zvB.js → chapel-D56VAXz2.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{cobol-CyAifE-8.js → cobol-BwRRZSIY.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{coffee-BNW4Zttb.js → coffee-Cet_1pvq.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{cpp-De6s9lN-.js → cpp-CZ8C9S-h.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{crystal-JzhjW33_.js → crystal-EOxgVG_1.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{css-DAnI1EKT.js → css-lK_4bfX4.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{edge-DlbkxEj4.js → edge-C7pTOYjM.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{elixir-BXz6vpDX.js → elixir-D1OnpXXN.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{elm-CJcSKM6_.js → elm-DPOsZNNm.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{erb-VHOaTR5g.js → erb-D_E9Tmw3.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{git-rebase-PAYwCVk-.js → git-rebase-FRK9CwsX.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{glimmer-js-CspJJiU_.js → glimmer-js-BNQDK9N_.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{glimmer-ts-CSMIai5o.js → glimmer-ts-TWnnEP-7.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{glsl-Cw9yKL2B.js → glsl-C90MQFKT.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{graphql-D0OMgGsM.js → graphql-hToq3IB7.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{hack-DfiAACVA.js → hack-B9KIVyIF.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{haml-DU8pAbRK.js → haml-CP3pGCyN.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{handlebars-CoaiK3xT.js → handlebars-D1bq27yj.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{html-B4C3aTRB.js → html-DDPkOyoL.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{html-derivative-C_NJUsxF.js → html-derivative-BR4J_BCH.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{http-Cr9w59E3.js → http-BmExgdIq.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{hurl-BEScLvfg.js → hurl-UvOw6UjF.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/index-Bo8LhGN_.js +2796 -0
- package/extensions/pidiff/pidiff-ui/dist/assets/{java-hb-3TTCg.js → java-CFjffq9z.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{javascript-Bc0DeQtz.js → javascript-D6jF2sRD.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{jinja-DoEIYL2y.js → jinja-_MAtAn3c.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{jison-C2OZYR2e.js → jison-CzjG9woH.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{json-DKeabECb.js → json-Cq0awPRn.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{jsx-Dadu-wAB.js → jsx-iffPQUDY.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{julia-CpUr0nh2.js → julia-BJh5g1V6.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{just-BysHL69Z.js → just-BiplQU-x.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{latex-iNrlK36R.js → latex-C9i8ER6l.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{liquid-g8x_A5Ru.js → liquid-CJFKQVVC.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{lua-B4BSWsUC.js → lua-CQPggDzM.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{marko-CHSFXjEi.js → marko-d7_vCT_U.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{mdc-js2C5rVG.js → mdc-5MqUEkF6.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{nginx-BblTQvRl.js → nginx-BGYrJESG.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{nim-ChxMAPPZ.js → nim-BT5d3bbB.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{org-CHoj3SWZ.js → org-Dcsoek_G.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{perl-3zusLAIc.js → perl-Dzum8ON5.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{php-DVx3dbu3.js → php-DMd6jR7H.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{pug-DITNjLsK.js → pug-zSUb8M7G.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{qml-BYmD788c.js → qml-B-xKh7qQ.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{r-Dp_qVPOX.js → r-Bjwskoo1.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{razor-DT4CEDjF.js → razor-CXf_WKrJ.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{regexp-DVFGbBOe.js → regexp-DT6fzMFy.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{rst-njZM0E3u.js → rst-9nW3vkMx.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{ruby-gu2hDDCx.js → ruby-qIjs3ahE.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{sas-Bo6NwyH7.js → sas-C5jUuuyr.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{scss-COdGMqPA.js → scss-DDLNCEB-.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{shellscript-DSg7qNC1.js → shellscript-BhivxeP8.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{shellsession-CD1RmUpx.js → shellsession-n_duFdfp.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{soy-C0QWCEMl.js → soy-Dpt5nO7G.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{sql-VoWQdTUw.js → sql-BtjLhqvp.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{stata-BEj75h4q.js → stata-D4GbpMWg.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{surrealql-Cj1XbW6j.js → surrealql-Cn3MlekA.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{svelte-HcwCBvjC.js → svelte-DkuizfXa.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{templ-D775vyzu.js → templ-Bu3VqwV1.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{tex-DNmK_71Q.js → tex-BmuNCLCK.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{ts-tags-KERjFDJj.js → ts-tags-BYWWTBIT.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{tsx-CMT8AiHA.js → tsx-DNTVrA8s.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{twig-B5uxCA6R.js → twig-CxSsuKw_.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{typescript-5zwf3bJw.js → typescript-nBJ-jOMj.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{typst-xx02l63O.js → typst-CJ8ooa07.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{vue-BvvQRkFj.js → vue-CFh-25xK.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{vue-html-BrBDvgBy.js → vue-html-C0o6Mt8F.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{vue-vine-1CllPWUz.js → vue-vine-CdvaqX2M.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/worker-1fjNO6T8.js +164 -0
- package/extensions/pidiff/pidiff-ui/dist/assets/{xml-C3nsfg4x.js → xml-DgTZJwkL.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{xsl-DDOShcMh.js → xsl-DpEWsE0p.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{yaml-BGNXenqm.js → yaml-DnMT_m8N.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/index.html +1 -1
- package/extensions/pidiff/pidiff-ui/package.json +1 -1
- package/extensions/providers/acpx-driver.ts +136 -69
- package/extensions/providers/claude-driver.ts +20 -18
- package/extensions/providers/cursor-acpx-driver.ts +136 -70
- package/extensions/providers/cursor-cli-driver.ts +22 -17
- package/extensions/providers/gemini-driver.ts +20 -17
- package/extensions/providers/index.ts +22 -7
- package/extensions/providers/last-thinking-level.ts +174 -0
- package/extensions/shared/build-system-prompt.ts +12 -5
- package/extensions/shared/create-runtime-provider.ts +1 -1
- package/extensions/shared/file-logger.ts +22 -18
- package/extensions/shared/format-total.ts +11 -0
- package/extensions/shared/install-logger.mjs +18 -0
- package/extensions/shared/logger-core.mjs +35 -0
- package/extensions/shared/logger.ts +10 -27
- package/extensions/shared/openai-compatible-discovery.ts +28 -0
- package/extensions/shared/types.ts +4 -0
- package/package.json +11 -2
- package/scripts/daemon-shared.ts +11 -0
- package/scripts/graft-allow-scripts.mjs +47 -0
- package/scripts/pidash-server.ts +9 -5
- package/scripts/pidash-session-state.ts +10 -0
- package/scripts/pidiff-server.ts +2 -2
- package/settings-keys.json +21 -0
- package/extensions/pidash/pidash-ui/dist/assets/index-j_XBAdtq.css +0 -2
- package/extensions/pidiff/pidiff-ui/dist/assets/index-CisAus9r.js +0 -2801
- package/extensions/pidiff/pidiff-ui/dist/assets/worker-BX5VNpEv.js +0 -161
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Single extension that provides:
|
|
|
28
28
|
| **Git status** | Live git status in status line with colored icons — updates after every tool call. Shows clickable `#N` when the branch has an open PR. Last-activity clock `⏱ HH:MM (Xm/Xh ago)` shows time since last response |
|
|
29
29
|
| **Desktop notifications** | Notifies via `notify-send` on task completion, waiting for input, and action required |
|
|
30
30
|
| **File preview** | Serves generated HTML/frontend files via HTTP for browser preview from container |
|
|
31
|
+
| **Graft integration** | When `graft_enable=true`, main agents and subagents query the local Graft graph before raw project navigation on substantive prompts. Children only consume graphs. Stale graphs remain usable, retrieval failures fall back to raw tools, and main processes coordinate rebuilds with a cross-process lock. |
|
|
31
32
|
| **Pidash dashboard** | Live web dashboard — multi-session monitoring, browser messaging, model switching, live session name updates, reasoning token display |
|
|
32
33
|
| **Pidiff viewer** | Per-project diff viewer with review comments — branch diffs, file tree, inline comments, git-based ignore rules |
|
|
33
34
|
| **Dreaming** | Background memory consolidation — extracts memories from sessions, deduplicates, maintains topic-based memory |
|
|
@@ -157,7 +158,7 @@ The installer covers:
|
|
|
157
158
|
|
|
158
159
|
- **Pi Packages** — pi-config, pi-vertex-claude, pi-web-access, myk-pi-tools, bun
|
|
159
160
|
- **Python Tools** — prek
|
|
160
|
-
- **npm Packages** — mcpc, acpx, agent-browser
|
|
161
|
+
- **npm Packages** — mcpc, acpx, agent-browser, graft
|
|
161
162
|
- **Browser Automation** — playwright + chromium
|
|
162
163
|
- **Environment Setup** — gitignore configuration
|
|
163
164
|
|
|
@@ -404,6 +405,29 @@ Set `image_model` in `pi-config-settings.json` or use `PI_IMAGE_MODEL` env var.
|
|
|
404
405
|
|
|
405
406
|
In containers, images are auto-served via HTTP for browser preview.
|
|
406
407
|
|
|
408
|
+
### Graft repository graph (optional)
|
|
409
|
+
|
|
410
|
+
Set `graft_enable` to `true` in `.pi/pi-config-settings.json` (or set
|
|
411
|
+
`PI_GRAFT_ENABLE=true`) to enable local Graft repository graph context:
|
|
412
|
+
|
|
413
|
+
```json
|
|
414
|
+
{
|
|
415
|
+
"graft_enable": true
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Graft requires Node.js 22+, npm 12+, node-gyp prerequisites (Python 3, `make`, and
|
|
420
|
+
G++), and the `graft` CLI. The container includes them; native installs can use
|
|
421
|
+
`uv run scripts/install.py`. The installer downloads `@nanonets/graft@latest` with
|
|
422
|
+
scripts disabled into a dedicated npm prefix, audits that isolated dependency tree
|
|
423
|
+
for lifecycle scripts, and strictly rebuilds it with only those packages approved.
|
|
424
|
+
The completed prefix is then published under the global npm prefix and its `graft`
|
|
425
|
+
binary linked globally, so unrelated global packages are never evaluated. The
|
|
426
|
+
installer verifies `graft --version`; `DO_NOT_TRACK=1` disables Graft telemetry.
|
|
427
|
+
When enabled, Graft reads the trusted project and writes a regenerable `./graft/`
|
|
428
|
+
cache. Delete that directory or set `graft_enable` to `false` to disable it. If the
|
|
429
|
+
native CLI is unavailable or its installation fails, pi continues without Graft.
|
|
430
|
+
|
|
407
431
|
### Cache Miss Notices
|
|
408
432
|
|
|
409
433
|
Enable `showCacheMissNotices` in pi settings to see transcript notices on significant prompt-cache misses — useful for investigating unexpected token costs:
|
|
@@ -425,6 +449,23 @@ Run pi inside a disposable container for **filesystem isolation** — the agent
|
|
|
425
449
|
- **Consistent tooling** — All required tools pre-installed in a single image
|
|
426
450
|
- **Disposable** — Container is destroyed after each session (`--rm`)
|
|
427
451
|
|
|
452
|
+
**Graft (optional `graft_enable`):** The image installs the latest
|
|
453
|
+
`@nanonets/graft` with `DO_NOT_TRACK=1`. It downloads with scripts disabled, derives
|
|
454
|
+
an exact script allowlist by auditing the installed package's reachable dependency
|
|
455
|
+
metadata in a dedicated npm prefix, then strictly rebuilds that isolated tree with
|
|
456
|
+
those approvals. Only after the rebuild succeeds is the completed prefix moved under
|
|
457
|
+
the global npm prefix and its `graft` binary linked globally. This prevents unrelated
|
|
458
|
+
global packages from entering npm's strict preflight. The allowlist is command-scoped
|
|
459
|
+
and changes with `@latest`; other npm commands keep npm 12's install-script
|
|
460
|
+
protections. The image includes Python 3,
|
|
461
|
+
`make`, and the GCC/G++ toolchain required by node-gyp for Graft and other native
|
|
462
|
+
npm packages; these remain available for reliable package rebuilds. The build then
|
|
463
|
+
verifies `graft --version`. Enable Graft in trusted project or global pi-config
|
|
464
|
+
settings; it reads the project locally and stores its regenerable graph in `./graft/`. Set
|
|
465
|
+
`graft_enable` to `false` and delete that directory to disable and remove the cache.
|
|
466
|
+
Native installations require Node.js 22+, npm 12+, Python 3, `make`, and G++; the
|
|
467
|
+
installer uses the same audited strict installation and failure remains non-fatal.
|
|
468
|
+
|
|
428
469
|
**CLI provider binaries (optional `cli_agents`):** The image installs the CLIs used by
|
|
429
470
|
`cli-*` providers — `claude` (Claude Code), `gemini` (`@google/gemini-cli`), and
|
|
430
471
|
`agent` (Cursor Agent CLI). Enable with `cli_agents` in settings (e.g.
|
|
@@ -691,6 +732,7 @@ PI_PIDIFF_ENABLE=false pi
|
|
|
691
732
|
| `acpx` | Agent proxy for remote models |
|
|
692
733
|
| `kubectl` / `oc` | Kubernetes and OpenShift CLI |
|
|
693
734
|
| `agent-browser` | Browser automation CLI (navigate, click, screenshot, forms) |
|
|
735
|
+
| `graft` | Local repository code graph for opt-in `graft_enable`; graph cache is stored in the mounted project’s `./graft/` directory |
|
|
694
736
|
| `procps` | Process utilities (ps, top, pgrep, pkill) |
|
|
695
737
|
| `passwd` | `usermod`/`groupmod` — init remaps user `node` to `PI_HOST_UID`/`PI_HOST_GID` |
|
|
696
738
|
| `docker` / `podman` | Container CLIs (used via `docker-safe` read-only wrapper) |
|
|
@@ -844,7 +886,7 @@ See [DEVELOPMENT.md](DEVELOPMENT.md) for tips on testing extensions locally, run
|
|
|
844
886
|
|
|
845
887
|
## Prerequisites
|
|
846
888
|
|
|
847
|
-
- [pi](https://github.com/badlogic/pi-mono) (minimum version: **0.
|
|
889
|
+
- [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.
|
|
848
890
|
- `gh` CLI (for GitHub operations)
|
|
849
891
|
- `uv` (for Python execution)
|
|
850
892
|
- `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
|
-
|
|
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 } =
|
|
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
|
-
//
|
|
2025
|
-
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 =
|
|
665
|
+
createTaskForSession = (await import("../pitasks/index.js")).createTaskForSession;
|
|
664
666
|
} catch { return 0; }
|
|
665
667
|
if (!createTaskForSession) return 0;
|
|
666
668
|
|
|
@@ -4,13 +4,16 @@ import { createRuntimeProvider } from "../shared/create-runtime-provider.js";
|
|
|
4
4
|
import { createLogger } from "../shared/logger.js";
|
|
5
5
|
import {
|
|
6
6
|
OpenAiCompatibleDiscoveryCache,
|
|
7
|
+
buildLiteLlmCapabilitiesUrl,
|
|
7
8
|
buildOpenAiCompatibleModelsRequest,
|
|
9
|
+
enrichLiteLlmReasoning,
|
|
8
10
|
findEligibleOpenAiCompatibleProviderConfigsResult,
|
|
9
11
|
formatOpenAiCompatibleDiscoverySummary,
|
|
10
12
|
materializeOpenAiCompatibleModels,
|
|
11
13
|
openAiCompatibleConnectionFingerprint,
|
|
12
14
|
redactOpenAiCompatibleDiagnostic,
|
|
13
15
|
resolveStaticOpenAiCompatibleHeaders,
|
|
16
|
+
type LiteLlmCapabilityRecord,
|
|
14
17
|
type OpenAiCompatibleModelRecord,
|
|
15
18
|
type ResolvedOpenAiCompatibleConnection,
|
|
16
19
|
} from "../shared/openai-compatible-discovery.js";
|
|
@@ -28,6 +31,18 @@ function responseRecords(payload: unknown): OpenAiCompatibleModelRecord[] {
|
|
|
28
31
|
});
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
function capabilityRecords(payload: unknown): LiteLlmCapabilityRecord[] {
|
|
35
|
+
if (!payload || typeof payload !== "object" || !Array.isArray((payload as { data?: unknown }).data))
|
|
36
|
+
throw new Error("LiteLLM capability response must contain a data array");
|
|
37
|
+
return (payload as { data: unknown[] }).data.filter(
|
|
38
|
+
(record): record is LiteLlmCapabilityRecord => Boolean(record) && typeof record === "object" && !Array.isArray(record),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isLiteLlmProvider(sourceProviderId: string, source: Provider): boolean {
|
|
43
|
+
return `${sourceProviderId} ${source.name ?? ""}`.toLowerCase().includes("litellm");
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
function resolvedConnection(source: Provider, auth?: AuthResult, staticHeaders?: Record<string, string>): ResolvedOpenAiCompatibleConnection {
|
|
32
47
|
return {
|
|
33
48
|
baseUrl: auth?.auth.baseUrl ?? source.baseUrl,
|
|
@@ -84,7 +99,20 @@ async function discoverProvider(
|
|
|
84
99
|
signal: AbortSignal.any([signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]),
|
|
85
100
|
});
|
|
86
101
|
if (!response.ok) throw new Error(`OpenAI-compatible /v1/models returned HTTP ${response.status}`);
|
|
87
|
-
|
|
102
|
+
const records = responseRecords(await response.json());
|
|
103
|
+
if (!isLiteLlmProvider(sourceProviderId, source)) return records;
|
|
104
|
+
try {
|
|
105
|
+
const capabilityResponse = await fetch(buildLiteLlmCapabilitiesUrl(request.url), {
|
|
106
|
+
headers: request.headers,
|
|
107
|
+
redirect: "error",
|
|
108
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]),
|
|
109
|
+
});
|
|
110
|
+
if (!capabilityResponse.ok) throw new Error(`LiteLLM capability endpoint returned HTTP ${capabilityResponse.status}`);
|
|
111
|
+
return enrichLiteLlmReasoning(records, capabilityRecords(await capabilityResponse.json()));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
log.warn(`${sourceProviderId}: reasoning capability enrichment unavailable`, redactOpenAiCompatibleDiagnostic(error instanceof Error ? error.message : String(error), connection));
|
|
114
|
+
return records;
|
|
115
|
+
}
|
|
88
116
|
} catch (error) {
|
|
89
117
|
log.warn(`${sourceProviderId}: discovery refresh failed`, redactOpenAiCompatibleDiagnostic(error instanceof Error ? error.message : String(error), connection));
|
|
90
118
|
throw error;
|
|
@@ -9,7 +9,13 @@
|
|
|
9
9
|
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
11
|
import * as path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
12
13
|
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
import { createLogger } from "../shared/logger.js";
|
|
15
|
+
|
|
16
|
+
const log = createLogger("agents");
|
|
17
|
+
|
|
18
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
19
|
|
|
14
20
|
export type AgentScope = "user" | "project" | "both";
|
|
15
21
|
|
|
@@ -103,7 +109,9 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
|
103
109
|
* and the agents are in ../../agents/ relative to this file.
|
|
104
110
|
*/
|
|
105
111
|
function getPackageAgentsDir(): string {
|
|
106
|
-
|
|
112
|
+
const resolvedPath = path.resolve(MODULE_DIR, "..", "..", "agents");
|
|
113
|
+
log.debug("resolved package agents directory", { path: resolvedPath });
|
|
114
|
+
return resolvedPath;
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
@@ -15,6 +15,7 @@ const require = createRequire(import.meta.url);
|
|
|
15
15
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import type { AgentConfig, AgentDiscoveryResult } from "./agents.js";
|
|
17
17
|
import { resolveAgentModelProvider } from "./resolve-agent-model.js";
|
|
18
|
+
import { withGraftTools } from "./graft.js";
|
|
18
19
|
import { getPiInvocation, getProjectTmpDir, parseProcStartTime, djb2Hash } from "./utils.js";
|
|
19
20
|
import { addReviewerPending, recordReviewerResult, countFindings, readReviewState } from "./pi-config-review-state.js";
|
|
20
21
|
import {
|
|
@@ -924,7 +925,8 @@ export function registerAsyncAgents(
|
|
|
924
925
|
piArgs.push("--session-id", deterministicSessionId);
|
|
925
926
|
if (effectiveModel) piArgs.push("--model", effectiveModel);
|
|
926
927
|
if (effectiveProvider) piArgs.push("--provider", effectiveProvider);
|
|
927
|
-
|
|
928
|
+
const tools = withGraftTools(agent.tools, getSetting(cwd, "graft_enable"));
|
|
929
|
+
if (tools?.length) piArgs.push("--tools", tools.join(","));
|
|
928
930
|
|
|
929
931
|
if (agent.systemPrompt?.trim()) {
|
|
930
932
|
const promptPath = path.join(workerDir, "system-prompt.md");
|