@blackbelt-technology/pi-agent-dashboard 0.4.6 → 0.5.1
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/AGENTS.md +339 -190
- package/README.md +50 -7
- package/docs/architecture.md +238 -23
- package/package.json +23 -13
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +1 -1
- package/packages/extension/src/__tests__/build-provider-catalogue.test.ts +176 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +68 -0
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +1 -1
- package/packages/extension/src/__tests__/markdown-image-inliner.test.ts +355 -0
- package/packages/extension/src/__tests__/no-tui-multiselect-arm-regression.test.ts +1 -1
- package/packages/extension/src/__tests__/openspec-activity-detector.test.ts +68 -0
- package/packages/extension/src/__tests__/prompt-expander.test.ts +45 -0
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +74 -0
- package/packages/extension/src/__tests__/retry-tracker.test.ts +147 -0
- package/packages/extension/src/__tests__/server-launcher.test.ts +24 -1
- package/packages/extension/src/__tests__/session-sync.test.ts +72 -0
- package/packages/extension/src/__tests__/usage-limit-orderer.test.ts +105 -0
- package/packages/extension/src/ask-user-tool.ts +1 -1
- package/packages/extension/src/bridge-context.ts +1 -1
- package/packages/extension/src/bridge.ts +169 -4
- package/packages/extension/src/command-handler.ts +65 -2
- package/packages/extension/src/flow-event-wiring.ts +1 -1
- package/packages/extension/src/markdown-image-inliner.ts +268 -0
- package/packages/extension/src/multiselect-list.ts +1 -1
- package/packages/extension/src/pi-env.d.ts +16 -9
- package/packages/extension/src/prompt-expander.ts +50 -2
- package/packages/extension/src/provider-register.ts +132 -8
- package/packages/extension/src/retry-tracker.ts +123 -0
- package/packages/extension/src/server-launcher.ts +18 -1
- package/packages/extension/src/session-sync.ts +15 -1
- package/packages/extension/src/usage-limit-orderer.ts +76 -0
- package/packages/server/package.json +6 -6
- package/packages/server/src/__tests__/auto-attach-slug-defense.test.ts +104 -0
- package/packages/server/src/__tests__/bootstrap-install-from-list.test.ts +263 -0
- package/packages/server/src/__tests__/browser-gateway-snapshot-on-connect.test.ts +143 -0
- package/packages/server/src/__tests__/build-auth-status.test.ts +190 -0
- package/packages/server/src/__tests__/changelog-fs.test.ts +171 -0
- package/packages/server/src/__tests__/changelog-parser.test.ts +220 -0
- package/packages/server/src/__tests__/changelog-remote.test.ts +193 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +22 -4
- package/packages/server/src/__tests__/cold-boot-openspec-broadcast.test.ts +161 -0
- package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service-toctou.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service.test.ts +1 -1
- package/packages/server/src/__tests__/doctor-route.test.ts +132 -0
- package/packages/server/src/__tests__/event-wiring-providers-list.test.ts +154 -0
- package/packages/server/src/__tests__/fixtures/pi-changelog-slice.md +180 -0
- package/packages/server/src/__tests__/fork-empty-session-preflight.test.ts +268 -0
- package/packages/server/src/__tests__/has-openspec-dir.test.ts +64 -0
- package/packages/server/src/__tests__/headless-pid-registry.test.ts +83 -0
- package/packages/server/src/__tests__/health-shape.test.ts +43 -0
- package/packages/server/src/__tests__/idle-timer-respects-terminals.test.ts +115 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +1 -1
- package/packages/server/src/__tests__/openspec-connect-snapshot.test.ts +92 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +4 -4
- package/packages/server/src/__tests__/package-routes.test.ts +1 -1
- package/packages/server/src/__tests__/pending-fork-registry.test.ts +48 -24
- package/packages/server/src/__tests__/pi-changelog-integration.test.ts +165 -0
- package/packages/server/src/__tests__/pi-changelog-routes.test.ts +409 -0
- package/packages/server/src/__tests__/pi-core-checker.test.ts +155 -13
- package/packages/server/src/__tests__/pi-core-updater-managed-path.test.ts +236 -0
- package/packages/server/src/__tests__/pi-core-updater.test.ts +1 -1
- package/packages/server/src/__tests__/pi-dev-version-check.test.ts +184 -0
- package/packages/server/src/__tests__/pi-version-skew.test.ts +4 -4
- package/packages/server/src/__tests__/process-manager-codes.test.ts +80 -0
- package/packages/server/src/__tests__/process-manager-managed-path.test.ts +73 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +12 -4
- package/packages/server/src/__tests__/provider-auth-storage.test.ts +42 -11
- package/packages/server/src/__tests__/provider-catalogue-cache.test.ts +44 -0
- package/packages/server/src/__tests__/recommended-routes.test.ts +1 -1
- package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +17 -2
- package/packages/server/src/__tests__/session-action-handler-spawn.test.ts +150 -0
- package/packages/server/src/__tests__/session-discovery-skill-firstmessage.test.ts +95 -0
- package/packages/server/src/__tests__/spawn-correlation-token-integration.test.ts +91 -0
- package/packages/server/src/__tests__/spawn-failure-log.test.ts +118 -0
- package/packages/server/src/__tests__/spawn-preflight.test.ts +91 -0
- package/packages/server/src/__tests__/spawn-register-watchdog.test.ts +250 -0
- package/packages/server/src/__tests__/spawn-token.test.ts +57 -0
- package/packages/server/src/__tests__/subscription-handler.test.ts +98 -6
- package/packages/server/src/__tests__/system-routes-reextract.test.ts +91 -0
- package/packages/server/src/__tests__/system-routes-spawn-failures.test.ts +84 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +45 -0
- package/packages/server/src/bootstrap-install-from-list.ts +232 -0
- package/packages/server/src/bootstrap-state.ts +18 -0
- package/packages/server/src/browser-gateway.ts +70 -24
- package/packages/server/src/browser-handlers/directory-handler.ts +4 -0
- package/packages/server/src/browser-handlers/handler-context.ts +9 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +159 -18
- package/packages/server/src/browser-handlers/subscription-handler.ts +50 -3
- package/packages/server/src/changelog-fs.ts +167 -0
- package/packages/server/src/changelog-parser.ts +321 -0
- package/packages/server/src/changelog-remote.ts +134 -0
- package/packages/server/src/cli.ts +23 -2
- package/packages/server/src/directory-service.ts +31 -0
- package/packages/server/src/event-wiring.ts +105 -5
- package/packages/server/src/headless-pid-registry.ts +54 -5
- package/packages/server/src/home-lock.d.ts +124 -0
- package/packages/server/src/home-lock.js +330 -0
- package/packages/server/src/home-lock.js.map +1 -0
- package/packages/server/src/idle-timer.ts +15 -1
- package/packages/server/src/pending-client-correlations.ts +73 -0
- package/packages/server/src/pending-fork-registry.ts +24 -12
- package/packages/server/src/pi-core-checker.ts +77 -17
- package/packages/server/src/pi-core-updater.ts +81 -15
- package/packages/server/src/pi-dev-version-check.ts +145 -0
- package/packages/server/src/pi-gateway.ts +10 -0
- package/packages/server/src/pi-version-skew.ts +12 -4
- package/packages/server/src/process-manager.ts +115 -21
- package/packages/server/src/provider-auth-handlers.ts +9 -0
- package/packages/server/src/provider-auth-storage.ts +83 -51
- package/packages/server/src/provider-catalogue-cache.ts +47 -0
- package/packages/server/src/routes/doctor-routes.ts +140 -0
- package/packages/server/src/routes/pi-changelog-routes.ts +194 -0
- package/packages/server/src/routes/pi-core-routes.ts +1 -1
- package/packages/server/src/routes/provider-auth-routes.ts +14 -1
- package/packages/server/src/routes/provider-routes.ts +4 -4
- package/packages/server/src/routes/system-routes.ts +38 -1
- package/packages/server/src/server.ts +85 -66
- package/packages/server/src/session-api.ts +54 -3
- package/packages/server/src/session-bootstrap.ts +27 -12
- package/packages/server/src/session-discovery.ts +11 -4
- package/packages/server/src/session-file-reader.ts +1 -1
- package/packages/server/src/session-scanner.ts +4 -2
- package/packages/server/src/spawn-failure-log.ts +130 -0
- package/packages/server/src/spawn-preflight.ts +82 -0
- package/packages/server/src/spawn-register-watchdog.ts +291 -0
- package/packages/server/src/spawn-token.ts +20 -0
- package/packages/server/src/terminal-manager.ts +12 -1
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +25 -17
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +5 -4
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +6 -5
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +1 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +5 -4
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +2 -1
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +6 -3
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +1 -1
- package/packages/shared/src/__tests__/bootstrap-install-resolve-npm.test.ts +72 -0
- package/packages/shared/src/__tests__/browser-protocol-types.test.ts +47 -1
- package/packages/shared/src/__tests__/changelog-types.test.ts +78 -0
- package/packages/shared/src/__tests__/config.test.ts +48 -0
- package/packages/shared/src/__tests__/dashboard-starter.test.ts +40 -0
- package/packages/shared/src/__tests__/detached-spawn.test.ts +24 -0
- package/packages/shared/src/__tests__/doctor-core.test.ts +134 -0
- package/packages/shared/src/__tests__/doctor-fault-tolerance.test.ts +218 -0
- package/packages/shared/src/__tests__/doctor-format.test.ts +121 -0
- package/packages/shared/src/__tests__/install-managed-node-bootstrap-order.test.ts +68 -0
- package/packages/shared/src/__tests__/install-managed-node.test.ts +192 -0
- package/packages/shared/src/__tests__/installable-list.test.ts +130 -0
- package/packages/shared/src/__tests__/managed-node-path.test.ts +122 -0
- package/packages/shared/src/__tests__/managed-runtime-strategy.test.ts +74 -0
- package/packages/shared/src/__tests__/no-installable-list-in-bridge.test.ts +52 -0
- package/packages/shared/src/__tests__/no-raw-openspec-status-in-skills.test.ts +6 -1
- package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +56 -20
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +140 -9
- package/packages/shared/src/__tests__/skill-block-parser.test.ts +153 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +1 -1
- package/packages/shared/src/bootstrap-install.ts +197 -3
- package/packages/shared/src/browser-protocol.ts +155 -1
- package/packages/shared/src/changelog-types.ts +111 -0
- package/packages/shared/src/config.ts +15 -0
- package/packages/shared/src/dashboard-starter.ts +33 -0
- package/packages/shared/src/doctor-core.ts +821 -0
- package/packages/shared/src/index.ts +9 -0
- package/packages/shared/src/installable-list.ts +152 -0
- package/packages/shared/src/launch-source-flag.ts +14 -0
- package/packages/shared/src/launch-source-types.ts +18 -0
- package/packages/shared/src/openspec-activity-detector.ts +25 -7
- package/packages/shared/src/platform/detached-spawn.ts +13 -2
- package/packages/shared/src/platform/managed-node-path.ts +77 -0
- package/packages/shared/src/platform/node-spawn.ts +29 -21
- package/packages/shared/src/protocol.ts +54 -2
- package/packages/shared/src/resolve-jiti.ts +62 -9
- package/packages/shared/src/rest-api.ts +4 -0
- package/packages/shared/src/skill-block-parser.ts +115 -0
- package/packages/shared/src/tool-registry/__tests__/managed-runtime-strategy.test.ts +166 -0
- package/packages/shared/src/tool-registry/definitions.ts +33 -8
- package/packages/shared/src/tool-registry/strategies.ts +42 -0
- package/packages/shared/src/types.ts +64 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill block parser & builder.
|
|
3
|
+
*
|
|
4
|
+
* Pi's `_expandSkillCommand` (in `@earendil-works/pi-coding-agent`) wraps skill
|
|
5
|
+
* expansions in a `<skill name="..." location="...">…</skill>\n\nargs` envelope.
|
|
6
|
+
* The dashboard's bridge expander (`packages/extension/src/prompt-expander.ts`)
|
|
7
|
+
* aligns to the same byte format. This module is the single source of truth for
|
|
8
|
+
* both producing and recovering that envelope.
|
|
9
|
+
*
|
|
10
|
+
* See change: render-skill-invocations-collapsibly.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface SkillBlock {
|
|
14
|
+
/** Bare skill name (no `skill:` prefix), e.g. `"openspec-explore"`. */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Absolute path to `SKILL.md`. */
|
|
17
|
+
location: string;
|
|
18
|
+
/**
|
|
19
|
+
* Skill body with the `References are relative to <baseDir>.\n\n` preamble
|
|
20
|
+
* stripped — what users see in the card. The preamble is bridge-internal
|
|
21
|
+
* orientation for the LLM and is not interesting to users.
|
|
22
|
+
*
|
|
23
|
+
* Pi's own `parseSkillBlock` returns the unstripped form (calls it `content`).
|
|
24
|
+
* We strip here so the renderer doesn't have to. If the preamble shape ever
|
|
25
|
+
* changes upstream and stripping fails, `body` falls back to the captured
|
|
26
|
+
* content verbatim.
|
|
27
|
+
*/
|
|
28
|
+
body: string;
|
|
29
|
+
/** User text after the skill name. `undefined` when no args were typed. */
|
|
30
|
+
args: string | undefined;
|
|
31
|
+
/** Slash-command form: `"/skill:" + name + (args ? " " + args : "")`. */
|
|
32
|
+
condensed: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Anchored, non-greedy match of a wrapped skill block.
|
|
37
|
+
*
|
|
38
|
+
* Why anchored: prevents a literal `<skill>` substring elsewhere in user text
|
|
39
|
+
* from matching. Why non-greedy + optional trailing args at end-of-string:
|
|
40
|
+
* forces the regex engine to extend the body to the last valid
|
|
41
|
+
* `\n</skill>(\n\nargs)?$` boundary, so SKILL.md bodies that document the
|
|
42
|
+
* `<skill>` tag (e.g. include the literal text in code samples) do not
|
|
43
|
+
* terminate the match prematurely.
|
|
44
|
+
*/
|
|
45
|
+
const SKILL_BLOCK_RE =
|
|
46
|
+
/^<skill name="([^"]+)" location="([^"]+)">\n([\s\S]*?)\n<\/skill>(?:\n\n([\s\S]+))?$/;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse a skill block from message text. Returns `null` when the input is not
|
|
50
|
+
* a well-formed skill envelope.
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Strip the `References are relative to <baseDir>.\n\n` preamble from a captured
|
|
54
|
+
* `<skill>` content block. Returns the stripped body, or the input unchanged if
|
|
55
|
+
* the preamble shape doesn't match.
|
|
56
|
+
*/
|
|
57
|
+
function stripReferencesPreamble(content: string): string {
|
|
58
|
+
const m = content.match(/^References are relative to [^\n]+\.\n\n([\s\S]*)$/);
|
|
59
|
+
return m ? m[1] : content;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function parseSkillBlock(text: string): SkillBlock | null {
|
|
63
|
+
const m = text.match(SKILL_BLOCK_RE);
|
|
64
|
+
if (!m) return null;
|
|
65
|
+
const name = m[1];
|
|
66
|
+
const location = m[2];
|
|
67
|
+
const body = stripReferencesPreamble(m[3]);
|
|
68
|
+
const args = m[4];
|
|
69
|
+
const condensed = `/skill:${name}${args ? " " + args : ""}`;
|
|
70
|
+
return { name, location, body, args, condensed };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface BuildSkillBlockArgs {
|
|
74
|
+
/** Bare skill name (no `skill:` prefix). */
|
|
75
|
+
name: string;
|
|
76
|
+
/** Absolute path to `SKILL.md`. */
|
|
77
|
+
filePath: string;
|
|
78
|
+
/** Skill base directory — `dirname(filePath)`. */
|
|
79
|
+
baseDir: string;
|
|
80
|
+
/** Skill body, frontmatter already stripped. Should be `.trim()`-ed. */
|
|
81
|
+
body: string;
|
|
82
|
+
/** Optional user-typed args appended after the closing tag. */
|
|
83
|
+
userArgs?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build a skill block in the exact byte format pi's `_expandSkillCommand`
|
|
88
|
+
* produces. The output is byte-identical to pi's output for the same inputs;
|
|
89
|
+
* `parseSkillBlock(buildSkillBlock(x))` round-trips for `name`, `body`, `args`.
|
|
90
|
+
*/
|
|
91
|
+
export function buildSkillBlock(input: BuildSkillBlockArgs): string {
|
|
92
|
+
const wrapper =
|
|
93
|
+
`<skill name="${input.name}" location="${input.filePath}">\n` +
|
|
94
|
+
`References are relative to ${input.baseDir}.\n\n` +
|
|
95
|
+
`${input.body}\n` +
|
|
96
|
+
`</skill>`;
|
|
97
|
+
return input.userArgs ? `${wrapper}\n\n${input.userArgs}` : wrapper;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Condense a user-message content string for `firstMessage` / display purposes.
|
|
102
|
+
*
|
|
103
|
+
* If `text` parses as a `<skill>` envelope, returns the slash-command form
|
|
104
|
+
* (`/skill:name args`) truncated to `maxLen` chars. Otherwise returns
|
|
105
|
+
* `text.slice(0, maxLen)`. Used by session-scanner / session-discovery so the
|
|
106
|
+
* 200-char `firstMessage` shows the recognisable slash form instead of the
|
|
107
|
+
* front of an opaque wrapper.
|
|
108
|
+
*
|
|
109
|
+
* See change: render-skill-invocations-collapsibly.
|
|
110
|
+
*/
|
|
111
|
+
export function condenseForFirstMessage(text: string, maxLen: number): string {
|
|
112
|
+
const block = parseSkillBlock(text);
|
|
113
|
+
if (block) return block.condensed.slice(0, maxLen);
|
|
114
|
+
return text.slice(0, maxLen);
|
|
115
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain-order tests for the managed-Node runtime strategy.
|
|
3
|
+
*
|
|
4
|
+
* After change `embed-managed-node-runtime`, the `node` and `npm`
|
|
5
|
+
* strategy chains gain a `managedRuntimeStrategy` between
|
|
6
|
+
* `overrideStrategy` and the existing PATH/where lookup. These tests
|
|
7
|
+
* pin the precedence:
|
|
8
|
+
*
|
|
9
|
+
* 1. override (tool-overrides.json) — wins
|
|
10
|
+
* 2. managed runtime (<managedDir>/node/) — preferred over PATH
|
|
11
|
+
* 3. where / PATH — fallback
|
|
12
|
+
*
|
|
13
|
+
* `exists` is injected so no real filesystem is touched.
|
|
14
|
+
*
|
|
15
|
+
* See change: embed-managed-node-runtime (task 3.3).
|
|
16
|
+
*/
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { describe, expect, it } from "vitest";
|
|
20
|
+
import {
|
|
21
|
+
ToolRegistry,
|
|
22
|
+
registerDefaultTools,
|
|
23
|
+
OverridesStore,
|
|
24
|
+
} from "../index.js";
|
|
25
|
+
|
|
26
|
+
function freshRegistry(opts: {
|
|
27
|
+
exists?: (p: string) => boolean;
|
|
28
|
+
which?: (name: string) => string | null;
|
|
29
|
+
overrides?: Record<string, string>;
|
|
30
|
+
platform?: NodeJS.Platform;
|
|
31
|
+
}) {
|
|
32
|
+
const store = new OverridesStore({
|
|
33
|
+
filePath: path.join(os.tmpdir(), `mr-test-${Math.random()}.json`),
|
|
34
|
+
warn: () => {},
|
|
35
|
+
});
|
|
36
|
+
for (const [k, v] of Object.entries(opts.overrides ?? {})) store.set(k, v);
|
|
37
|
+
|
|
38
|
+
const r = new ToolRegistry({
|
|
39
|
+
overrides: store,
|
|
40
|
+
platform: opts.platform ?? "linux",
|
|
41
|
+
});
|
|
42
|
+
registerDefaultTools(r, {
|
|
43
|
+
exists: opts.exists ?? (() => false),
|
|
44
|
+
which: opts.which ?? (() => null),
|
|
45
|
+
npmRootGlobal: () => "",
|
|
46
|
+
});
|
|
47
|
+
return r;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const HOME = os.homedir();
|
|
51
|
+
const MANAGED_NODE_UNIX = path.join(HOME, ".pi-dashboard", "node", "bin", "node");
|
|
52
|
+
const MANAGED_NPM_UNIX = path.join(HOME, ".pi-dashboard", "node", "bin", "npm");
|
|
53
|
+
const MANAGED_NODE_WIN = path.join(HOME, ".pi-dashboard", "node", "node.exe");
|
|
54
|
+
const MANAGED_NPM_WIN = path.join(HOME, ".pi-dashboard", "node", "npm.cmd");
|
|
55
|
+
|
|
56
|
+
describe("node: managed-runtime strategy precedence", () => {
|
|
57
|
+
it("managed runtime present → returned over PATH (Unix)", () => {
|
|
58
|
+
const r = freshRegistry({
|
|
59
|
+
platform: "linux",
|
|
60
|
+
exists: (p) => p === MANAGED_NODE_UNIX,
|
|
61
|
+
which: () => "/usr/bin/node",
|
|
62
|
+
});
|
|
63
|
+
const res = r.resolve("node");
|
|
64
|
+
expect(res.ok).toBe(true);
|
|
65
|
+
expect(res.path).toBe(MANAGED_NODE_UNIX);
|
|
66
|
+
expect(res.source).toBe("managed");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("managed runtime present → returned over PATH (Windows)", () => {
|
|
70
|
+
const r = freshRegistry({
|
|
71
|
+
platform: "win32",
|
|
72
|
+
exists: (p) => p === MANAGED_NODE_WIN,
|
|
73
|
+
which: () => "C:\\node\\node.exe",
|
|
74
|
+
});
|
|
75
|
+
const res = r.resolve("node");
|
|
76
|
+
expect(res.ok).toBe(true);
|
|
77
|
+
expect(res.path).toBe(MANAGED_NODE_WIN);
|
|
78
|
+
expect(res.source).toBe("managed");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("override wins over managed runtime", () => {
|
|
82
|
+
const custom = "/opt/custom/node";
|
|
83
|
+
const r = freshRegistry({
|
|
84
|
+
platform: "linux",
|
|
85
|
+
overrides: { node: custom },
|
|
86
|
+
exists: (p) => p === custom || p === MANAGED_NODE_UNIX,
|
|
87
|
+
});
|
|
88
|
+
const res = r.resolve("node");
|
|
89
|
+
expect(res.ok).toBe(true);
|
|
90
|
+
expect(res.path).toBe(custom);
|
|
91
|
+
expect(res.source).toBe("override");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("both absent → falls through to PATH lookup", () => {
|
|
95
|
+
const r = freshRegistry({
|
|
96
|
+
platform: "linux",
|
|
97
|
+
exists: () => false,
|
|
98
|
+
which: (name) => (name === "node" ? "/usr/bin/node" : null),
|
|
99
|
+
});
|
|
100
|
+
const res = r.resolve("node");
|
|
101
|
+
expect(res.ok).toBe(true);
|
|
102
|
+
expect(res.path).toBe("/usr/bin/node");
|
|
103
|
+
expect(res.source).toBe("system");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("nothing present → ok:false with diagnostic trail", () => {
|
|
107
|
+
const r = freshRegistry({ platform: "linux", exists: () => false, which: () => null });
|
|
108
|
+
const res = r.resolve("node");
|
|
109
|
+
expect(res.ok).toBe(false);
|
|
110
|
+
const trailNames = res.tried.map((t) => t.strategy);
|
|
111
|
+
expect(trailNames).toContain("override");
|
|
112
|
+
expect(trailNames).toContain("managed");
|
|
113
|
+
expect(trailNames).toContain("where");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("npm: managed-runtime strategy precedence", () => {
|
|
118
|
+
it("managed npm present → returned over PATH (Unix)", () => {
|
|
119
|
+
const r = freshRegistry({
|
|
120
|
+
platform: "linux",
|
|
121
|
+
exists: (p) => p === MANAGED_NPM_UNIX,
|
|
122
|
+
which: () => "/usr/bin/npm",
|
|
123
|
+
});
|
|
124
|
+
const res = r.resolve("npm");
|
|
125
|
+
expect(res.ok).toBe(true);
|
|
126
|
+
expect(res.path).toBe(MANAGED_NPM_UNIX);
|
|
127
|
+
expect(res.source).toBe("managed");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("managed npm present → returned over PATH (Windows)", () => {
|
|
131
|
+
const r = freshRegistry({
|
|
132
|
+
platform: "win32",
|
|
133
|
+
exists: (p) => p === MANAGED_NPM_WIN,
|
|
134
|
+
which: () => "C:\\node\\npm.cmd",
|
|
135
|
+
});
|
|
136
|
+
const res = r.resolve("npm");
|
|
137
|
+
expect(res.ok).toBe(true);
|
|
138
|
+
expect(res.path).toBe(MANAGED_NPM_WIN);
|
|
139
|
+
expect(res.source).toBe("managed");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("override wins over managed npm", () => {
|
|
143
|
+
const custom = "/opt/custom/npm";
|
|
144
|
+
const r = freshRegistry({
|
|
145
|
+
platform: "linux",
|
|
146
|
+
overrides: { npm: custom },
|
|
147
|
+
exists: (p) => p === custom || p === MANAGED_NPM_UNIX,
|
|
148
|
+
});
|
|
149
|
+
const res = r.resolve("npm");
|
|
150
|
+
expect(res.ok).toBe(true);
|
|
151
|
+
expect(res.path).toBe(custom);
|
|
152
|
+
expect(res.source).toBe("override");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("npm: managed absent + PATH present → falls through (Unix)", () => {
|
|
156
|
+
const r = freshRegistry({
|
|
157
|
+
platform: "linux",
|
|
158
|
+
exists: () => false,
|
|
159
|
+
which: (name) => (name === "npm" ? "/usr/bin/npm" : null),
|
|
160
|
+
});
|
|
161
|
+
const res = r.resolve("npm");
|
|
162
|
+
expect(res.ok).toBe(true);
|
|
163
|
+
expect(res.path).toBe("/usr/bin/npm");
|
|
164
|
+
expect(res.source).toBe("system");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
bareImportStrategy,
|
|
19
19
|
managedBinStrategy,
|
|
20
20
|
managedModuleStrategy,
|
|
21
|
+
managedRuntimeStrategy,
|
|
21
22
|
npmGlobalStrategy,
|
|
22
23
|
overrideStrategy,
|
|
23
24
|
whereStrategy,
|
|
@@ -39,21 +40,27 @@ function classify(strategyName: string): Source {
|
|
|
39
40
|
// ── Binary definitions ──────────────────────────────────────────────────────
|
|
40
41
|
|
|
41
42
|
function binaryDef(binaryName: string, deps?: StrategyDeps): ToolDefinition {
|
|
43
|
+
// The `node` binary gets the managed-Node runtime strategy prepended
|
|
44
|
+
// (after override) so the persistent <managedDir>/node/ install wins
|
|
45
|
+
// over PATH lookup. See change: embed-managed-node-runtime.
|
|
46
|
+
const isNode = binaryName === "node";
|
|
47
|
+
const strategies = [
|
|
48
|
+
overrideStrategy(binaryName, deps),
|
|
49
|
+
...(isNode ? [managedRuntimeStrategy("node", deps)] : []),
|
|
50
|
+
managedBinStrategy(binaryName, deps),
|
|
51
|
+
whereStrategy(binaryName, deps),
|
|
52
|
+
];
|
|
42
53
|
return {
|
|
43
54
|
name: binaryName,
|
|
44
55
|
kind: "binary",
|
|
45
|
-
strategies
|
|
46
|
-
overrideStrategy(binaryName, deps),
|
|
47
|
-
managedBinStrategy(binaryName, deps),
|
|
48
|
-
whereStrategy(binaryName, deps),
|
|
49
|
-
],
|
|
56
|
+
strategies,
|
|
50
57
|
classify,
|
|
51
58
|
};
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
// ── Module definitions ──────────────────────────────────────────────────────
|
|
55
62
|
|
|
56
|
-
/** Sibling probe for an aliased package name (pi: `@
|
|
63
|
+
/** Sibling probe for an aliased package name (pi: `@earendil-works/*` + `@mariozechner/*`). */
|
|
57
64
|
function moduleDefWithAliases(
|
|
58
65
|
canonicalName: string,
|
|
59
66
|
pkgNames: readonly string[],
|
|
@@ -181,7 +188,7 @@ const nodeScriptToArgv: ToolDefinition["toArgv"] = (resolvedPath, { platform, re
|
|
|
181
188
|
* On Unix, the chain finds `pi` on PATH; argv = [pi].
|
|
182
189
|
*/
|
|
183
190
|
function piExecutorDef(deps?: StrategyDeps): ToolDefinition {
|
|
184
|
-
const piPkgAliases = ["@
|
|
191
|
+
const piPkgAliases = ["@earendil-works/pi-coding-agent", "@mariozechner/pi-coding-agent"];
|
|
185
192
|
const cliEntry = path.join("dist", "cli.js");
|
|
186
193
|
|
|
187
194
|
const winStrategies = [
|
|
@@ -283,14 +290,20 @@ function npmExecutorDef(deps?: StrategyDeps): ToolDefinition {
|
|
|
283
290
|
},
|
|
284
291
|
};
|
|
285
292
|
|
|
293
|
+
// Managed-Node runtime: prefer <managedDir>/node/{npm.cmd,bin/npm}
|
|
294
|
+
// when the runtime is installed. See change: embed-managed-node-runtime.
|
|
295
|
+
const managedNpm = managedRuntimeStrategy("npm", deps);
|
|
296
|
+
|
|
286
297
|
const winStrategies = [
|
|
287
298
|
overrideStrategy("npm", deps),
|
|
299
|
+
managedNpm,
|
|
288
300
|
npmCliBesideNodeStrategy,
|
|
289
301
|
whereStrategy("npm", deps),
|
|
290
302
|
];
|
|
291
303
|
|
|
292
304
|
const unixStrategies = [
|
|
293
305
|
overrideStrategy("npm", deps),
|
|
306
|
+
managedNpm,
|
|
294
307
|
whereStrategy("npm", deps),
|
|
295
308
|
];
|
|
296
309
|
|
|
@@ -400,7 +413,19 @@ export function registerDefaultTools(registry: ToolRegistry, deps?: StrategyDeps
|
|
|
400
413
|
registry.register(
|
|
401
414
|
moduleDefWithAliases(
|
|
402
415
|
"pi-coding-agent",
|
|
403
|
-
["@
|
|
416
|
+
["@earendil-works/pi-coding-agent", "@mariozechner/pi-coding-agent"],
|
|
417
|
+
path.join("dist", "index.js"),
|
|
418
|
+
deps,
|
|
419
|
+
),
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// pi-ai module — used by model-proxy to call upstream LLM providers.
|
|
423
|
+
// Aliases: @earendil-works/pi-ai (preferred) + @mariozechner/pi-ai (legacy fallback).
|
|
424
|
+
// See change: add-dashboard-model-proxy.
|
|
425
|
+
registry.register(
|
|
426
|
+
moduleDefWithAliases(
|
|
427
|
+
"pi-ai",
|
|
428
|
+
["@earendil-works/pi-ai", "@mariozechner/pi-ai"],
|
|
404
429
|
path.join("dist", "index.js"),
|
|
405
430
|
deps,
|
|
406
431
|
),
|
|
@@ -13,6 +13,7 @@ import { createRequire } from "node:module";
|
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
import { ToolResolver, isAppImageSelfHit } from "../platform/binary-lookup.js";
|
|
15
15
|
import { getManagedBin, getManagedDir } from "../managed-paths.js";
|
|
16
|
+
import { getManagedNodeBinDir } from "../platform/managed-node-path.js";
|
|
16
17
|
import * as npm from "../platform/npm.js";
|
|
17
18
|
import type { Strategy, StrategyCtx, StrategyResult } from "./types.js";
|
|
18
19
|
|
|
@@ -86,6 +87,47 @@ export function overrideStrategy(toolName: string, deps?: StrategyDeps): Strateg
|
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Managed Node runtime: `<managedDir>/node/{node.exe,npm.cmd,npx.cmd}`
|
|
92
|
+
* on Windows or `<managedDir>/node/bin/{node,npm,npx}` on Unix.
|
|
93
|
+
*
|
|
94
|
+
* Lets `ToolRegistry.resolve("node")` and `resolve("npm")` prefer the
|
|
95
|
+
* persistent runtime under `~/.pi-dashboard/node/` (installed by
|
|
96
|
+
* `installManagedNode`) over the system PATH lookup, while still
|
|
97
|
+
* deferring to `tool-overrides.json`.
|
|
98
|
+
*
|
|
99
|
+
* Returns `null` when the managed Node runtime is not present, so the
|
|
100
|
+
* standalone-CLI / no-Electron-resources case falls through cleanly to
|
|
101
|
+
* the existing `where`/PATH strategy.
|
|
102
|
+
*
|
|
103
|
+
* See change: embed-managed-node-runtime (spec: managed-node-runtime,
|
|
104
|
+
* Requirement: ToolRegistry resolves managed runtime first).
|
|
105
|
+
*/
|
|
106
|
+
export function managedRuntimeStrategy(
|
|
107
|
+
toolName: "node" | "npm" | "npx",
|
|
108
|
+
deps?: StrategyDeps,
|
|
109
|
+
): Strategy {
|
|
110
|
+
const { exists } = d(deps);
|
|
111
|
+
return {
|
|
112
|
+
name: "managed",
|
|
113
|
+
run(ctx): StrategyResult {
|
|
114
|
+
const dir = getManagedNodeBinDir(ctx.env, ctx.platform);
|
|
115
|
+
const isWin = ctx.platform === "win32";
|
|
116
|
+
const fileName =
|
|
117
|
+
toolName === "node"
|
|
118
|
+
? isWin
|
|
119
|
+
? "node.exe"
|
|
120
|
+
: "node"
|
|
121
|
+
: isWin
|
|
122
|
+
? `${toolName}.cmd`
|
|
123
|
+
: toolName;
|
|
124
|
+
const candidate = path.join(dir, fileName);
|
|
125
|
+
if (exists(candidate)) return { ok: true, path: candidate };
|
|
126
|
+
return { ok: false, reason: `missing: ${candidate}` };
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
89
131
|
/**
|
|
90
132
|
* Managed install: `~/.pi-dashboard/node_modules/.bin/<name>(.cmd)` for
|
|
91
133
|
* binaries, or any explicit relative path under `MANAGED_DIR` for
|
|
@@ -125,6 +125,15 @@ export interface DashboardSession {
|
|
|
125
125
|
* the entry. See change: add-extension-ui-decorations.
|
|
126
126
|
*/
|
|
127
127
|
uiDecorators?: Record<string, DecoratorDescriptor>;
|
|
128
|
+
/**
|
|
129
|
+
* Per-session image asset registry, keyed by content hash. Populated by
|
|
130
|
+
* `asset_register` events emitted by the bridge for local-file images
|
|
131
|
+
* referenced as `` in assistant markdown. Survives event-buffer
|
|
132
|
+
* eviction (lives on Session, not in the rolling event buffer) so
|
|
133
|
+
* `pi-asset:<hash>` references in older messages still resolve.
|
|
134
|
+
* See change: chat-markdown-local-images-and-math.
|
|
135
|
+
*/
|
|
136
|
+
assets?: Record<string, { data: string; mimeType: string }>;
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
// ── Extension UI System (Phase 1: management-modal slot) ───────────
|
|
@@ -333,6 +342,41 @@ export interface ModelInfo {
|
|
|
333
342
|
id: string;
|
|
334
343
|
}
|
|
335
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Provider catalogue entry pushed by the bridge to the server.
|
|
347
|
+
* Derived from pi's live `ModelRegistry` (see provider-register.ts in
|
|
348
|
+
* the bridge). The server caches the most recently received catalogue
|
|
349
|
+
* and uses it as the source for `GET /api/provider-auth/status`.
|
|
350
|
+
* See change: replace-hardcoded-provider-lists.
|
|
351
|
+
*/
|
|
352
|
+
export interface ProviderInfo {
|
|
353
|
+
/** pi-ai provider id (e.g. "anthropic", "deepseek", "google-vertex"). */
|
|
354
|
+
id: string;
|
|
355
|
+
/** From `modelRegistry.getProviderDisplayName(id)`; falls back to id. */
|
|
356
|
+
displayName: string;
|
|
357
|
+
/** True iff `authStorage.getOAuthProviders()` includes this id. */
|
|
358
|
+
hasOAuth: boolean;
|
|
359
|
+
/** True iff a credential is stored in auth.json. */
|
|
360
|
+
configured: boolean;
|
|
361
|
+
/** Where the credential is sourced from, when configured. */
|
|
362
|
+
source?: "stored" | "environment" | "fallback" | "runtime";
|
|
363
|
+
/** First env var name pi-ai consults for this provider, when applicable. */
|
|
364
|
+
envVar?: string;
|
|
365
|
+
/** True when configured via ambient credential chain (AWS profile / GCP ADC). */
|
|
366
|
+
ambient?: boolean;
|
|
367
|
+
/** Expiry timestamp for OAuth credentials. */
|
|
368
|
+
expires?: number;
|
|
369
|
+
/**
|
|
370
|
+
* True when this provider was registered by the dashboard itself via
|
|
371
|
+
* `pi.registerProvider()` from `~/.pi/agent/providers.json` (a "custom"
|
|
372
|
+
* provider managed by the LLM Providers settings section). Consumers
|
|
373
|
+
* use this to suppress API-key auth rows for custom providers — their
|
|
374
|
+
* keys are managed elsewhere. OAuth rows are still emitted because a
|
|
375
|
+
* custom OAuth provider needs its login button.
|
|
376
|
+
*/
|
|
377
|
+
custom?: boolean;
|
|
378
|
+
}
|
|
379
|
+
|
|
336
380
|
/** Role assignment info (from pi-flows role-manager) */
|
|
337
381
|
export interface RoleInfo {
|
|
338
382
|
roles: Record<string, string>;
|
|
@@ -383,6 +427,19 @@ export function deriveChangeState(change: OpenSpecChange): ChangeState {
|
|
|
383
427
|
export interface OpenSpecData {
|
|
384
428
|
initialized: boolean;
|
|
385
429
|
changes: OpenSpecChange[];
|
|
430
|
+
/**
|
|
431
|
+
* Cold-boot signaling: server has detected `openspec/changes/` for this
|
|
432
|
+
* cwd but the slow poll has not yet produced authoritative data.
|
|
433
|
+
*
|
|
434
|
+
* Optional for backwards compatibility — absence means `false`. Composes
|
|
435
|
+
* with `initialized` to encode three states:
|
|
436
|
+
* - { initialized: false, pending: false } → no openspec dir
|
|
437
|
+
* - { initialized: false, pending: true } → dir exists, polling
|
|
438
|
+
* - { initialized: true, pending: ? } → poll complete
|
|
439
|
+
*
|
|
440
|
+
* See change: fix-cold-boot-openspec-protocol.
|
|
441
|
+
*/
|
|
442
|
+
pending?: boolean;
|
|
386
443
|
}
|
|
387
444
|
|
|
388
445
|
/** OpenSpec workflow phase detected from tool calls */
|
|
@@ -592,4 +649,11 @@ export interface ApiResponse<T = unknown> {
|
|
|
592
649
|
success: boolean;
|
|
593
650
|
data?: T;
|
|
594
651
|
error?: string;
|
|
652
|
+
/**
|
|
653
|
+
* Optional structured failure-classifier code paired with `error`.
|
|
654
|
+
* Lets clients render specific UI for known failure modes
|
|
655
|
+
* (e.g., `"FORK_EMPTY_SESSION"`).
|
|
656
|
+
* See change: fix-fork-empty-session-silent-timeout.
|
|
657
|
+
*/
|
|
658
|
+
code?: string;
|
|
595
659
|
}
|