@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,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel re-exports for selected shared symbols. Most consumers import
|
|
3
|
+
* directly from per-file paths (`@blackbelt-technology/pi-dashboard-shared/<file>.js`)
|
|
4
|
+
* via the package's `exports` map. This barrel exists for symbols that
|
|
5
|
+
* would otherwise be cumbersome to wire — currently the doctor core.
|
|
6
|
+
*
|
|
7
|
+
* Added by change: doctor-rich-output.
|
|
8
|
+
*/
|
|
9
|
+
export * from "./doctor-core.js";
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* installable-list.ts — types and helpers for ~/.pi/dashboard/installable.json.
|
|
3
|
+
*
|
|
4
|
+
* This file describes the set of packages the dashboard needs installed.
|
|
5
|
+
* Electron seeds the file on first run; Bridge / Standalone ignore it (file-absent
|
|
6
|
+
* path is a no-op in the server bootstrap). The server reads it during bootstrap.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
export type InstallableKind = "npm" | "pi-extension";
|
|
13
|
+
|
|
14
|
+
export interface InstallablePackage {
|
|
15
|
+
name: string;
|
|
16
|
+
/** Semver range or "*". */
|
|
17
|
+
version: string;
|
|
18
|
+
required: boolean;
|
|
19
|
+
kind: InstallableKind;
|
|
20
|
+
deprecated?: boolean;
|
|
21
|
+
defaultOff?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface InstallableList {
|
|
25
|
+
version: string;
|
|
26
|
+
packages: InstallablePackage[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MergeResult {
|
|
30
|
+
list: InstallableList;
|
|
31
|
+
warnings: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const VALID_KINDS: ReadonlySet<string> = new Set<InstallableKind>(["npm", "pi-extension"]);
|
|
35
|
+
|
|
36
|
+
function defaultConfigDir(): string {
|
|
37
|
+
return path.join(os.homedir(), ".pi", "dashboard");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function installablePath(configDir: string): string {
|
|
41
|
+
return path.join(configDir, "installable.json");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read `~/.pi/dashboard/installable.json` (or `configDir/installable.json`).
|
|
46
|
+
*
|
|
47
|
+
* Returns `null` when the file is absent. Logs a warning and drops entries
|
|
48
|
+
* with an invalid `kind` field. Does NOT create the file.
|
|
49
|
+
*/
|
|
50
|
+
export async function readInstallableList(
|
|
51
|
+
configDir?: string,
|
|
52
|
+
): Promise<InstallableList | null> {
|
|
53
|
+
const dir = configDir ?? defaultConfigDir();
|
|
54
|
+
const filePath = installablePath(dir);
|
|
55
|
+
|
|
56
|
+
let raw: string;
|
|
57
|
+
try {
|
|
58
|
+
raw = await fs.promises.readFile(filePath, "utf-8");
|
|
59
|
+
} catch (err: any) {
|
|
60
|
+
if (err.code === "ENOENT") return null;
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const parsed = JSON.parse(raw) as InstallableList;
|
|
65
|
+
|
|
66
|
+
const validPackages: InstallablePackage[] = [];
|
|
67
|
+
for (const pkg of parsed.packages ?? []) {
|
|
68
|
+
if (!VALID_KINDS.has(pkg.kind)) {
|
|
69
|
+
console.warn(
|
|
70
|
+
`[installable-list] Dropping entry "${pkg.name}" with unknown kind "${pkg.kind}".`,
|
|
71
|
+
);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
validPackages.push(pkg);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { version: parsed.version, packages: validPackages };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Atomically write `list` to `configDir/installable.json`.
|
|
82
|
+
* Writes a temp file then renames so readers never see a partial write.
|
|
83
|
+
*/
|
|
84
|
+
export async function writeInstallableList(
|
|
85
|
+
list: InstallableList,
|
|
86
|
+
configDir?: string,
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
const dir = configDir ?? defaultConfigDir();
|
|
89
|
+
const filePath = installablePath(dir);
|
|
90
|
+
const tmpPath = filePath + ".tmp." + process.pid;
|
|
91
|
+
|
|
92
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
93
|
+
await fs.promises.writeFile(tmpPath, JSON.stringify(list, null, 2), "utf-8");
|
|
94
|
+
await fs.promises.rename(tmpPath, filePath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Pure merge: reconcile a user's `existing` list against the `bundled` defaults.
|
|
99
|
+
*
|
|
100
|
+
* Rules:
|
|
101
|
+
* - Package in both: keep user's pinned version (user wins); emit a warning when
|
|
102
|
+
* the bundled default differs.
|
|
103
|
+
* - Package in existing but NOT in bundled: keep it, set `deprecated: true`, emit warning.
|
|
104
|
+
* - New `required: true` package in bundled: add it.
|
|
105
|
+
* - New `required: false` package in bundled: add it with `defaultOff: true`.
|
|
106
|
+
* - Result version marker comes from `bundled.version`.
|
|
107
|
+
*/
|
|
108
|
+
export function mergeInstallableList(
|
|
109
|
+
existing: InstallableList,
|
|
110
|
+
bundled: InstallableList,
|
|
111
|
+
): MergeResult {
|
|
112
|
+
const warnings: string[] = [];
|
|
113
|
+
const bundledMap = new Map(bundled.packages.map((p) => [p.name, p]));
|
|
114
|
+
const existingMap = new Map(existing.packages.map((p) => [p.name, p]));
|
|
115
|
+
|
|
116
|
+
const merged: InstallablePackage[] = [];
|
|
117
|
+
|
|
118
|
+
// Walk existing packages.
|
|
119
|
+
for (const pkg of existing.packages) {
|
|
120
|
+
const bundledPkg = bundledMap.get(pkg.name);
|
|
121
|
+
if (!bundledPkg) {
|
|
122
|
+
// Dropped in bundled → deprecate.
|
|
123
|
+
warnings.push(
|
|
124
|
+
`Package "${pkg.name}" is no longer in the bundled list and has been marked deprecated.`,
|
|
125
|
+
);
|
|
126
|
+
merged.push({ ...pkg, deprecated: true });
|
|
127
|
+
} else {
|
|
128
|
+
// Present in both → user version wins.
|
|
129
|
+
if (pkg.version !== bundledPkg.version) {
|
|
130
|
+
warnings.push(
|
|
131
|
+
`Package "${pkg.name}" is pinned at "${pkg.version}" (bundled default: "${bundledPkg.version}"). User pin preserved.`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
merged.push({ ...pkg });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Walk bundled packages not yet in existing.
|
|
139
|
+
for (const bundledPkg of bundled.packages) {
|
|
140
|
+
if (existingMap.has(bundledPkg.name)) continue;
|
|
141
|
+
if (!bundledPkg.required) {
|
|
142
|
+
merged.push({ ...bundledPkg, defaultOff: true });
|
|
143
|
+
} else {
|
|
144
|
+
merged.push({ ...bundledPkg });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
list: { version: bundled.version, packages: merged },
|
|
150
|
+
warnings,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature flag for the LaunchSource V2 resolver.
|
|
3
|
+
* Default: true (Phase C cutover). Set LAUNCH_SOURCE_V2=false to revert to
|
|
4
|
+
* the legacy wizard + mode.json path for debugging.
|
|
5
|
+
*
|
|
6
|
+
* TODO: remove LAUNCH_SOURCE_V2 flag in follow-up change after Phase C ships
|
|
7
|
+
* without regressions for one release cycle. The flag and its CI matrix
|
|
8
|
+
* entry should be deleted at that point.
|
|
9
|
+
*/
|
|
10
|
+
export function isLaunchSourceV2Enabled(env: Record<string, string | undefined>): boolean {
|
|
11
|
+
const val = env["LAUNCH_SOURCE_V2"];
|
|
12
|
+
if (val === undefined) return true; // default ON in Phase C
|
|
13
|
+
return val === "true" || val === "1";
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LaunchSource — discriminated union describing how the dashboard server was (or should be) started.
|
|
3
|
+
*
|
|
4
|
+
* "attach" — a server is already running; Electron attaches to it.
|
|
5
|
+
* "devMonorepo" — running from a checked-out monorepo (dev workflow).
|
|
6
|
+
* "piExtension" — the pi bridge extension owns a server package in node_modules.
|
|
7
|
+
* "npmGlobal" — `pi-dashboard` is installed globally via npm.
|
|
8
|
+
* "extracted" — bundled Electron resources provide the server (managed install).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type SourceKind = "attach" | "devMonorepo" | "piExtension" | "npmGlobal" | "extracted";
|
|
12
|
+
|
|
13
|
+
export type LaunchSource =
|
|
14
|
+
| { kind: "attach"; url: string; starter: "Bridge" | "Standalone" | "Electron" }
|
|
15
|
+
| { kind: "devMonorepo"; cliPath: string; cwd: string }
|
|
16
|
+
| { kind: "piExtension"; cliPath: string; cwd: string }
|
|
17
|
+
| { kind: "npmGlobal"; cliPath: string; cwd: string }
|
|
18
|
+
| { kind: "extracted"; cliPath: string; cwd: string; didExtract?: boolean };
|
|
@@ -40,6 +40,22 @@ const CLI_ARCHIVE_RE = /openspec\s+archive\s+["']?([^\s"']+)["']?/;
|
|
|
40
40
|
/** Regex to match openspec new change "name" (positional arg) */
|
|
41
41
|
const CLI_NEW_CHANGE_RE = /openspec\s+new\s+change\s+["']?([^\s"']+)["']?/;
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* OpenSpec change-slug shape: lowercase kebab-case, must start with a letter,
|
|
45
|
+
* max 64 characters. Mirrors the validation enforced by `openspec new change`.
|
|
46
|
+
*
|
|
47
|
+
* Single source of truth for any code that needs to gate a captured token
|
|
48
|
+
* before treating it as an OpenSpec change name (detector + auto-attach
|
|
49
|
+
* defense-in-depth in event-wiring.ts).
|
|
50
|
+
*
|
|
51
|
+
* See change: fix-uuid-rename-bug.
|
|
52
|
+
*/
|
|
53
|
+
const OPENSPEC_CHANGE_SLUG_RE = /^[a-z][a-z0-9-]{0,63}$/;
|
|
54
|
+
|
|
55
|
+
export function isValidOpenSpecChangeSlug(name: string): boolean {
|
|
56
|
+
return OPENSPEC_CHANGE_SLUG_RE.test(name);
|
|
57
|
+
}
|
|
58
|
+
|
|
43
59
|
export function detectOpenSpecActivity(
|
|
44
60
|
toolName: string,
|
|
45
61
|
args: Record<string, unknown> | undefined,
|
|
@@ -63,7 +79,7 @@ export function detectOpenSpecActivity(
|
|
|
63
79
|
|
|
64
80
|
// Check for openspec change file read → change name detection (passive)
|
|
65
81
|
const changeMatch = path.match(CHANGE_PATH_RE);
|
|
66
|
-
if (changeMatch) {
|
|
82
|
+
if (changeMatch && isValidOpenSpecChangeSlug(changeMatch[1])) {
|
|
67
83
|
return { changeName: changeMatch[1], isActive: false };
|
|
68
84
|
}
|
|
69
85
|
|
|
@@ -75,7 +91,7 @@ export function detectOpenSpecActivity(
|
|
|
75
91
|
if (!path) return null;
|
|
76
92
|
|
|
77
93
|
const changeMatch = path.match(CHANGE_PATH_RE);
|
|
78
|
-
if (changeMatch) {
|
|
94
|
+
if (changeMatch && isValidOpenSpecChangeSlug(changeMatch[1])) {
|
|
79
95
|
return { changeName: changeMatch[1], isActive: true };
|
|
80
96
|
}
|
|
81
97
|
|
|
@@ -94,11 +110,13 @@ export function detectOpenSpecActivity(
|
|
|
94
110
|
if (!match) return null;
|
|
95
111
|
|
|
96
112
|
const name = match[1];
|
|
97
|
-
// Reject
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
113
|
+
// Reject any token that is not a valid OpenSpec change slug. Subsumes the
|
|
114
|
+
// earlier `-`-prefix guard (a leading `-` fails the `[a-z]` first-char
|
|
115
|
+
// class) and additionally rejects UUIDs, mixed-case, underscored, or
|
|
116
|
+
// overlong tokens that the CLI regexes' `[^\s"']+` capture group would
|
|
117
|
+
// otherwise pass through into auto-attach + auto-rename.
|
|
118
|
+
// See changes: fix-openspec-flag-rename-bug, fix-uuid-rename-bug.
|
|
119
|
+
if (!isValidOpenSpecChangeSlug(name)) return null;
|
|
102
120
|
|
|
103
121
|
return { changeName: name, isActive: true };
|
|
104
122
|
}
|
|
@@ -47,10 +47,18 @@ export interface SpawnDetachedOptions {
|
|
|
47
47
|
/** Environment for the child. Defaults to `process.env` via Node. */
|
|
48
48
|
env?: NodeJS.ProcessEnv;
|
|
49
49
|
/**
|
|
50
|
-
* Optional file descriptor for stderr. When omitted,
|
|
50
|
+
* Optional file descriptor for combined stdout + stderr. When omitted,
|
|
51
|
+
* BOTH stdout and stderr are "ignore".
|
|
52
|
+
*
|
|
51
53
|
* Caller is responsible for `fs.openSync(logPath, "a")` and closing the
|
|
52
54
|
* parent's copy after spawn (the child retains its dup via stdio
|
|
53
55
|
* inheritance). File fds survive parent death; pipes do not.
|
|
56
|
+
*
|
|
57
|
+
* Routing both streams to a single fd matches what the standalone CLI
|
|
58
|
+
* (`packages/server/src/cli.ts`) does directly with raw `spawn()`. Prior
|
|
59
|
+
* to change `fix-electron-extracted-jiti-and-stdio-capture`, only stderr
|
|
60
|
+
* was captured and clean-startup `console.log` output was silently
|
|
61
|
+
* dropped (resulting in 0-byte server.log files).
|
|
54
62
|
*/
|
|
55
63
|
logFd?: number;
|
|
56
64
|
/**
|
|
@@ -123,7 +131,10 @@ export interface SpawnDetachedResult {
|
|
|
123
131
|
*/
|
|
124
132
|
export async function spawnDetached(opts: SpawnDetachedOptions): Promise<SpawnDetachedResult> {
|
|
125
133
|
const stdioIn: "ignore" | "pipe" = opts.stdinMode ?? "ignore";
|
|
126
|
-
|
|
134
|
+
// logFd applies to BOTH stdout and stderr. See JSDoc on logFd above and
|
|
135
|
+
// change: fix-electron-extracted-jiti-and-stdio-capture.
|
|
136
|
+
const outFd: "ignore" | number = opts.logFd ?? "ignore";
|
|
137
|
+
const stdio: ("ignore" | "pipe" | number)[] = [stdioIn, outFd, outFd];
|
|
127
138
|
|
|
128
139
|
let child: ChildProcess;
|
|
129
140
|
let spawnError: string | null = null;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepend the managed Node runtime directory to a child-process `PATH`.
|
|
3
|
+
*
|
|
4
|
+
* The managed Node lives at `<managedDir>/node/` (Windows: binaries at
|
|
5
|
+
* the root; Unix: binaries under `bin/`). When present, every spawn the
|
|
6
|
+
* dashboard controls SHALL inherit that directory at the front of its
|
|
7
|
+
* `PATH` so plain `node` / `npm` invocations inside the child process
|
|
8
|
+
* resolve to the managed runtime.
|
|
9
|
+
*
|
|
10
|
+
* Pure helper: never mutates `process.env`, returns a distinct cloned
|
|
11
|
+
* env object, no-ops when the managed runtime is absent.
|
|
12
|
+
*
|
|
13
|
+
* See change: embed-managed-node-runtime (spec: managed-node-runtime,
|
|
14
|
+
* Requirement: Spawned children inherit managed Node on PATH).
|
|
15
|
+
*/
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { getManagedDir, type ManagedPathsEnv } from "../managed-paths.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the bin directory inside the managed Node tree.
|
|
22
|
+
* Windows: `<managedDir>/node` (node.exe + npm.cmd live at root)
|
|
23
|
+
* Unix: `<managedDir>/node/bin` (node, npm, npx live under bin/)
|
|
24
|
+
*
|
|
25
|
+
* `platform` defaults to `process.platform`; tests inject `"win32"` from
|
|
26
|
+
* a Linux host to exercise the Windows layout.
|
|
27
|
+
*/
|
|
28
|
+
export function getManagedNodeBinDir(
|
|
29
|
+
env?: ManagedPathsEnv,
|
|
30
|
+
platform: NodeJS.Platform = process.platform,
|
|
31
|
+
): string {
|
|
32
|
+
const root = path.join(getManagedDir(env), "node");
|
|
33
|
+
return platform === "win32" ? root : path.join(root, "bin");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Path to the managed `node` / `node.exe` binary. */
|
|
37
|
+
export function getManagedNodeBinary(
|
|
38
|
+
env?: ManagedPathsEnv,
|
|
39
|
+
platform: NodeJS.Platform = process.platform,
|
|
40
|
+
): string {
|
|
41
|
+
const bin = getManagedNodeBinDir(env, platform);
|
|
42
|
+
return path.join(bin, platform === "win32" ? "node.exe" : "node");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** True iff the managed Node runtime is installed (binary exists). */
|
|
46
|
+
export function isManagedNodePresent(
|
|
47
|
+
env?: ManagedPathsEnv,
|
|
48
|
+
platform: NodeJS.Platform = process.platform,
|
|
49
|
+
): boolean {
|
|
50
|
+
return existsSync(getManagedNodeBinary(env, platform));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Return a shallow-cloned env with the managed Node bin directory
|
|
55
|
+
* prepended to `PATH`. No-op (still returns a clone) when the managed
|
|
56
|
+
* runtime is not installed or its directory is already on PATH.
|
|
57
|
+
*
|
|
58
|
+
* Never mutates the input env or `process.env`.
|
|
59
|
+
*/
|
|
60
|
+
export function prependManagedNodeToPath(
|
|
61
|
+
baseEnv: NodeJS.ProcessEnv = process.env,
|
|
62
|
+
managedPathsEnv?: ManagedPathsEnv,
|
|
63
|
+
): NodeJS.ProcessEnv {
|
|
64
|
+
const cloned: NodeJS.ProcessEnv = { ...baseEnv };
|
|
65
|
+
if (!isManagedNodePresent(managedPathsEnv)) return cloned;
|
|
66
|
+
|
|
67
|
+
const dir = getManagedNodeBinDir(managedPathsEnv);
|
|
68
|
+
const currentPath = cloned.PATH ?? "";
|
|
69
|
+
// Avoid duplicate prepends when the dir is already at the head; cheap
|
|
70
|
+
// string contains check matches `buildSpawnEnv` style.
|
|
71
|
+
if (currentPath.split(path.delimiter).includes(dir)) return cloned;
|
|
72
|
+
|
|
73
|
+
cloned.PATH = currentPath
|
|
74
|
+
? `${dir}${path.delimiter}${currentPath}`
|
|
75
|
+
: dir;
|
|
76
|
+
return cloned;
|
|
77
|
+
}
|
|
@@ -118,29 +118,37 @@ export function toFileUrl(pathOrUrl: string): string {
|
|
|
118
118
|
* host can exercise the Windows branch without mutating `process.platform`.
|
|
119
119
|
*
|
|
120
120
|
* !! JITI VERSION CONTRACT !!
|
|
121
|
-
* The Windows-non-tsx arm
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
121
|
+
* The Windows-non-tsx arm relies on jiti's `file:///` triple-slash URL
|
|
122
|
+
* handling. Verified-good baselines (must be one of these in the
|
|
123
|
+
* offline cacache):
|
|
124
|
+
* • `@earendil-works/pi-coding-agent@0.74.x` (jiti `^2.7.0`) — current
|
|
125
|
+
* • `@mariozechner/pi-coding-agent@0.70.x` (jiti 2.x) — legacy
|
|
126
|
+
*
|
|
127
|
+
* Both ship a jiti that correctly normalises `file:///` entries on
|
|
128
|
+
* Windows. The contract was originally carved around 0.70.x in change
|
|
129
|
+
* `fix-windows-entry-script-url` and re-anchored at 0.74.x in change
|
|
130
|
+
* `migrate-pi-fork-to-earendil` (E.7).
|
|
131
|
+
*
|
|
132
|
+
* Known-broken (do NOT pin): `pi-coding-agent@0.71.x` shipping
|
|
133
|
+
* `jiti@2.6.5`. That jiti version misnormalises triple-slash to
|
|
134
|
+
* single-slash and prepends cwd as if the entry were a relative
|
|
135
|
+
* specifier, producing `<cwd>/file:/...` ENOENT errors. Keep the
|
|
136
|
+
* 0.71.x / 2.6.5 mention here so contributors recognise the
|
|
137
|
+
* regression pattern if it recurs in a future jiti.
|
|
138
|
+
*
|
|
139
|
+
* The Electron Windows codepath defends against version drift by
|
|
140
|
+
* resolving jiti from the managed dir's pinned `pi-coding-agent`
|
|
141
|
+
* (currently `@earendil-works/pi-coding-agent@0.74.0`, pinned in
|
|
142
|
+
* `packages/electron/offline-packages.json` and extracted into
|
|
143
|
+
* `~/.pi-dashboard/` by `installStandalone()` on first launch — see
|
|
144
|
+
* Defect 1 of change `fix-electron-windows-installer-and-server-bootstrap`).
|
|
145
|
+
* Since the managed-dir tree is pinned, the contract holds regardless
|
|
146
|
+
* of what jiti is on the user's PATH.
|
|
140
147
|
*
|
|
141
148
|
* If a future change bumps the offline-cacache `pi-coding-agent` pin to
|
|
142
|
-
* a version
|
|
143
|
-
* manually (run a packaged Electron app on Win10 + Win11) and
|
|
149
|
+
* a version OUTSIDE the verified baselines, RE-VERIFY this contract on
|
|
150
|
+
* Windows manually (run a packaged Electron app on Win10 + Win11) and
|
|
151
|
+
* either:
|
|
144
152
|
* 1. Update the contract (fix the file:// URL handling expectation), OR
|
|
145
153
|
* 2. Add a per-jiti-version branch here, OR
|
|
146
154
|
* 3. Switch the bundled loader to tsx (which has its own contract).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Extension ↔ Server WebSocket protocol messages.
|
|
3
3
|
*/
|
|
4
|
-
import type { DashboardEvent, CommandInfo, FlowInfo, SessionSource, ImageContent, FileEntry, TurnUsage, ContextUsage, ModelInfo, PiSessionInfo, OpenSpecPhase, RoleInfo, ExtensionUiModule, DecoratorDescriptor } from "./types.js";
|
|
4
|
+
import type { DashboardEvent, CommandInfo, FlowInfo, SessionSource, ImageContent, FileEntry, TurnUsage, ContextUsage, ModelInfo, ProviderInfo, PiSessionInfo, OpenSpecPhase, RoleInfo, ExtensionUiModule, DecoratorDescriptor } from "./types.js";
|
|
5
5
|
|
|
6
6
|
// ── Extension → Server ──────────────────────────────────────────────
|
|
7
7
|
|
|
@@ -22,6 +22,14 @@ export interface SessionRegisterMessage {
|
|
|
22
22
|
eventCount?: number;
|
|
23
23
|
/** OS process ID of the pi agent — used for force-kill escalation */
|
|
24
24
|
pid?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Server-minted spawn correlation token. Bridge populates this from
|
|
27
|
+
* `process.env.PI_DASHBOARD_SPAWN_TOKEN` IFF this is the first register
|
|
28
|
+
* for the bridge process (`bc.hasRegisteredOnce === false`). Subsequent
|
|
29
|
+
* registers (reattach, in-process new/fork/resume) omit it.
|
|
30
|
+
* See change: spawn-correlation-token.
|
|
31
|
+
*/
|
|
32
|
+
spawnToken?: string;
|
|
25
33
|
/**
|
|
26
34
|
* Why the bridge is registering this session. The bridge sets this to
|
|
27
35
|
* `"spawn"` for the very first `session_register` after process boot
|
|
@@ -151,6 +159,17 @@ export interface ModelsListMessage {
|
|
|
151
159
|
models: ModelInfo[];
|
|
152
160
|
}
|
|
153
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Bridge -> server: pi's live provider catalogue derived from
|
|
164
|
+
* `modelRegistry.authStorage` + `modelRegistry.getProviderDisplayName`.
|
|
165
|
+
* Sent alongside ModelsListMessage. See change: replace-hardcoded-provider-lists.
|
|
166
|
+
*/
|
|
167
|
+
export interface ProvidersListMessage {
|
|
168
|
+
type: "providers_list";
|
|
169
|
+
sessionId: string;
|
|
170
|
+
providers: ProviderInfo[];
|
|
171
|
+
}
|
|
172
|
+
|
|
154
173
|
export interface SessionNameUpdateMessage {
|
|
155
174
|
type: "session_name_update";
|
|
156
175
|
sessionId: string;
|
|
@@ -286,6 +305,27 @@ export interface ExtUiDecoratorMessage {
|
|
|
286
305
|
removed?: boolean;
|
|
287
306
|
}
|
|
288
307
|
|
|
308
|
+
// ── Markdown asset inlining (chat-markdown-local-images-and-math) ──
|
|
309
|
+
//
|
|
310
|
+
// Bridge → server: register a base64-encoded image asset under a content
|
|
311
|
+
// hash. Emitted by the bridge BEFORE the `message_update` / `message_end`
|
|
312
|
+
// event whose text references `pi-asset:<hash>`. Bytes ride exactly once
|
|
313
|
+
// per (session, hash) pair — subsequent references in later events emit
|
|
314
|
+
// no further `asset_register`. Persisted in `events.jsonl` alongside the
|
|
315
|
+
// referencing message events so reconnect/replay rebuilds the per-session
|
|
316
|
+
// asset registry deterministically. See change:
|
|
317
|
+
// chat-markdown-local-images-and-math.
|
|
318
|
+
export interface AssetRegisterMessage {
|
|
319
|
+
type: "asset_register";
|
|
320
|
+
sessionId: string;
|
|
321
|
+
/** Content hash (sha256 truncated to 16 hex chars). */
|
|
322
|
+
hash: string;
|
|
323
|
+
/** MIME type (one of the bridge's image allowlist). */
|
|
324
|
+
mimeType: string;
|
|
325
|
+
/** Base64-encoded file bytes. */
|
|
326
|
+
data: string;
|
|
327
|
+
}
|
|
328
|
+
|
|
289
329
|
export type ExtensionToServerMessage =
|
|
290
330
|
| SessionRegisterMessage
|
|
291
331
|
| SessionUnregisterMessage
|
|
@@ -299,6 +339,7 @@ export type ExtensionToServerMessage =
|
|
|
299
339
|
| JjStateUpdateMessage
|
|
300
340
|
| SessionNameUpdateMessage
|
|
301
341
|
| ModelsListMessage
|
|
342
|
+
| ProvidersListMessage
|
|
302
343
|
| ModelUpdateMessage
|
|
303
344
|
| SessionsListExtensionMessage
|
|
304
345
|
| ExtensionUiDismissMessage
|
|
@@ -312,7 +353,8 @@ export type ExtensionToServerMessage =
|
|
|
312
353
|
| ProcessListMessage
|
|
313
354
|
| UiModulesListMessage
|
|
314
355
|
| UiDataListMessage
|
|
315
|
-
| ExtUiDecoratorMessage
|
|
356
|
+
| ExtUiDecoratorMessage
|
|
357
|
+
| AssetRegisterMessage;
|
|
316
358
|
|
|
317
359
|
// ── Server → Extension ──────────────────────────────────────────────
|
|
318
360
|
|
|
@@ -357,6 +399,15 @@ export interface RequestModelsMessage {
|
|
|
357
399
|
sessionId: string;
|
|
358
400
|
}
|
|
359
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Server -> bridge: ask the bridge to push a fresh providers_list.
|
|
404
|
+
* See change: replace-hardcoded-provider-lists.
|
|
405
|
+
*/
|
|
406
|
+
export interface RequestProvidersMessage {
|
|
407
|
+
type: "request_providers";
|
|
408
|
+
sessionId: string;
|
|
409
|
+
}
|
|
410
|
+
|
|
360
411
|
export interface SetThinkingLevelMessage {
|
|
361
412
|
type: "set_thinking_level";
|
|
362
413
|
sessionId: string;
|
|
@@ -514,6 +565,7 @@ export type ServerToExtensionMessage =
|
|
|
514
565
|
| ListFilesMessage
|
|
515
566
|
| RenameSessionExtensionMessage
|
|
516
567
|
| RequestModelsMessage
|
|
568
|
+
| RequestProvidersMessage
|
|
517
569
|
| SetThinkingLevelMessage
|
|
518
570
|
| ListSessionsExtensionMessage
|
|
519
571
|
| SetModelMessage
|
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
* points to pi's CLI entry (e.g., pi-coding-agent/dist/cli.js). Since
|
|
6
6
|
* jiti is a dependency of pi-coding-agent, createRequire(process.argv[1])
|
|
7
7
|
* can resolve it directly.
|
|
8
|
+
*
|
|
9
|
+
* Supported jiti providers, in lookup order:
|
|
10
|
+
* 1. `@mariozechner/jiti` — legacy fork shipped with pi ≤ 0.73.0.
|
|
11
|
+
* 2. `@oh-my-pi/jiti` — fork shipped with `@oh-my-pi/pi-coding-agent`.
|
|
12
|
+
* 3. `jiti` — upstream package. Pi 0.73.1+ dropped the
|
|
13
|
+
* fork in favour of upstream jiti 2.7,
|
|
14
|
+
* which ships the same `lib/jiti-register.mjs`
|
|
15
|
+
* layout the helpers below assume.
|
|
16
|
+
*
|
|
17
|
+
* Forks are tried first to preserve behaviour for users on older pi
|
|
18
|
+
* versions; upstream is the fallthrough for pi 0.73.1+. See change:
|
|
19
|
+
* support-upstream-jiti-resolution.
|
|
8
20
|
*/
|
|
9
21
|
|
|
10
22
|
import { createRequire } from "node:module";
|
|
@@ -12,9 +24,11 @@ import { existsSync, realpathSync } from "node:fs";
|
|
|
12
24
|
import path from "node:path";
|
|
13
25
|
import { pathToFileURL } from "node:url";
|
|
14
26
|
|
|
15
|
-
const JITI_PACKAGES = [
|
|
27
|
+
export const JITI_PACKAGES = [
|
|
28
|
+
// @earendil-works/pi-coding-agent depends on plain `jiti`. Try the
|
|
29
|
+
// bare name first; fall back to the legacy namespaced fork.
|
|
30
|
+
"jiti",
|
|
16
31
|
"@mariozechner/jiti",
|
|
17
|
-
"@oh-my-pi/jiti",
|
|
18
32
|
];
|
|
19
33
|
|
|
20
34
|
/**
|
|
@@ -44,6 +58,29 @@ export function buildJitiRegisterUrl(pkgJsonPath: string): string {
|
|
|
44
58
|
return pathToFileURL(registerPath).href;
|
|
45
59
|
}
|
|
46
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Test seam: a function that takes a package specifier (e.g.
|
|
63
|
+
* `"jiti/package.json"`) and returns the resolved path. Production
|
|
64
|
+
* supplies `createRequire(realpath(process.argv[1])).resolve`; tests
|
|
65
|
+
* supply a stub.
|
|
66
|
+
*/
|
|
67
|
+
export type JitiResolver = (specifier: string) => string;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Internal: walk the JITI_PACKAGES list using the given resolver and
|
|
71
|
+
* return the first hit's register URL. Pure function once the
|
|
72
|
+
* resolver is supplied. Returns null when no provider resolves.
|
|
73
|
+
*/
|
|
74
|
+
export function pickJitiRegisterUrl(resolver: JitiResolver): string | null {
|
|
75
|
+
for (const jiti of JITI_PACKAGES) {
|
|
76
|
+
try {
|
|
77
|
+
const pkgJson = resolver(`${jiti}/package.json`);
|
|
78
|
+
return buildJitiRegisterUrl(pkgJson);
|
|
79
|
+
} catch { /* next */ }
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
47
84
|
/**
|
|
48
85
|
* Returns jiti's register hook as a file:// URL suitable for `node --import`.
|
|
49
86
|
* Uses process.argv[1] (pi's entry point) to anchor module resolution.
|
|
@@ -58,18 +95,14 @@ export function resolveJitiImport(): string {
|
|
|
58
95
|
// Resolve symlinks — process.argv[1] may be a symlink (e.g., bin/pi → dist/cli.js)
|
|
59
96
|
const resolved = realpathSync(anchor);
|
|
60
97
|
const req = createRequire(resolved);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const pkgJson = req.resolve(`${jiti}/package.json`);
|
|
64
|
-
return buildJitiRegisterUrl(pkgJson);
|
|
65
|
-
} catch { /* next */ }
|
|
66
|
-
}
|
|
98
|
+
const url = pickJitiRegisterUrl((spec) => req.resolve(spec));
|
|
99
|
+
if (url) return url;
|
|
67
100
|
} catch { /* fall through */ }
|
|
68
101
|
}
|
|
69
102
|
|
|
70
103
|
throw new Error(
|
|
71
104
|
"Cannot find pi's TypeScript loader (jiti). " +
|
|
72
|
-
"Is @
|
|
105
|
+
"Is @earendil-works/pi-coding-agent or @mariozechner/pi-coding-agent installed?"
|
|
73
106
|
);
|
|
74
107
|
}
|
|
75
108
|
|
|
@@ -100,3 +133,23 @@ export function resolveJitiFromAnchor(anchorPath: string): string | null {
|
|
|
100
133
|
} catch { /* ignore */ }
|
|
101
134
|
return null;
|
|
102
135
|
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Test seam for `resolveJitiFromAnchor`. Pure function: given a resolver
|
|
139
|
+
* and a `pathExists` predicate, walk JITI_PACKAGES and return the first
|
|
140
|
+
* hit's register URL. Production wires `createRequire(anchor).resolve`
|
|
141
|
+
* and `existsSync`; tests inject stubs.
|
|
142
|
+
*/
|
|
143
|
+
export function pickJitiFromAnchor(
|
|
144
|
+
resolver: JitiResolver,
|
|
145
|
+
pathExists: (p: string) => boolean,
|
|
146
|
+
): string | null {
|
|
147
|
+
for (const jiti of JITI_PACKAGES) {
|
|
148
|
+
try {
|
|
149
|
+
const pkgJson = resolver(`${jiti}/package.json`);
|
|
150
|
+
const registerPath = path.join(path.dirname(pkgJson), "lib", "jiti-register.mjs");
|
|
151
|
+
if (pathExists(registerPath)) return pathToFileURL(registerPath).href;
|
|
152
|
+
} catch { /* next */ }
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
@@ -255,6 +255,10 @@ export interface ProviderAuthStatus {
|
|
|
255
255
|
authenticated: boolean;
|
|
256
256
|
expires?: number;
|
|
257
257
|
maskedKey?: string;
|
|
258
|
+
/** Name of the env var pi-ai consults for this provider (api-key rows only). */
|
|
259
|
+
envVar?: string;
|
|
260
|
+
/** True when configured via ambient credential chain (AWS profile / GCP ADC). */
|
|
261
|
+
ambient?: boolean;
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
export interface AuthorizeResponse {
|