pi-orchestrator-config 4.17.0 → 4.18.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 +4 -2
- package/extensions/coms/coms-p2p.ts +33 -10
- package/extensions/coms/coms-wrapper.ts +4 -1
- package/extensions/image-gen/image-gen.ts +10 -1
- package/extensions/openai-compatible-discovery/index.ts +8 -8
- package/extensions/orchestrator/ask-user.ts +5 -4
- package/extensions/orchestrator/async-status-ui.ts +1 -1
- package/extensions/orchestrator/cron.ts +1 -1
- package/extensions/orchestrator/graft.ts +8 -4
- package/extensions/orchestrator/index.ts +5 -3
- package/extensions/orchestrator/memory-embeddings.ts +4 -3
- package/extensions/orchestrator/memory-scoring.ts +2 -2
- package/extensions/orchestrator/memory-tools.ts +26 -22
- package/extensions/orchestrator/memory-write.ts +154 -7
- package/extensions/orchestrator/project-settings.ts +19 -1
- package/extensions/orchestrator/review-learning.ts +1 -1
- package/extensions/orchestrator/review-ui.ts +3 -3
- package/extensions/orchestrator/rules.ts +2 -2
- package/extensions/orchestrator/session-search.ts +2 -0
- package/extensions/orchestrator/settings-tui-helpers.ts +1 -1
- package/extensions/orchestrator/status.ts +1 -2
- package/extensions/orchestrator/subagent-tool.ts +3 -3
- package/extensions/pidash/pidash-ui/tsconfig.json +19 -5
- package/extensions/pidash/pidash.ts +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{angular-html-Bfyy3ooe.js → angular-html-CwyqPeSz.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{angular-ts-nVgE7bZq.js → angular-ts-Dcs1FIbz.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{apl-tvfwqHpz.js → apl-C7xBLXGx.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{astro-l8TnD3AA.js → astro-BFUwqfrd.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{blade-BdHBbmKC.js → blade-BjNOHmhK.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{c-D1CypT9z.js → c-CZ-CwvED.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{chapel-D56VAXz2.js → chapel-CAUGdYR4.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{cobol-BwRRZSIY.js → cobol-BXQ0NrmC.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{coffee-Cet_1pvq.js → coffee-AFjtB2X3.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{cpp-CZ8C9S-h.js → cpp-D1TFCGlp.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{crystal-EOxgVG_1.js → crystal-CKcHF4xe.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{css-lK_4bfX4.js → css-CLC5Cqcn.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{edge-C7pTOYjM.js → edge-BG6TSzTp.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{elixir-D1OnpXXN.js → elixir-BZH067kD.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{elm-DPOsZNNm.js → elm-lEDyiWLo.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{erb-D_E9Tmw3.js → erb-BR8WS6Cx.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{git-rebase-FRK9CwsX.js → git-rebase-BdxT0FYt.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{glimmer-js-BNQDK9N_.js → glimmer-js-VvMDmlEk.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{glimmer-ts-TWnnEP-7.js → glimmer-ts-D03eMuZZ.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{glsl-C90MQFKT.js → glsl-BrMuNIO1.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{graphql-hToq3IB7.js → graphql-DbYNf72H.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{hack-B9KIVyIF.js → hack-BBqyAZi-.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{haml-CP3pGCyN.js → haml-DjcXrKGH.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{handlebars-D1bq27yj.js → handlebars-CzNOAnV2.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{html-DDPkOyoL.js → html-BJVm4DNc.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{html-derivative-BR4J_BCH.js → html-derivative-IExyEGQB.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{http-BmExgdIq.js → http-DJ3plDxM.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{hurl-UvOw6UjF.js → hurl-BrB7dEfw.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{index-Bo8LhGN_.js → index-CNGTUxlS.js} +3 -3
- package/extensions/pidiff/pidiff-ui/dist/assets/{java-CFjffq9z.js → java-CCGQ3m75.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{javascript-D6jF2sRD.js → javascript-Ku50Wq9K.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{jinja-_MAtAn3c.js → jinja-CLeKp8_-.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{jison-CzjG9woH.js → jison-Cb8rkTso.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{json-Cq0awPRn.js → json-CqjpqBY8.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{jsx-iffPQUDY.js → jsx-GYEFSJQk.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{julia-BJh5g1V6.js → julia-CAuSCy3z.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{just-BiplQU-x.js → just-Db5dhjrQ.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{latex-C9i8ER6l.js → latex-DF6G6r1P.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{liquid-CJFKQVVC.js → liquid-Cq7unoCR.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{lua-CQPggDzM.js → lua-JjuxLI5P.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{marko-d7_vCT_U.js → marko-Dk-3Aeut.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{mdc-5MqUEkF6.js → mdc-CJyvmiwK.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{nginx-BGYrJESG.js → nginx-BIsypiXC.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{nim-BT5d3bbB.js → nim-D2jvctay.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{org-Dcsoek_G.js → org-BRGhyUU7.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{perl-Dzum8ON5.js → perl-BmxxOv4e.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{php-DMd6jR7H.js → php-D7e1Hsbt.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{pug-zSUb8M7G.js → pug-CrUG5Kh6.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{qml-B-xKh7qQ.js → qml-PR0-5H8u.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{r-Bjwskoo1.js → r-WvcAB6gY.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{razor-CXf_WKrJ.js → razor-B1k_Gbl4.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{regexp-DT6fzMFy.js → regexp-DuGWerYo.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{rst-9nW3vkMx.js → rst-DGuCLeqg.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{ruby-qIjs3ahE.js → ruby-BqvCP7Bi.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{sas-C5jUuuyr.js → sas-ClnFwsgU.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{scss-DDLNCEB-.js → scss-CT4XA26v.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{shellscript-BhivxeP8.js → shellscript-DgrnvgHZ.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{shellsession-n_duFdfp.js → shellsession-C6PgLTo4.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{soy-Dpt5nO7G.js → soy-nIOIsh8p.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{sql-BtjLhqvp.js → sql-B2jLPOKf.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{stata-D4GbpMWg.js → stata-Cy-cTFpi.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{surrealql-Cn3MlekA.js → surrealql-DVUMTW4V.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{svelte-DkuizfXa.js → svelte-Cvvw3d91.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{templ-Bu3VqwV1.js → templ-BjdAQ08a.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{tex-BmuNCLCK.js → tex-DXSGpBNr.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{ts-tags-BYWWTBIT.js → ts-tags-DORvofmG.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{tsx-DNTVrA8s.js → tsx-RoEQTmWU.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{twig-CxSsuKw_.js → twig-Duoy_8TR.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{typescript-nBJ-jOMj.js → typescript-CfCnqsgS.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{typst-CJ8ooa07.js → typst-D5JfUJK-.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{vue-CFh-25xK.js → vue-BYc_7aoa.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{vue-html-C0o6Mt8F.js → vue-html-BvkXM02d.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{vue-vine-CdvaqX2M.js → vue-vine-DKo-YX4e.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{xml-DgTZJwkL.js → xml-CvbayHmE.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{xsl-DpEWsE0p.js → xsl-CMBzdYyb.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{yaml-DnMT_m8N.js → yaml-B9JM0AY8.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/index.html +1 -1
- package/extensions/pidiff/pidiff-ui/package.json +1 -1
- package/extensions/pidiff/pidiff-ui/src/App.tsx +5 -5
- package/extensions/pidiff/pidiff-ui/tsconfig.json +15 -4
- package/extensions/pidiff/pidiff.ts +4 -1
- package/extensions/providers/last-thinking-level.ts +4 -1
- package/extensions/shared/build-system-prompt.ts +3 -2
- package/extensions/shared/daemon-manager.ts +8 -2
- package/extensions/shared/logger-core.d.mts +35 -0
- package/extensions/shared/stream-builder.ts +9 -3
- package/extensions/shared/ui/tsconfig.json +22 -0
- package/package.json +2 -1
- package/rules/60-task-tracking.md +1 -1
- package/extensions/orchestrator/btw.ts +0 -210
- package/extensions/shared/ui/resizable.tsx +0 -48
package/README.md
CHANGED
|
@@ -16,7 +16,6 @@ Single extension that provides:
|
|
|
16
16
|
| **`list_models`** | List available models and providers for subagent model override |
|
|
17
17
|
| **Async background agents** | Spawn agents in background with `async: true` — results surface automatically when complete. Fullscreen overlay dashboard with live output, keyboard nav, and kill support (`/async-status`: `x` kills, `Space` multi-selects, `a` kills all). On **acpx** parents, optional async is coerced to sync; dream/cron need `internal_operations_provider` + `internal_operations_model`. `cli-*` providers support async natively (see `docs/cli-provider.md`) |
|
|
18
18
|
| **CLI / ACPX providers** | Optional `cli_agents` / `acpx_agents` register `cli-*` / `acpx-*` via native `createProvider` (pi ≥ 0.84): `/login cli-<agent>` or `/login acpx-<agent>`, model refresh, filter when unavailable. Missing context window / maxTokens / cost filled from models.dev (`~/.pi/pi-config/models.dev.json`, 1-day refresh). Cold-start default restore (#753): gates `startup`\|`resume` (`new` only when no model is selected), empty `enabledModels`, trusted project merge — see `docs/cli-provider.md`. Headless Cursor `--approve-mcps` when `CLI_APPROVE_MCPS` is set (`false` opts out) or the process is a sidecar (`SIDECAR_PORT`; `startSidecar()` stamps it while running and restores it on `close()`). Executable sidecar consumers should call `bindSidecarListenExit()` so bind/`stopped` fatals exit 1. Headless Gemini defaults `GEMINI_CLI_TRUST_WORKSPACE=true` and preserves an explicit parent value (including `false`). |
|
|
19
|
-
| **`/btw` command** | Quick side questions without polluting conversation history — ephemeral overlay |
|
|
20
19
|
| **`/async-status` command** | Show status of background agents — select one for live output streaming, multi-select and kill (`x`), or kill everything (`a`) |
|
|
21
20
|
| **`ask_user` tool** | Structured user input with options and free-text — used by workflows |
|
|
22
21
|
| **Python/pip enforcement** | Auto-fixes `python`/`python3` → `uv run python`; blocks `pip`/`pip3` — requires `uv` |
|
|
@@ -36,7 +35,7 @@ Single extension that provides:
|
|
|
36
35
|
| **Task tracking** | Structured task lists for multi-step workflows — live widget, progress tracking, reminder nudges (owned, based on [@tintinweb/pi-tasks](https://github.com/tintinweb/pi-tasks) MIT) |
|
|
37
36
|
| **Neovim integration** | Send changed files and review findings to nvim's quickfix list — only active when running inside nvim |
|
|
38
37
|
| **Inter-agent communication** | P2P (`/coms`) agent communication — on-demand activation via slash command |
|
|
39
|
-
| **Slash commands** | `/pr-review`, `/issue-review`, `/release`, `/review-local`, `/review-status`, `/query-db`, `/
|
|
38
|
+
| **Slash commands** | `/pr-review`, `/issue-review`, `/release`, `/review-local`, `/review-status`, `/query-db`, `/async-status`, `/status`, `/dream`, `/remember`, `/coms`, `/pi-config-settings` — with autocomplete argument hints |
|
|
40
39
|
| **GitHub autocomplete** | Type `#` in the editor to get issue/PR suggestions from the current repo — lazy-loaded, 5min cache |
|
|
41
40
|
| **Command arg completions** | Tab-complete arguments for slash commands — providers and models for `/external-ai`, branches for `/review-local`, PR numbers for `/pr-review`, and more |
|
|
42
41
|
| **Discord bot** | Control pi sessions from your phone via Discord DMs — send prompts, answer ask_user dialogs, switch sessions |
|
|
@@ -808,6 +807,7 @@ PI_PIDIFF_ENABLE=false pi
|
|
|
808
807
|
| `myk-pi-tools release create` | Create a GitHub release with changelog |
|
|
809
808
|
| `myk-pi-tools db` | Review database query commands |
|
|
810
809
|
| `myk-pi-tools reviews` | Review handling commands (fetch, poll, address, post, store, store-session) |
|
|
810
|
+
| `myk-pi-tools reviews poll` | Poll a PR for review comments until approved or actionable — `--source qodo` reads findings through the `qodo` CLI (structured, with spans and authoritative open/closed status) when the binary is installed and authenticated, and falls back to the GitHub sticky-comment scrape otherwise. Read-only: replies are always posted as GitHub comments via `reviews address` |
|
|
811
811
|
| `myk-pi-tools reviews address` | Apply replies/statuses to a fetched reviews JSON, then post and store it (see below) |
|
|
812
812
|
| `myk-pi-tools reviews store-session` | Store Qodo pr-review-session findings JSON into the reviews DB (idempotent; run at loop convergence) |
|
|
813
813
|
| `myk-pi-tools memory` | Project memory commands — persistent per-repo learning |
|
|
@@ -961,6 +961,8 @@ The bot starts automatically with pidash when `DISCORD_BOT_TOKEN` is configured.
|
|
|
961
961
|
## Development
|
|
962
962
|
|
|
963
963
|
See [DEVELOPMENT.md](DEVELOPMENT.md) for tips on testing extensions locally, running tests, and managing Python dependencies.
|
|
964
|
+
The repository-wide TypeScript gate is `npm run typecheck` (strict `tsc --noEmit` across all six projects);
|
|
965
|
+
it also runs as a pre-commit hook when TypeScript files change.
|
|
964
966
|
|
|
965
967
|
## Prerequisites
|
|
966
968
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Usage: loaded via extensions/coms/index.ts
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import type { AgentToolResult, ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
19
19
|
import { Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
20
20
|
import { Type } from "typebox";
|
|
21
21
|
import { applyExtensionDefaults } from "./themeMap.js";
|
|
@@ -187,6 +187,22 @@ interface PendingReply {
|
|
|
187
187
|
progress?: { note: string; at: string };
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/** #951: coms_send tool result details — blocked send or delivered message. */
|
|
191
|
+
type ComsSendDetails =
|
|
192
|
+
| { blocked: boolean; reason: string; target: string }
|
|
193
|
+
| { msg_id: string; target: string; target_session: string; hops: number; peer_queue_depth: number; peer_status: string };
|
|
194
|
+
|
|
195
|
+
/** #951: coms_get tool result details — error, completed reply, or pending progress. */
|
|
196
|
+
type ComsGetDetails =
|
|
197
|
+
| { status: "error"; error: string }
|
|
198
|
+
| { status: "complete"; response: unknown; error: string | null; queued_msg_ids: string[] }
|
|
199
|
+
| { status: "pending"; progress: { note: string; at: string } | null };
|
|
200
|
+
|
|
201
|
+
/** #951: coms_task_delete tool result details — blocked or deleted. */
|
|
202
|
+
type ComsTaskDeleteDetails =
|
|
203
|
+
| { blocked: boolean; reason: string }
|
|
204
|
+
| { target: string; task_id: string };
|
|
205
|
+
|
|
190
206
|
interface InboundContext {
|
|
191
207
|
msg_id: string;
|
|
192
208
|
hops: number;
|
|
@@ -2235,7 +2251,7 @@ Do not respond to this message.`;
|
|
|
2235
2251
|
entry.timer = setTimeout(() => {
|
|
2236
2252
|
if (entry.result) return;
|
|
2237
2253
|
entry.result = { error: "timeout" };
|
|
2238
|
-
try { entry.resolve(entry.result); } catch { /* ignore */ }
|
|
2254
|
+
try { entry.resolve?.(entry.result); } catch { /* ignore */ }
|
|
2239
2255
|
// Clean up timed-out entry from both maps
|
|
2240
2256
|
const pSet = pendingOutbound.get(target.name);
|
|
2241
2257
|
if (pSet) { pSet.delete(msg_id); if (pSet.size === 0) pendingOutbound.delete(target.name); }
|
|
@@ -2389,7 +2405,9 @@ Do not respond to this message.`;
|
|
|
2389
2405
|
priority: Type.Optional(Type.Boolean({ description: "Jump the peer's queued message backlog (urgent messages). Default false." })),
|
|
2390
2406
|
clearPrevious: Type.Optional(Type.Boolean({ description: "Deprecated and rejected: inspect with coms_queue_inspect, then explicitly clear using preview_id." })),
|
|
2391
2407
|
}),
|
|
2392
|
-
|
|
2408
|
+
// #951: explicit result type so the blocked and delivered details shapes form
|
|
2409
|
+
// one union instead of racing TDetails inference.
|
|
2410
|
+
async execute(_callId, params): Promise<AgentToolResult<ComsSendDetails>> {
|
|
2393
2411
|
if (!identity) {
|
|
2394
2412
|
throw new Error("coms not initialised");
|
|
2395
2413
|
}
|
|
@@ -2618,7 +2636,7 @@ Do not respond to this message.`;
|
|
|
2618
2636
|
parameters: Type.Object({
|
|
2619
2637
|
msg_id: Type.String({ description: "msg_id returned by coms_send." }),
|
|
2620
2638
|
}),
|
|
2621
|
-
async execute(_callId, params) {
|
|
2639
|
+
async execute(_callId, params): Promise<AgentToolResult<ComsGetDetails>> {
|
|
2622
2640
|
const entry = pendingReplies.get(params.msg_id);
|
|
2623
2641
|
if (!entry) {
|
|
2624
2642
|
return {
|
|
@@ -2731,7 +2749,7 @@ Do not respond to this message.`;
|
|
|
2731
2749
|
if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
|
|
2732
2750
|
const result = await sendQueueManage(target, "delete", params.msg_id, undefined, params.preview_id);
|
|
2733
2751
|
if (result.error) throw new Error(`coms_queue_delete failed: ${result.error}`);
|
|
2734
|
-
return { content: [{ type: "text" as const, text: `coms_queue_delete → ${target.name}\nmsg_id: ${params.msg_id}` }] };
|
|
2752
|
+
return { content: [{ type: "text" as const, text: `coms_queue_delete → ${target.name}\nmsg_id: ${params.msg_id}` }], details: undefined };
|
|
2735
2753
|
},
|
|
2736
2754
|
});
|
|
2737
2755
|
|
|
@@ -2749,7 +2767,7 @@ Do not respond to this message.`;
|
|
|
2749
2767
|
if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
|
|
2750
2768
|
const result = await sendQueueManage(target, "edit", params.msg_id, params.new_content);
|
|
2751
2769
|
if (result.error) throw new Error(`coms_queue_edit failed: ${result.error}`);
|
|
2752
|
-
return { content: [{ type: "text" as const, text: `coms_queue_edit → ${target.name}\nmsg_id: ${params.msg_id}` }] };
|
|
2770
|
+
return { content: [{ type: "text" as const, text: `coms_queue_edit → ${target.name}\nmsg_id: ${params.msg_id}` }], details: undefined };
|
|
2753
2771
|
},
|
|
2754
2772
|
});
|
|
2755
2773
|
|
|
@@ -2784,7 +2802,7 @@ Do not respond to this message.`;
|
|
|
2784
2802
|
if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
|
|
2785
2803
|
const result = await sendQueueManage(target, "prioritize", params.msg_id);
|
|
2786
2804
|
if (result.error) throw new Error(`coms_queue_prioritize failed: ${result.error}`);
|
|
2787
|
-
return { content: [{ type: "text" as const, text: `coms_queue_prioritize → ${target.name}\nmsg_id: ${params.msg_id}` }] };
|
|
2805
|
+
return { content: [{ type: "text" as const, text: `coms_queue_prioritize → ${target.name}\nmsg_id: ${params.msg_id}` }], details: undefined };
|
|
2788
2806
|
},
|
|
2789
2807
|
});
|
|
2790
2808
|
|
|
@@ -2896,7 +2914,7 @@ Do not respond to this message.`;
|
|
|
2896
2914
|
target: Type.String({ description: "Peer name or session_id." }),
|
|
2897
2915
|
task_id: Type.String({ description: "Task ID to delete." }),
|
|
2898
2916
|
}),
|
|
2899
|
-
async execute(_callId, params) {
|
|
2917
|
+
async execute(_callId, params): Promise<AgentToolResult<ComsTaskDeleteDetails>> {
|
|
2900
2918
|
log.debug("coms_task_delete_start", { target: params.target, taskId: params.task_id });
|
|
2901
2919
|
if (!identity) throw new Error("coms not initialised");
|
|
2902
2920
|
const target = await resolveTarget(params.target);
|
|
@@ -3721,7 +3739,10 @@ Do not respond to this message.`;
|
|
|
3721
3739
|
// Expose command handlers to pidash for browser command dispatch
|
|
3722
3740
|
try {
|
|
3723
3741
|
if ((globalThis as any).__coms_p2p_pidash_listener) {
|
|
3724
|
-
|
|
3742
|
+
// #951: pi's EventBus exposes only emit/on — no removeListener. This cleanup has
|
|
3743
|
+
// always thrown (swallowed by the catch), so the stale listener is never removed;
|
|
3744
|
+
// latent bug left as-is, typed to match reality.
|
|
3745
|
+
try { (pi.events as { removeListener?: (channel: string, handler: (data: unknown) => void) => void }).removeListener?.("pidash:request-commands", (globalThis as any).__coms_p2p_pidash_listener); } catch {}
|
|
3725
3746
|
}
|
|
3726
3747
|
const registerWithPidash = () => {
|
|
3727
3748
|
pi.events.emit("pidash:register-command", { name: "coms-queue", handler: comsQueueCmdHandler });
|
|
@@ -3790,7 +3811,9 @@ Do not respond to this message.`;
|
|
|
3790
3811
|
try {
|
|
3791
3812
|
for (const connection of connections) connection.destroy();
|
|
3792
3813
|
connections.clear();
|
|
3793
|
-
|
|
3814
|
+
// #951: net.Server has no closeAllConnections at runtime or in @types/node —
|
|
3815
|
+
// the optional call is a graceful no-op; typed accordingly.
|
|
3816
|
+
(activeServer as net.Server & { closeAllConnections?: () => void }).closeAllConnections?.(); activeServer.close(); activeServer.unref();
|
|
3794
3817
|
} catch { /* ignore */ }
|
|
3795
3818
|
}
|
|
3796
3819
|
if (pingWorker) {
|
|
@@ -166,7 +166,10 @@ export function registerComs(pi: ExtensionAPI) {
|
|
|
166
166
|
// Expose handler to pidash for browser command dispatch
|
|
167
167
|
try {
|
|
168
168
|
if ((globalThis as any).__coms_pidash_listener) {
|
|
169
|
-
|
|
169
|
+
// #951: pi's EventBus exposes only emit/on — no removeListener. This cleanup has
|
|
170
|
+
// always thrown (swallowed by the catch), so the stale listener is never removed;
|
|
171
|
+
// latent bug left as-is, typed to match reality.
|
|
172
|
+
try { (pi.events as { removeListener?: (channel: string, handler: (data: unknown) => void) => void }).removeListener?.("pidash:request-commands", (globalThis as any).__coms_pidash_listener); } catch {}
|
|
170
173
|
}
|
|
171
174
|
const registerWithPidash = () => pi.events.emit("pidash:register-command", { name: "coms", handler: comsCmdHandler });
|
|
172
175
|
(globalThis as any).__coms_pidash_listener = registerWithPidash;
|
|
@@ -152,7 +152,7 @@ function serveImage(imagePath: string): { url: string; port: string; dir: string
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
export function createImageGenTool(): ToolDefinition {
|
|
155
|
+
export function createImageGenTool(): ToolDefinition<typeof imageGenSchema> {
|
|
156
156
|
return {
|
|
157
157
|
name: "generate_image",
|
|
158
158
|
label: "GenerateImage",
|
|
@@ -173,6 +173,7 @@ export function createImageGenTool(): ToolDefinition {
|
|
|
173
173
|
type: "text" as const,
|
|
174
174
|
text: "Error: image model is not set. Add image_model to pi-config-settings.json (or PI_IMAGE_MODEL env) and reload pi.",
|
|
175
175
|
}],
|
|
176
|
+
details: undefined,
|
|
176
177
|
};
|
|
177
178
|
}
|
|
178
179
|
if (!isImageGenModelId(model)) {
|
|
@@ -182,6 +183,7 @@ export function createImageGenTool(): ToolDefinition {
|
|
|
182
183
|
type: "text" as const,
|
|
183
184
|
text: `Error: image_model '${model}' is not an image-generation model. Choose a Gemini image model (id contains image or imagen).`,
|
|
184
185
|
}],
|
|
186
|
+
details: undefined,
|
|
185
187
|
};
|
|
186
188
|
}
|
|
187
189
|
|
|
@@ -192,6 +194,7 @@ export function createImageGenTool(): ToolDefinition {
|
|
|
192
194
|
type: "text" as const,
|
|
193
195
|
text: "Error: No API key found. Set GEMINI_API_KEY or GOOGLE_API_KEY environment variable and restart pi.",
|
|
194
196
|
}],
|
|
197
|
+
details: undefined,
|
|
195
198
|
};
|
|
196
199
|
}
|
|
197
200
|
|
|
@@ -231,6 +234,7 @@ export function createImageGenTool(): ToolDefinition {
|
|
|
231
234
|
type: "text" as const,
|
|
232
235
|
text: `Error: Gemini API request failed: ${err?.message ?? String(err)}`,
|
|
233
236
|
}],
|
|
237
|
+
details: undefined,
|
|
234
238
|
};
|
|
235
239
|
}
|
|
236
240
|
|
|
@@ -248,6 +252,7 @@ export function createImageGenTool(): ToolDefinition {
|
|
|
248
252
|
type: "text" as const,
|
|
249
253
|
text: `Error: Gemini image request failed (${response.status}): ${message}`,
|
|
250
254
|
}],
|
|
255
|
+
details: undefined,
|
|
251
256
|
};
|
|
252
257
|
}
|
|
253
258
|
|
|
@@ -260,6 +265,7 @@ export function createImageGenTool(): ToolDefinition {
|
|
|
260
265
|
type: "text" as const,
|
|
261
266
|
text: `Error: Failed to parse Gemini response: ${rawText.slice(0, 200)}`,
|
|
262
267
|
}],
|
|
268
|
+
details: undefined,
|
|
263
269
|
};
|
|
264
270
|
}
|
|
265
271
|
|
|
@@ -269,6 +275,7 @@ export function createImageGenTool(): ToolDefinition {
|
|
|
269
275
|
type: "text" as const,
|
|
270
276
|
text: `Error: Image generation blocked by safety filter: ${data.promptFeedback.blockReason}`,
|
|
271
277
|
}],
|
|
278
|
+
details: undefined,
|
|
272
279
|
};
|
|
273
280
|
}
|
|
274
281
|
|
|
@@ -296,6 +303,7 @@ export function createImageGenTool(): ToolDefinition {
|
|
|
296
303
|
type: "text" as const,
|
|
297
304
|
text: `No image data returned from Gemini.${extra}`,
|
|
298
305
|
}],
|
|
306
|
+
details: undefined,
|
|
299
307
|
};
|
|
300
308
|
}
|
|
301
309
|
|
|
@@ -344,6 +352,7 @@ export function createImageGenTool(): ToolDefinition {
|
|
|
344
352
|
type: "text" as const,
|
|
345
353
|
text: lines.join("\n"),
|
|
346
354
|
}],
|
|
355
|
+
details: undefined,
|
|
347
356
|
};
|
|
348
357
|
},
|
|
349
358
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthResult, Model, Provider, RefreshModelsContext } from "@earendil-works/pi-ai";
|
|
1
|
+
import type { Api, AuthResult, Model, Provider, RefreshModelsContext } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { createLogger } from "../shared/logger.js";
|
|
4
4
|
import {
|
|
@@ -47,7 +47,7 @@ function resolvedConnection(source: Provider, auth?: AuthResult, staticHeaders?:
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
function combineModels(sourceProviderId: string, staticModels: readonly Model[], records: readonly OpenAiCompatibleModelRecord[], baseUrl: string): Model[] {
|
|
50
|
+
function combineModels(sourceProviderId: string, staticModels: readonly Model<Api>[], records: readonly OpenAiCompatibleModelRecord[], baseUrl: string): Model<Api>[] {
|
|
51
51
|
const existingIds = new Set(staticModels.map((model) => model.id));
|
|
52
52
|
const discovered = materializeOpenAiCompatibleModels(records, baseUrl, sourceProviderId)
|
|
53
53
|
.filter((model) => !existingIds.has(model.id));
|
|
@@ -59,14 +59,14 @@ async function refreshProviderModels(
|
|
|
59
59
|
ctx: any,
|
|
60
60
|
sourceProviderId: string,
|
|
61
61
|
source: Provider,
|
|
62
|
-
staticModels: readonly Model[],
|
|
62
|
+
staticModels: readonly Model<Api>[],
|
|
63
63
|
refresh: RefreshModelsContext,
|
|
64
64
|
appendSummary: () => boolean,
|
|
65
65
|
discoverModelCapabilities: boolean,
|
|
66
|
-
rawStaticHeaders
|
|
67
|
-
snapshot: { discovered: Model[]; fingerprint?: string; generation: number },
|
|
66
|
+
rawStaticHeaders: Record<string, string> | undefined,
|
|
67
|
+
snapshot: { discovered: Model<Api>[]; fingerprint?: string; generation: number },
|
|
68
68
|
lifecycle: { signal: AbortSignal; isCurrent: () => boolean },
|
|
69
|
-
): Promise<Model[]> {
|
|
69
|
+
): Promise<Model<Api>[]> {
|
|
70
70
|
const generation = ++snapshot.generation;
|
|
71
71
|
const active = () => {
|
|
72
72
|
const current = lifecycle.isCurrent();
|
|
@@ -206,9 +206,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
206
206
|
log.warn(`${id}: configured provider was not resolved`);
|
|
207
207
|
continue;
|
|
208
208
|
}
|
|
209
|
-
const staticModels = (ctx.modelRegistry.getAll() as Model[])
|
|
209
|
+
const staticModels = (ctx.modelRegistry.getAll() as Model<Api>[])
|
|
210
210
|
.filter((model) => model.provider === id);
|
|
211
|
-
const snapshot = { discovered: [] as Model[], fingerprint: undefined as string | undefined, generation: 0 };
|
|
211
|
+
const snapshot = { discovered: [] as Model<Api>[], fingerprint: undefined as string | undefined, generation: 0 };
|
|
212
212
|
// Registration starts an unawaited offline refresh. Its first callback signals
|
|
213
213
|
// that it has acquired its generation, so the network pass can safely supersede it.
|
|
214
214
|
let registrationStarted!: () => void;
|
|
@@ -48,6 +48,7 @@ export function registerAskUser(
|
|
|
48
48
|
content: [
|
|
49
49
|
{ type: "text", text: "No UI available for user interaction" },
|
|
50
50
|
],
|
|
51
|
+
details: undefined,
|
|
51
52
|
isError: true,
|
|
52
53
|
};
|
|
53
54
|
}
|
|
@@ -265,17 +266,17 @@ export function registerAskUser(
|
|
|
265
266
|
unsubscribe();
|
|
266
267
|
|
|
267
268
|
if (result === null) {
|
|
268
|
-
return { content: [{ type: "text", text: "User cancelled" }], terminate: true };
|
|
269
|
+
return { content: [{ type: "text", text: "User cancelled" }], details: undefined, terminate: true };
|
|
269
270
|
}
|
|
270
|
-
return { content: [{ type: "text", text: result }] };
|
|
271
|
+
return { content: [{ type: "text", text: result }], details: undefined };
|
|
271
272
|
},
|
|
272
273
|
|
|
273
274
|
renderCall(args, theme) {
|
|
274
275
|
let t =
|
|
275
276
|
theme.fg("toolTitle", theme.bold("ask_user ")) +
|
|
276
277
|
theme.fg("accent", args.question || "...");
|
|
277
|
-
if (args.options?.length > 0) {
|
|
278
|
-
t += `\n ${theme.fg("dim", args.options
|
|
278
|
+
if ((args.options?.length ?? 0) > 0) {
|
|
279
|
+
t += `\n ${theme.fg("dim", args.options!.join(" • "))}`;
|
|
279
280
|
}
|
|
280
281
|
return new Text(t, 0, 0);
|
|
281
282
|
},
|
|
@@ -57,7 +57,7 @@ function jobTitle(job: AsyncStatusJobView): string {
|
|
|
57
57
|
*/
|
|
58
58
|
export function formatKillOriginSuffix(
|
|
59
59
|
killOrigin: string | undefined,
|
|
60
|
-
theme:
|
|
60
|
+
theme: Pick<Theme, "fg">,
|
|
61
61
|
): string {
|
|
62
62
|
if (!killOrigin) {
|
|
63
63
|
log.debug("kill_origin_header: no origin recorded");
|
|
@@ -193,7 +193,7 @@ export function registerCron(pi: ExtensionAPI, spawnAsyncAgent: any): { getCronT
|
|
|
193
193
|
const task = tasks.get(id); if (scope === "session") { if (!task) return false; stop(id); tasks.delete(id); if (sessionStore) writeSessionTasks(sessionStore, [...tasks.values()].filter(t => t.scope === "session")); } else { const file = projectStore(ctx.cwd); let found = false; mutateDurableCronStore(file, old => { found = old.some(t => t.id === id); return old.filter(t => t.id !== id); }); if (!found) return false; if (task) { stop(id); tasks.delete(id); } }
|
|
194
194
|
updateStatus(); return true;
|
|
195
195
|
}
|
|
196
|
-
pi.registerTool({ name: "cron_manage", description: "Manage scheduled tasks. Set persist=true to keep a task across Pi sessions in this project. Set model to \"provider/model-id\" (or a bare \"model-id\") to pin that cron's model.", parameters: Type.Object({ action: Type.Union([Type.Literal("add"), Type.Literal("list"), Type.Literal("remove")]), persist: Type.Optional(Type.Boolean()), description: Type.Optional(Type.String()), task: Type.Optional(Type.String()), model: Type.Optional(Type.String()), interval_seconds: Type.Optional(Type.Number()), at_hour: Type.Optional(Type.Number()), at_minute: Type.Optional(Type.Number()), id: Type.Optional(Type.String()) }), async execute(_id, params: any) {
|
|
196
|
+
pi.registerTool({ name: "cron_manage", description: "Manage scheduled tasks. Set persist=true to keep a task across Pi sessions in this project. Set model to \"provider/model-id\" (or a bare \"model-id\") to pin that cron's model.", parameters: Type.Object({ action: Type.Union([Type.Literal("add"), Type.Literal("list"), Type.Literal("remove")]), persist: Type.Optional(Type.Boolean()), description: Type.Optional(Type.String()), task: Type.Optional(Type.String()), model: Type.Optional(Type.String()), interval_seconds: Type.Optional(Type.Number()), at_hour: Type.Optional(Type.Number()), at_minute: Type.Optional(Type.Number()), id: Type.Optional(Type.String()) }), async execute(_id: string, params: any) {
|
|
197
197
|
const scope: CronScope = params.persist ? "project" : "session";
|
|
198
198
|
try { if (params.action === "add" && (ctx?.mode === "print" || ctx?.mode === "json")) return { content: [{ type: "text", text: "Error: Cron scheduling is unavailable in one-shot mode." }] }; if (params.action === "add") { const task = add(scope, params); return { content: [{ type: "text", text: `Cron ${qualifyCronId(task)} created: ${formatCronSchedule(task)} → ${task.description}${task.model ? ` [${task.model}]` : ""}` }] }; } if (params.action === "remove") return { content: [{ type: "text", text: remove(params.id || "") ? "Cron removed." : "Task not found." }] }; const selected = [...tasks.values()].filter(t => !params.persist || t.scope === "project"); return { content: [{ type: "text", text: selected.length ? selected.map(t => `${qualifyCronId(t)} | ${formatCronSchedule(t)} | ${t.description} | ${t.model || "inherited model"} | ${t.leader === false ? "waiting for leader" : "active"}`).join("\n") : "No scheduled tasks." }] }; } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}` }] }; }
|
|
199
199
|
}, } as any);
|
|
@@ -147,11 +147,15 @@ export function acquireBuildLock(root: string, afterClaim?: () => void): (() =>
|
|
|
147
147
|
function trusted(ctx: any): boolean { return typeof ctx?.isProjectTrusted !== "function" || ctx.isProjectTrusted() === true; }
|
|
148
148
|
function relevant(ask: Ask): Hit[] { const hits = ask.hits?.filter(hit => text(hit.pointer) && text(hit.title)) ?? []; if (!hits.length) return []; if ((typeof ask.coverage === "number" || typeof ask.coverageStrong === "number") && (ask.coverageStrong ?? 0) < .1 && (ask.coverage ?? 0) < .5) return []; return hits.slice(0, 3); }
|
|
149
149
|
function pointerText(hits: Hit[]): string { return hits.map((hit, i) => { const snippet = text(hit.snippet, 140); return ` ${i + 1}. ${text(hit.title)!}: ${text(hit.pointer)!}${snippet ? `\n ${snippet.replace(/\s+/g, " ")}` : ""}`; }).join("\n"); }
|
|
150
|
+
type GraftNode = { id: string; name: string; path: string };
|
|
151
|
+
type GraftEdge = { source: string; target: string; relation: string };
|
|
152
|
+
type GraftWiring = { nodes?: GraftNode[]; edges?: GraftEdge[] };
|
|
150
153
|
function blastHint(root: string, path: unknown): string | null {
|
|
151
|
-
const file = typeof path === "string" ? path : null; const graph = file && wiring(root); if (!file || !graph) return null;
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const
|
|
154
|
+
const file = typeof path === "string" ? path : null; const graph = file && (wiring(root) as GraftWiring | null); if (!file || !graph) return null;
|
|
155
|
+
const graphNodes = graph.nodes ?? [];
|
|
156
|
+
const names = new Map<string, GraftNode>(graphNodes.map((node) => [node.id, node] as [string, GraftNode]));
|
|
157
|
+
const ids = new Set(graphNodes.filter((node) => node.path === file || file.endsWith(`/${node.path}`)).map((node) => node.id));
|
|
158
|
+
const dependents = (graph.edges ?? []).filter((edge) => ids.has(edge.target) && !ids.has(edge.source)).slice(0, 8).map((edge) => { const node = names.get(edge.source); return ` • ${edge.relation} ← ${node ? `${node.name} (${basename(node.path)})` : edge.source}`; });
|
|
155
159
|
return dependents.length ? `[graft] blast radius for ${basename(file)}, who depends on it:\n${dependents.join("\n")}` : null;
|
|
156
160
|
}
|
|
157
161
|
export function isEligibleGraftStartup(event: any, ctx: any): boolean { return event?.reason === "startup" && ctx?.mode !== "print" && ctx?.mode !== "json" && process.env.PI_SUBAGENT_CHILD !== "1" && !isPiOneshotInvocation(); }
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - Subagent tool (based on pi's subagent example, with package agent discovery)
|
|
6
6
|
* - Enforcement handlers (python/pip, git protection, dangerous commands)
|
|
7
7
|
* - Rule injection & memory loading (before_agent_start)
|
|
8
|
-
* - Slash commands (/
|
|
8
|
+
* - Slash commands (/async-status, /dream-auto)
|
|
9
9
|
* - Notifications and status line
|
|
10
10
|
* - Session validation (required tools check)
|
|
11
11
|
* - Async agent infrastructure
|
|
@@ -17,7 +17,6 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
17
17
|
import { Type } from "typebox";
|
|
18
18
|
import { registerAskUser } from "./ask-user.js";
|
|
19
19
|
import { registerAsyncAgents } from "./async-agents.js";
|
|
20
|
-
import { registerBtw } from "./btw.js";
|
|
21
20
|
import { registerDreaming } from "./dreaming.js";
|
|
22
21
|
import { registerEnforcement } from "./enforcement.js";
|
|
23
22
|
import { registerProjectSettings } from "./project-settings.js";
|
|
@@ -104,7 +103,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
104
103
|
registerRules(pi, getAsyncJobs);
|
|
105
104
|
|
|
106
105
|
registerStatusLine(pi, IN_CONTAINER, terminalNotify);
|
|
107
|
-
registerBtw(pi);
|
|
108
106
|
registerDreaming(pi, spawnAsyncAgent);
|
|
109
107
|
const { getCronTasks } = registerCron(pi, spawnAsyncAgent);
|
|
110
108
|
registerSessionValidation(pi);
|
|
@@ -137,12 +135,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
137
135
|
if (!registry) {
|
|
138
136
|
return {
|
|
139
137
|
content: [{ type: "text" as const, text: "Model registry not available." }],
|
|
138
|
+
details: undefined,
|
|
140
139
|
};
|
|
141
140
|
}
|
|
142
141
|
models = registry.getAvailable?.() || registry.getAll?.() || [];
|
|
143
142
|
} catch (e: any) {
|
|
144
143
|
return {
|
|
145
144
|
content: [{ type: "text" as const, text: `Failed to list models: ${e?.message || e}` }],
|
|
145
|
+
details: undefined,
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
// Filter out acpx-* providers — they cannot work in subagent children
|
|
@@ -154,6 +154,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
154
154
|
if (filtered.length === 0) {
|
|
155
155
|
return {
|
|
156
156
|
content: [{ type: "text" as const, text: params.provider ? `No models found for provider "${params.provider}".` : "No models available." }],
|
|
157
|
+
details: undefined,
|
|
157
158
|
};
|
|
158
159
|
}
|
|
159
160
|
// Group by provider for readability
|
|
@@ -172,6 +173,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
172
173
|
}
|
|
173
174
|
return {
|
|
174
175
|
content: [{ type: "text" as const, text: lines.join("\n") }],
|
|
176
|
+
details: undefined,
|
|
175
177
|
};
|
|
176
178
|
},
|
|
177
179
|
});
|
|
@@ -94,9 +94,10 @@ async function getPipeline(): Promise<any> {
|
|
|
94
94
|
pipelineLoading = (async () => {
|
|
95
95
|
try {
|
|
96
96
|
const { pipeline } = await import("@huggingface/transformers");
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
// q8 quantized weights: ~4x smaller and faster than fp32, negligible
|
|
98
|
+
// accuracy loss for embedding similarity (dtype replaced `quantized` in
|
|
99
|
+
// @huggingface/transformers v4 — issue #951 follow-up).
|
|
100
|
+
const extractor = await pipeline("feature-extraction", "Xenova/bge-small-en-v1.5", { dtype: "q8" });
|
|
100
101
|
pipelineInstance = extractor;
|
|
101
102
|
return extractor;
|
|
102
103
|
} catch {
|
|
@@ -302,7 +302,7 @@ export function loadScores(cwd: string): ScoresFile {
|
|
|
302
302
|
// the cross-process lock — migrating the snapshot loaded above could
|
|
303
303
|
// append a stale state and erase a concurrent writer's update.
|
|
304
304
|
if (data.hashAlgo !== HASH_ALGO) {
|
|
305
|
-
getScoresStore(cwd).mutate
|
|
305
|
+
getScoresStore(cwd).mutate(
|
|
306
306
|
(latest) => {
|
|
307
307
|
if (latest.hashAlgo === HASH_ALGO) return; // a concurrent process already migrated
|
|
308
308
|
migrateScoreKeysInPlace(cwd, latest);
|
|
@@ -408,7 +408,7 @@ export function mutateScores(
|
|
|
408
408
|
): ScoresFile | null {
|
|
409
409
|
// Ensure key migration has run before mutating (idempotent, cached read)
|
|
410
410
|
loadScores(cwd);
|
|
411
|
-
return getScoresStore(cwd).mutate
|
|
411
|
+
return getScoresStore(cwd).mutate(fn, {
|
|
412
412
|
entries: {},
|
|
413
413
|
lastRebuild: new Date().toISOString(),
|
|
414
414
|
hashAlgo: HASH_ALGO,
|