@blackbelt-technology/pi-agent-dashboard 0.4.0 → 0.4.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 +30 -8
- package/README.md +386 -494
- package/docs/architecture.md +63 -9
- package/package.json +8 -5
- package/packages/extension/package.json +6 -4
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +40 -8
- package/packages/extension/src/__tests__/bridge-entry-id-pi-070.test.ts +174 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +30 -0
- package/packages/extension/src/__tests__/fork-entryid-timing.test.ts +64 -76
- package/packages/extension/src/__tests__/multiselect-list.test.ts +137 -0
- package/packages/extension/src/__tests__/no-session-replacement-calls.test.ts +99 -0
- package/packages/extension/src/ask-user-tool.ts +5 -4
- package/packages/extension/src/bridge.ts +102 -15
- package/packages/extension/src/multiselect-list.ts +146 -0
- package/packages/extension/src/multiselect-polyfill.ts +43 -0
- package/packages/extension/src/server-launcher.ts +15 -3
- package/packages/server/package.json +5 -5
- package/packages/server/src/__tests__/fixtures/fork-jsonl-roundtrip.jsonl +8 -0
- package/packages/server/src/__tests__/fork-jsonl-roundtrip.test.ts +49 -0
- package/packages/server/src/__tests__/pi-version-skew.test.ts +72 -0
- package/packages/server/src/__tests__/restart-helper.test.ts +34 -6
- package/packages/server/src/cli.ts +56 -9
- package/packages/server/src/pi-version-skew.ts +12 -1
- package/packages/server/src/restart-helper.ts +13 -2
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/no-hardcoded-node-modules-paths.test.ts +176 -0
- package/packages/shared/src/__tests__/no-raw-node-import.test.ts +146 -0
- package/packages/shared/src/__tests__/node-spawn.test.ts +210 -0
- package/packages/shared/src/__tests__/resolve-tool-cli.test.ts +105 -0
- package/packages/shared/src/__tests__/state-replay-entry-id.test.ts +69 -0
- package/packages/shared/src/platform/index.ts +1 -0
- package/packages/shared/src/platform/node-spawn.ts +154 -0
- package/packages/shared/src/protocol.ts +23 -0
- package/packages/shared/src/state-replay.ts +9 -0
- package/packages/shared/src/tool-registry/definitions.ts +92 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Round-trip test for state-replay (per change: fix-per-message-fork):
|
|
3
|
+
* for every persisted entry, the reducer-equivalent message_start /
|
|
4
|
+
* message_end carries entryId === entry.id. Replay does NOT need
|
|
5
|
+
* entry_persisted back-fill because it reads from the persisted JSONL.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from "vitest";
|
|
8
|
+
import { replayEntriesAsEvents } from "../state-replay.js";
|
|
9
|
+
|
|
10
|
+
describe("replayEntriesAsEvents — entryId fidelity", () => {
|
|
11
|
+
it("stamps entryId on user message_start matching the source entry id", () => {
|
|
12
|
+
const sessionId = "sess-1";
|
|
13
|
+
const entries = [
|
|
14
|
+
{
|
|
15
|
+
type: "message",
|
|
16
|
+
id: "u1",
|
|
17
|
+
parentId: "root",
|
|
18
|
+
timestamp: "2026-04-27T07:26:25.000Z",
|
|
19
|
+
message: { role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const events = replayEntriesAsEvents(sessionId, entries);
|
|
24
|
+
const start = events.find((e) => e.event.eventType === "message_start");
|
|
25
|
+
expect(start).toBeDefined();
|
|
26
|
+
expect((start!.event.data as any).entryId).toBe("u1");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("stamps entryId on assistant message_end matching the source entry id", () => {
|
|
30
|
+
const sessionId = "sess-1";
|
|
31
|
+
const entries = [
|
|
32
|
+
{
|
|
33
|
+
type: "message",
|
|
34
|
+
id: "a1",
|
|
35
|
+
parentId: "u1",
|
|
36
|
+
timestamp: "2026-04-27T07:26:30.000Z",
|
|
37
|
+
message: { role: "assistant", content: [{ type: "text", text: "Hi!" }] },
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const events = replayEntriesAsEvents(sessionId, entries);
|
|
42
|
+
const end = events.find((e) => e.event.eventType === "message_end");
|
|
43
|
+
expect(end).toBeDefined();
|
|
44
|
+
expect((end!.event.data as any).entryId).toBe("a1");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("emits no entry_persisted events during replay", () => {
|
|
48
|
+
const sessionId = "sess-1";
|
|
49
|
+
const entries = [
|
|
50
|
+
{
|
|
51
|
+
type: "message",
|
|
52
|
+
id: "u1",
|
|
53
|
+
timestamp: "2026-04-27T07:26:25.000Z",
|
|
54
|
+
message: { role: "user", content: [{ type: "text", text: "Hi" }] },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: "message",
|
|
58
|
+
id: "a1",
|
|
59
|
+
parentId: "u1",
|
|
60
|
+
timestamp: "2026-04-27T07:26:30.000Z",
|
|
61
|
+
message: { role: "assistant", content: [{ type: "text", text: "Hello!" }] },
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const events = replayEntriesAsEvents(sessionId, entries);
|
|
66
|
+
const persisted = events.filter((e) => e.event.eventType === "entry_persisted");
|
|
67
|
+
expect(persisted).toHaveLength(0);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -9,6 +9,7 @@ export * from "./detached-spawn.js";
|
|
|
9
9
|
export * from "./spawn-mechanism.js";
|
|
10
10
|
export * from "./process-identify.js";
|
|
11
11
|
export * from "./subprocess-adapter.js";
|
|
12
|
+
export * from "./node-spawn.js";
|
|
12
13
|
export * as git from "./git.js";
|
|
13
14
|
export * as openspec from "./openspec.js";
|
|
14
15
|
export * as npm from "./npm.js";
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical helper for spawning `node --import <loader> <entry>` argv.
|
|
3
|
+
*
|
|
4
|
+
* Node ≥ 20's ESM loader parses BOTH the `--import` loader position AND
|
|
5
|
+
* the entry-script position as URLs. Raw Windows paths like
|
|
6
|
+
* `B:\Dev\foo.ts` URL-parse to scheme `b:`, which is not in the ESM
|
|
7
|
+
* loader's allowlist (file, data, node) → the process crashes with
|
|
8
|
+
* `ERR_UNSUPPORTED_ESM_URL_SCHEME` before any filesystem access.
|
|
9
|
+
*
|
|
10
|
+
* Node's internal drive-letter heuristic catches the common cases
|
|
11
|
+
* (`C:\`, `D:\`) but has known gaps for `A:`, `B:`, and other letters.
|
|
12
|
+
* Rather than relying on the heuristic, we wrap the loader position
|
|
13
|
+
* with `file://` unconditionally.
|
|
14
|
+
*
|
|
15
|
+
* The entry-script position needs a more nuanced rule. Node's default
|
|
16
|
+
* resolver AND jiti's ESM hook both accept `file://` URL entries. But
|
|
17
|
+
* **tsx's ESM hook rejects `file://` URLs as entries** — tsx's resolver
|
|
18
|
+
* treats the entry as a user-typed specifier and attempts bare-import
|
|
19
|
+
* / relative-path resolution, producing `<cwd>/file:/...` errors.
|
|
20
|
+
* Since tsx is used as the jiti fallback on dev machines without pi
|
|
21
|
+
* installed (the most common Linux dev path), we must NOT URL-wrap
|
|
22
|
+
* the entry when the loader is tsx. Detection: the loader path
|
|
23
|
+
* contains `/tsx/` (every tsx install ships its hook under a `tsx/`
|
|
24
|
+
* directory; jiti's hook is under `jiti/`).
|
|
25
|
+
*
|
|
26
|
+
* This module is the canonical chokepoint. The repo-level lint test
|
|
27
|
+
* `no-raw-node-import.test.ts` refuses any other call site that
|
|
28
|
+
* passes a raw path to `--import` / `--loader`.
|
|
29
|
+
*
|
|
30
|
+
* See change: fix-windows-entry-script-url.
|
|
31
|
+
*/
|
|
32
|
+
import path from "node:path";
|
|
33
|
+
import { pathToFileURL } from "node:url";
|
|
34
|
+
import type { SpawnOptions, ChildProcess } from "node:child_process"; // ban:child_process-ok — types only
|
|
35
|
+
import { spawn as execSpawn } from "./exec.js";
|
|
36
|
+
|
|
37
|
+
export interface SpawnNodeScriptOptions {
|
|
38
|
+
/** Path to node.exe / node (raw OS path — binary, not ESM-loaded). */
|
|
39
|
+
nodeBin?: string;
|
|
40
|
+
|
|
41
|
+
/** Path to the script Node will run. Raw path OR file:// URL. */
|
|
42
|
+
entry: string;
|
|
43
|
+
|
|
44
|
+
/** Optional ESM loader for --import. Raw path OR file:// URL. */
|
|
45
|
+
loader?: string;
|
|
46
|
+
|
|
47
|
+
/** Arguments passed to the script (after entry). */
|
|
48
|
+
args?: string[];
|
|
49
|
+
|
|
50
|
+
/** Standard spawn options (cwd, env, stdio, detached, etc.). */
|
|
51
|
+
spawnOptions?: SpawnOptions;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Detect whether a loader (file:// URL or raw path) is tsx.
|
|
56
|
+
*
|
|
57
|
+
* tsx's ESM hook rejects `file://` URLs at the entry-script position,
|
|
58
|
+
* so the caller must pass a raw OS path for the entry when this
|
|
59
|
+
* returns true. jiti and Node's default resolver both accept URL
|
|
60
|
+
* entries.
|
|
61
|
+
*
|
|
62
|
+
* Heuristic: every tsx install places its hook under a `tsx/` package
|
|
63
|
+
* directory (e.g. `.../node_modules/tsx/dist/esm/index.mjs`). The
|
|
64
|
+
* check is tolerant of `file://` URLs, raw POSIX paths, and raw
|
|
65
|
+
* Windows paths with either slash direction.
|
|
66
|
+
*/
|
|
67
|
+
export function isTsxLoader(loader: string | null | undefined): boolean {
|
|
68
|
+
if (!loader) return false;
|
|
69
|
+
// Normalize backslashes so the `/tsx/` probe works on Windows paths.
|
|
70
|
+
const normalized = loader.replace(/\\/g, "/");
|
|
71
|
+
return /\/tsx\//i.test(normalized);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Convert a path-or-url string to a file:// URL.
|
|
76
|
+
*
|
|
77
|
+
* Pure and idempotent. Safe to call on strings that are already
|
|
78
|
+
* file:// URLs — returns them unchanged.
|
|
79
|
+
*
|
|
80
|
+
* Handles Windows-style input (drive letter + backslash) regardless of
|
|
81
|
+
* host OS, so unit tests on Linux/macOS can exercise the Windows path
|
|
82
|
+
* contract. Mirrors the pattern in
|
|
83
|
+
* `packages/shared/src/resolve-jiti.ts::buildJitiRegisterUrl`.
|
|
84
|
+
*/
|
|
85
|
+
export function toFileUrl(pathOrUrl: string): string {
|
|
86
|
+
if (pathOrUrl.startsWith("file:")) return pathOrUrl;
|
|
87
|
+
|
|
88
|
+
const isWindowsStyle = /^[A-Za-z]:[\\/]/.test(pathOrUrl);
|
|
89
|
+
if (isWindowsStyle) {
|
|
90
|
+
// pathToFileURL on POSIX hosts URL-encodes backslashes rather than
|
|
91
|
+
// treating them as separators. Build the URL manually so tests on
|
|
92
|
+
// Linux produce the same result a Windows host would.
|
|
93
|
+
return `file:///${pathOrUrl.replace(/\\/g, "/")}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Use path.resolve to ensure absolute path on the host OS, then
|
|
97
|
+
// let Node's pathToFileURL handle any host-specific quirks.
|
|
98
|
+
const absolute = path.isAbsolute(pathOrUrl) ? pathOrUrl : path.resolve(pathOrUrl);
|
|
99
|
+
return pathToFileURL(absolute).href;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Decide whether the entry-script position needs `file://` URL wrapping.
|
|
104
|
+
*
|
|
105
|
+
* Rule:
|
|
106
|
+
* - tsx loader: always raw path (tsx rejects file:// entries on every OS)
|
|
107
|
+
* - non-tsx (jiti / Node default) on POSIX: raw path
|
|
108
|
+
* (POSIX has no drive-letter / URL-scheme collision; jiti's resolver
|
|
109
|
+
* actively MISBEHAVES when handed `file://` URL entries — it
|
|
110
|
+
* normalises away the triple-slash and then treats `file:/...` as
|
|
111
|
+
* a relative specifier, producing `<cwd>/file:/...` ENOENT errors.)
|
|
112
|
+
* - non-tsx on Windows: file:// URL
|
|
113
|
+
* (Node parses drive letters like `B:` / `A:` as URL schemes in argv
|
|
114
|
+
* before loaders run, throwing ERR_UNSUPPORTED_ESM_URL_SCHEME.
|
|
115
|
+
* Wrapping with `file://` sidesteps the parse.)
|
|
116
|
+
*
|
|
117
|
+
* Keeps a `platform` parameter for testability so unit tests on a POSIX
|
|
118
|
+
* host can exercise the Windows branch without mutating `process.platform`.
|
|
119
|
+
*/
|
|
120
|
+
export function shouldUrlWrapEntry(
|
|
121
|
+
loader: string | null | undefined,
|
|
122
|
+
platform: NodeJS.Platform = process.platform,
|
|
123
|
+
): boolean {
|
|
124
|
+
if (isTsxLoader(loader)) return false;
|
|
125
|
+
return platform === "win32";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Spawn `node` with an optional `--import` loader and a script entry.
|
|
130
|
+
*
|
|
131
|
+
* The loader position is always URL-wrapped (Node's ESM loader
|
|
132
|
+
* requires `file://` on Windows drive letters outside the heuristic).
|
|
133
|
+
*
|
|
134
|
+
* The entry position follows `shouldUrlWrapEntry(loader, platform)` —
|
|
135
|
+
* URL on Windows + non-tsx, raw everywhere else.
|
|
136
|
+
*
|
|
137
|
+
* Delegates actual spawning to `platform/exec.ts::spawn` so the
|
|
138
|
+
* `windowsHide: true` default and other safe-spawn invariants are
|
|
139
|
+
* preserved. Does not import `node:child_process` directly (the type
|
|
140
|
+
* imports above are annotated with the opt-out marker).
|
|
141
|
+
*/
|
|
142
|
+
export function spawnNodeScript(opts: SpawnNodeScriptOptions): ChildProcess {
|
|
143
|
+
const nodeBin = opts.nodeBin ?? process.execPath;
|
|
144
|
+
const wrapEntry = shouldUrlWrapEntry(opts.loader);
|
|
145
|
+
|
|
146
|
+
const argv: string[] = [];
|
|
147
|
+
if (opts.loader) {
|
|
148
|
+
argv.push("--import", toFileUrl(opts.loader));
|
|
149
|
+
}
|
|
150
|
+
argv.push(wrapEntry ? toFileUrl(opts.entry) : opts.entry);
|
|
151
|
+
if (opts.args) argv.push(...opts.args);
|
|
152
|
+
|
|
153
|
+
return execSpawn(nodeBin, argv, opts.spawnOptions ?? {});
|
|
154
|
+
}
|
|
@@ -57,6 +57,29 @@ export interface EventForwardMessage {
|
|
|
57
57
|
event: DashboardEvent;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Conventions on `event_forward` payloads relevant to per-message fork:
|
|
62
|
+
*
|
|
63
|
+
* - `message_start` and `message_end` events MAY carry an optional
|
|
64
|
+
* `data.nonce: string` stamped by the bridge. The reducer carries it
|
|
65
|
+
* onto the resulting ChatMessage so a later `entry_persisted` event
|
|
66
|
+
* can back-fill the entry id.
|
|
67
|
+
* - `entry_persisted` events have shape:
|
|
68
|
+
* {
|
|
69
|
+
* eventType: "entry_persisted",
|
|
70
|
+
* timestamp,
|
|
71
|
+
* data: { type: "entry_persisted", entryId: string, nonce: string }
|
|
72
|
+
* }
|
|
73
|
+
* They are emitted by the bridge after pi calls
|
|
74
|
+
* `sessionManager.appendMessage` and the entry id has been generated.
|
|
75
|
+
* See change: fix-per-message-fork.
|
|
76
|
+
*/
|
|
77
|
+
export interface EntryPersistedEventData {
|
|
78
|
+
type: "entry_persisted";
|
|
79
|
+
entryId: string;
|
|
80
|
+
nonce: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
export interface CommandsListMessage {
|
|
61
84
|
type: "commands_list";
|
|
62
85
|
sessionId: string;
|
|
@@ -13,6 +13,15 @@ import type { EventForwardMessage } from "./protocol.js";
|
|
|
13
13
|
* - message_update + message_end for assistant messages
|
|
14
14
|
* - tool_execution_start / tool_execution_end for tool calls
|
|
15
15
|
* - model_select for model changes
|
|
16
|
+
*
|
|
17
|
+
* NOTE on entryId (per change: fix-per-message-fork):
|
|
18
|
+
* Replay reads from the persisted JSONL, so each entry already has a
|
|
19
|
+
* stable `id`. We attach it directly as `entryId` on both `message_start`
|
|
20
|
+
* (user) and `message_end` (assistant) events. Replay therefore does NOT
|
|
21
|
+
* need to emit an `entry_persisted` follow-up — the back-fill protocol
|
|
22
|
+
* exists to bridge a timing gap that only happens for LIVE pi events on
|
|
23
|
+
* pi 0.69+, where the bridge sees `message_start` before pi has assigned
|
|
24
|
+
* the entry id. Replay has no such gap.
|
|
16
25
|
*/
|
|
17
26
|
export function replayEntriesAsEvents(
|
|
18
27
|
sessionId: string,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
overrideStrategy,
|
|
23
23
|
whereStrategy,
|
|
24
24
|
} from "./strategies.js";
|
|
25
|
+
import type { Strategy } from "./types.js";
|
|
25
26
|
|
|
26
27
|
// ── Classifier ──────────────────────────────────────────────────────────────
|
|
27
28
|
|
|
@@ -66,6 +67,68 @@ function moduleDefWithAliases(
|
|
|
66
67
|
return { name: canonicalName, kind: "module", strategies, classify };
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
// ── Build-time module definitions (electron, node-pty) ────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Bare-import strategy that resolves `<pkg>/package.json` and returns the
|
|
74
|
+
* containing directory. Used for build-time tools whose useful artifact is
|
|
75
|
+
* a sibling file of `package.json` (e.g. `electron/install.js`,
|
|
76
|
+
* `node-pty/prebuilds/`). Mirrors the semantics that build-time consumers
|
|
77
|
+
* (`publish.yml`, `Dockerfile.build`, `scripts/fix-pty-permissions.cjs`)
|
|
78
|
+
* need — see change: register-build-time-tools.
|
|
79
|
+
*
|
|
80
|
+
* `searchPaths` are passed to Node's resolver as the `paths` option,
|
|
81
|
+
* making the lookup work whether the package is hoisted to the repo root
|
|
82
|
+
* or nested under a workspace.
|
|
83
|
+
*/
|
|
84
|
+
function bareImportPackageDirStrategy(
|
|
85
|
+
pkgName: string,
|
|
86
|
+
searchPaths?: readonly string[],
|
|
87
|
+
deps?: StrategyDeps,
|
|
88
|
+
): Strategy {
|
|
89
|
+
const fallbackResolve = (id: string, from: string): string | null => {
|
|
90
|
+
try {
|
|
91
|
+
if (searchPaths && searchPaths.length > 0) {
|
|
92
|
+
const req = createRequire(from) as unknown as {
|
|
93
|
+
resolve(id: string, opts?: { paths?: readonly string[] }): string;
|
|
94
|
+
};
|
|
95
|
+
return req.resolve(id, { paths: searchPaths });
|
|
96
|
+
}
|
|
97
|
+
return createRequire(from).resolve(id);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const resolveModule = deps?.resolveModule ?? fallbackResolve;
|
|
103
|
+
return {
|
|
104
|
+
name: "bare-import",
|
|
105
|
+
run() {
|
|
106
|
+
const pkgJson = resolveModule(`${pkgName}/package.json`, import.meta.url);
|
|
107
|
+
if (!pkgJson) {
|
|
108
|
+
return { ok: false, reason: `cannot resolve ${pkgName}/package.json` };
|
|
109
|
+
}
|
|
110
|
+
return { ok: true, path: path.dirname(pkgJson) };
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Module def that returns the package directory (containing package.json). */
|
|
116
|
+
function packageDirModuleDef(
|
|
117
|
+
toolName: string,
|
|
118
|
+
pkgName: string,
|
|
119
|
+
options: { searchPaths?: readonly string[]; includeManaged?: boolean },
|
|
120
|
+
deps?: StrategyDeps,
|
|
121
|
+
): ToolDefinition {
|
|
122
|
+
const strategies: Strategy[] = [
|
|
123
|
+
overrideStrategy(toolName, deps),
|
|
124
|
+
bareImportPackageDirStrategy(pkgName, options.searchPaths, deps),
|
|
125
|
+
];
|
|
126
|
+
if (options.includeManaged) {
|
|
127
|
+
strategies.push(managedModuleStrategy(pkgName, "package.json", deps));
|
|
128
|
+
}
|
|
129
|
+
return { name: toolName, kind: "module", strategies, classify };
|
|
130
|
+
}
|
|
131
|
+
|
|
69
132
|
// ── Registration ─────────────────────────────────────────────────
|
|
70
133
|
|
|
71
134
|
// Tools intentionally NOT registered:
|
|
@@ -76,6 +139,14 @@ function moduleDefWithAliases(
|
|
|
76
139
|
// - `pi-dashboard` — that's the package this code is part of.
|
|
77
140
|
// "Is it installed" is a bootstrap concern handled directly in
|
|
78
141
|
// `packages/electron/src/lib/dependency-detector.ts`.
|
|
142
|
+
//
|
|
143
|
+
// Build-time tools (see change: register-build-time-tools):
|
|
144
|
+
// - `electron` — module, returns the package directory containing
|
|
145
|
+
// `install.js`. Resolved with paths anchored at
|
|
146
|
+
// `packages/electron` to handle hoisted vs. nested
|
|
147
|
+
// layouts uniformly.
|
|
148
|
+
// - `node-pty` — module, returns the package directory containing
|
|
149
|
+
// `prebuilds/`. Standard module resolution suffices.
|
|
79
150
|
// See change: consolidate-tool-resolution (follow-up).
|
|
80
151
|
|
|
81
152
|
/**
|
|
@@ -333,6 +404,27 @@ export function registerDefaultTools(registry: ToolRegistry, deps?: StrategyDeps
|
|
|
333
404
|
deps,
|
|
334
405
|
),
|
|
335
406
|
);
|
|
407
|
+
|
|
408
|
+
// Build-time tools (see change: register-build-time-tools).
|
|
409
|
+
registry.register(
|
|
410
|
+
packageDirModuleDef(
|
|
411
|
+
"electron",
|
|
412
|
+
"electron",
|
|
413
|
+
{
|
|
414
|
+
searchPaths: [path.resolve("packages/electron")],
|
|
415
|
+
includeManaged: true,
|
|
416
|
+
},
|
|
417
|
+
deps,
|
|
418
|
+
),
|
|
419
|
+
);
|
|
420
|
+
registry.register(
|
|
421
|
+
packageDirModuleDef(
|
|
422
|
+
"node-pty",
|
|
423
|
+
"node-pty",
|
|
424
|
+
{ includeManaged: false },
|
|
425
|
+
deps,
|
|
426
|
+
),
|
|
427
|
+
);
|
|
336
428
|
}
|
|
337
429
|
|
|
338
430
|
/** Handy re-exports for callers that want raw definitions for testing. */
|