@danypops/pi-packed 0.28.2 → 0.28.4
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/dist/adoption/smoke.d.ts +9 -0
- package/dist/adoption/smoke.d.ts.map +1 -1
- package/package.json +3 -3
- package/service/src/adoption/doctor.ts +25 -2
- package/service/src/adoption/smoke-child.ts +31 -4
- package/service/src/adoption/smoke.ts +54 -2
- package/service/test/doctor.test.ts +51 -0
- package/service/test/smoke.test.ts +22 -0
package/dist/adoption/smoke.d.ts
CHANGED
|
@@ -7,6 +7,15 @@ export interface SmokeRegistrations {
|
|
|
7
7
|
providers: string[];
|
|
8
8
|
events: string[];
|
|
9
9
|
renderers: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Names, within tools/commands/shortcuts/flags respectively, whose registration call carried an
|
|
12
|
+
* explicit `shared: true` marker -- see @danypops/vehicle-client-pi's own markSharedRegistration.
|
|
13
|
+
* doctor.ts only suppresses a name's conflict once EVERY one of its claimants marked it shared.
|
|
14
|
+
*/
|
|
15
|
+
sharedTools: string[];
|
|
16
|
+
sharedCommands: string[];
|
|
17
|
+
sharedShortcuts: string[];
|
|
18
|
+
sharedFlags: string[];
|
|
10
19
|
}
|
|
11
20
|
export interface ExtensionSmokeResult {
|
|
12
21
|
path: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smoke.d.ts","sourceRoot":"","sources":["../../service/src/adoption/smoke.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,mBAAmB,GAAG,cAAc,GAAG,qBAAqB,CAAC;AAEpH,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"smoke.d.ts","sourceRoot":"","sources":["../../service/src/adoption/smoke.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,mBAAmB,GAAG,cAAc,GAAG,qBAAqB,CAAC;AAEpH,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,kBAAkB,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AA2DD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO7F;AA0LD,wBAAsB,iBAAiB,CACtC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAoF/B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-packed",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.4",
|
|
4
4
|
"description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@danypops/pi-tui-harness": "^0.0.1",
|
|
43
|
-
"@danypops/vehicle-client-pi": "^0.
|
|
43
|
+
"@danypops/vehicle-client-pi": "^0.44.0",
|
|
44
44
|
"@danypops/vehicle-conformance": "^0.4.0",
|
|
45
45
|
"@types/semver": "^7.7.1",
|
|
46
46
|
"follow-redirects": "^1.16.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@danypops/vehicle-client-pi": "^0.
|
|
49
|
+
"@danypops/vehicle-client-pi": "^0.44.0",
|
|
50
50
|
"@earendil-works/pi-coding-agent": "*",
|
|
51
51
|
"@earendil-works/pi-tui": "*",
|
|
52
52
|
"typebox": "*"
|
|
@@ -31,6 +31,18 @@ const CONFLICT_KIND: Record<RegistrationKind, "tool" | "command" | "shortcut" |
|
|
|
31
31
|
shortcuts: "shortcut",
|
|
32
32
|
flags: "flag",
|
|
33
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Maps each RegistrationKind to the smoke result's own parallel "which of these names were marked
|
|
36
|
+
* shared" field (see smoke.ts's own SmokeRegistrations doc comment). A name is only exempted from
|
|
37
|
+
* conflict reporting once EVERY one of its claimants marked it shared -- see runDoctor's own
|
|
38
|
+
* allSharedByName tracking below.
|
|
39
|
+
*/
|
|
40
|
+
const SHARED_REGISTRATION_KIND: Record<RegistrationKind, "sharedTools" | "sharedCommands" | "sharedShortcuts" | "sharedFlags"> = {
|
|
41
|
+
tools: "sharedTools",
|
|
42
|
+
commands: "sharedCommands",
|
|
43
|
+
shortcuts: "sharedShortcuts",
|
|
44
|
+
flags: "sharedFlags",
|
|
45
|
+
};
|
|
34
46
|
|
|
35
47
|
const MAX_EXTENSIONS_SCANNED = 50;
|
|
36
48
|
|
|
@@ -63,6 +75,11 @@ export async function runDoctor(piHome: string, projectRoot?: string, options: S
|
|
|
63
75
|
const bounded = jobs.slice(0, MAX_EXTENSIONS_SCANNED);
|
|
64
76
|
|
|
65
77
|
const claims = new Map<RegistrationKind, Map<string, DoctorClaim[]>>(REGISTRATION_KINDS.map((kind) => [kind, new Map()]));
|
|
78
|
+
// True for a (kind, name) pair only while EVERY claimant seen so far marked that exact
|
|
79
|
+
// registration shared: true -- a single unmarked claimant permanently flips it false, since an
|
|
80
|
+
// unmarked registration is a genuinely ambiguous case, not the coordinated-by-design pattern
|
|
81
|
+
// shared:true exists to exempt.
|
|
82
|
+
const allSharedByName = new Map<RegistrationKind, Map<string, boolean>>(REGISTRATION_KINDS.map((kind) => [kind, new Map()]));
|
|
66
83
|
const extensions: DoctorExtensionResult[] = [];
|
|
67
84
|
let anyNotOk = false;
|
|
68
85
|
|
|
@@ -82,14 +99,20 @@ export async function runDoctor(piHome: string, projectRoot?: string, options: S
|
|
|
82
99
|
const claim: DoctorClaim = { name: job.group.name, source: job.group.source, scope: job.group.scope, extension: job.extension };
|
|
83
100
|
for (const kind of REGISTRATION_KINDS) {
|
|
84
101
|
const byName = claims.get(kind)!;
|
|
85
|
-
|
|
102
|
+
const sharedFlags = allSharedByName.get(kind)!;
|
|
103
|
+
const sharedNamesHere = new Set(result.registrations[SHARED_REGISTRATION_KIND[kind]]);
|
|
104
|
+
for (const name of result.registrations[kind]) {
|
|
105
|
+
byName.set(name, [...(byName.get(name) ?? []), claim]);
|
|
106
|
+
sharedFlags.set(name, (sharedFlags.get(name) ?? true) && sharedNamesHere.has(name));
|
|
107
|
+
}
|
|
86
108
|
}
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
const conflicts: DoctorConflict[] = [];
|
|
90
112
|
for (const kind of REGISTRATION_KINDS) {
|
|
113
|
+
const sharedFlags = allSharedByName.get(kind)!;
|
|
91
114
|
for (const [name, claimants] of claims.get(kind)!) {
|
|
92
|
-
if (claimants.length > 1) conflicts.push({ kind: CONFLICT_KIND[kind], name, claimants });
|
|
115
|
+
if (claimants.length > 1 && !sharedFlags.get(name)) conflicts.push({ kind: CONFLICT_KIND[kind], name, claimants });
|
|
93
116
|
}
|
|
94
117
|
}
|
|
95
118
|
conflicts.sort((a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name));
|
|
@@ -8,6 +8,15 @@ interface Registrations {
|
|
|
8
8
|
providers: string[];
|
|
9
9
|
events: string[];
|
|
10
10
|
renderers: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Names, within tools/commands/shortcuts/flags respectively, whose registration call carried an
|
|
13
|
+
* explicit `shared: true` marker -- see @danypops/vehicle-client-pi's own markSharedRegistration.
|
|
14
|
+
* doctor.ts only suppresses a name's conflict once EVERY one of its claimants marked it shared.
|
|
15
|
+
*/
|
|
16
|
+
sharedTools: string[];
|
|
17
|
+
sharedCommands: string[];
|
|
18
|
+
sharedShortcuts: string[];
|
|
19
|
+
sharedFlags: string[];
|
|
11
20
|
}
|
|
12
21
|
|
|
13
22
|
const MAX_REGISTRATIONS_TOTAL = 100;
|
|
@@ -43,6 +52,10 @@ const registrations: Registrations = {
|
|
|
43
52
|
providers: [],
|
|
44
53
|
events: [],
|
|
45
54
|
renderers: [],
|
|
55
|
+
sharedTools: [],
|
|
56
|
+
sharedCommands: [],
|
|
57
|
+
sharedShortcuts: [],
|
|
58
|
+
sharedFlags: [],
|
|
46
59
|
};
|
|
47
60
|
|
|
48
61
|
function capture(kind: keyof Registrations, value: unknown): void {
|
|
@@ -51,19 +64,33 @@ function capture(kind: keyof Registrations, value: unknown): void {
|
|
|
51
64
|
registrations[kind].push(name.slice(0, MAX_NAME_LENGTH));
|
|
52
65
|
}
|
|
53
66
|
|
|
67
|
+
const SHARED_KIND: Partial<Record<keyof Registrations, keyof Registrations>> = {
|
|
68
|
+
tools: "sharedTools",
|
|
69
|
+
commands: "sharedCommands",
|
|
70
|
+
shortcuts: "sharedShortcuts",
|
|
71
|
+
flags: "sharedFlags",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Captures the plain registration as usual, plus a parallel sharedX entry when the caller's own definition/options object carried an explicit `shared: true` marker. */
|
|
75
|
+
function captureWithSharedMarker(kind: keyof Registrations, value: unknown, sharedMarker: unknown): void {
|
|
76
|
+
capture(kind, value);
|
|
77
|
+
const sharedKind = SHARED_KIND[kind];
|
|
78
|
+
if (sharedKind && sharedMarker === true) capture(sharedKind, value);
|
|
79
|
+
}
|
|
80
|
+
|
|
54
81
|
const api = new Proxy<Record<string, unknown>>(
|
|
55
82
|
{},
|
|
56
83
|
{
|
|
57
84
|
get(_target, property) {
|
|
58
85
|
switch (property) {
|
|
59
86
|
case "registerTool":
|
|
60
|
-
return (definition: { name?: unknown }) =>
|
|
87
|
+
return (definition: { name?: unknown; shared?: unknown }) => captureWithSharedMarker("tools", definition?.name, definition?.shared);
|
|
61
88
|
case "registerCommand":
|
|
62
|
-
return (name: unknown) =>
|
|
89
|
+
return (name: unknown, options?: { shared?: unknown }) => captureWithSharedMarker("commands", name, options?.shared);
|
|
63
90
|
case "registerShortcut":
|
|
64
|
-
return (name: unknown) =>
|
|
91
|
+
return (name: unknown, options?: { shared?: unknown }) => captureWithSharedMarker("shortcuts", name, options?.shared);
|
|
65
92
|
case "registerFlag":
|
|
66
|
-
return (name: unknown) =>
|
|
93
|
+
return (name: unknown, options?: { shared?: unknown }) => captureWithSharedMarker("flags", name, options?.shared);
|
|
67
94
|
case "registerProvider":
|
|
68
95
|
return (name: unknown) => capture("providers", name);
|
|
69
96
|
case "registerMessageRenderer":
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, realpathSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -13,6 +13,15 @@ export interface SmokeRegistrations {
|
|
|
13
13
|
providers: string[];
|
|
14
14
|
events: string[];
|
|
15
15
|
renderers: string[];
|
|
16
|
+
/**
|
|
17
|
+
* Names, within tools/commands/shortcuts/flags respectively, whose registration call carried an
|
|
18
|
+
* explicit `shared: true` marker -- see @danypops/vehicle-client-pi's own markSharedRegistration.
|
|
19
|
+
* doctor.ts only suppresses a name's conflict once EVERY one of its claimants marked it shared.
|
|
20
|
+
*/
|
|
21
|
+
sharedTools: string[];
|
|
22
|
+
sharedCommands: string[];
|
|
23
|
+
sharedShortcuts: string[];
|
|
24
|
+
sharedFlags: string[];
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
export interface ExtensionSmokeResult {
|
|
@@ -43,6 +52,10 @@ const EMPTY_REGISTRATIONS: SmokeRegistrations = {
|
|
|
43
52
|
providers: [],
|
|
44
53
|
events: [],
|
|
45
54
|
renderers: [],
|
|
55
|
+
sharedTools: [],
|
|
56
|
+
sharedCommands: [],
|
|
57
|
+
sharedShortcuts: [],
|
|
58
|
+
sharedFlags: [],
|
|
46
59
|
};
|
|
47
60
|
|
|
48
61
|
function bounded(value: number | undefined, fallback: number, maximum: number): number {
|
|
@@ -105,6 +118,41 @@ export function resolveDependencyModulesDir(fromDir: string, name: string): stri
|
|
|
105
118
|
}
|
|
106
119
|
}
|
|
107
120
|
|
|
121
|
+
const MAX_PROCESS_SCAN_ENTRIES = 100_000;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* RLIMIT_NPROC (prlimit's own --nproc) is a per-real-UID, system-wide limit -- NOT scoped to
|
|
125
|
+
* this sandbox's own process tree. A small absolute constant breaks the instant the invoking
|
|
126
|
+
* user's REAL total process count already exceeds it (routine on a heavily-used workstation
|
|
127
|
+
* with several long-running agent sessions): any further thread/fork Bun's own runtime needs
|
|
128
|
+
* internally (confirmed live: its native TS transpiler spawns one) immediately fails, surfacing
|
|
129
|
+
* as an unrecoverable SIGABRT (exit 134) with no output at all, not a catchable error. Correct
|
|
130
|
+
* enforcement of "at most N NEW processes the sandboxed extension can spawn" requires an offset
|
|
131
|
+
* from the CURRENT real count, not an absolute cap. Returns 0 (never throws) if /proc can't be
|
|
132
|
+
* read at all -- the caller's own maxProcesses then applies as an absolute floor, same as before
|
|
133
|
+
* this existed.
|
|
134
|
+
*/
|
|
135
|
+
function currentProcessCountForUid(uid: number): number {
|
|
136
|
+
let entries: string[];
|
|
137
|
+
try {
|
|
138
|
+
entries = readdirSync("/proc");
|
|
139
|
+
} catch {
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
let count = 0;
|
|
143
|
+
for (const entry of entries.slice(0, MAX_PROCESS_SCAN_ENTRIES)) {
|
|
144
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
145
|
+
try {
|
|
146
|
+
const status = readFileSync(`/proc/${entry}/status`, "utf8");
|
|
147
|
+
const match = status.match(/^Uid:\s+(\d+)/m);
|
|
148
|
+
if (match && Number(match[1]) === uid) count++;
|
|
149
|
+
} catch {
|
|
150
|
+
// The process exited mid-scan, or is otherwise unreadable -- not this sandbox's concern.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return count;
|
|
154
|
+
}
|
|
155
|
+
|
|
108
156
|
function sandboxCommand(packageRoot: string, extensionPath: string, maxProcesses: number): string[] | undefined {
|
|
109
157
|
if (process.platform !== "linux" || !existsSync("/usr/bin/bwrap") || !existsSync("/usr/bin/prlimit")) return undefined;
|
|
110
158
|
const serviceRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
@@ -185,7 +233,7 @@ function sandboxCommand(packageRoot: string, extensionPath: string, maxProcesses
|
|
|
185
233
|
packageRoot,
|
|
186
234
|
"--",
|
|
187
235
|
"/usr/bin/prlimit",
|
|
188
|
-
`--nproc=${maxProcesses}`,
|
|
236
|
+
`--nproc=${(typeof process.getuid === "function" ? currentProcessCountForUid(process.getuid()) : 0) + maxProcesses}`,
|
|
189
237
|
"--nofile=64",
|
|
190
238
|
"--fsize=16777216",
|
|
191
239
|
"--as=8589934592",
|
|
@@ -247,6 +295,10 @@ function normalizeRegistrations(value: unknown): SmokeRegistrations {
|
|
|
247
295
|
providers: names("providers"),
|
|
248
296
|
events: names("events"),
|
|
249
297
|
renderers: names("renderers"),
|
|
298
|
+
sharedTools: names("sharedTools"),
|
|
299
|
+
sharedCommands: names("sharedCommands"),
|
|
300
|
+
sharedShortcuts: names("sharedShortcuts"),
|
|
301
|
+
sharedFlags: names("sharedFlags"),
|
|
250
302
|
};
|
|
251
303
|
}
|
|
252
304
|
|
|
@@ -132,6 +132,57 @@ describeIfSandboxed("runDoctor", () => {
|
|
|
132
132
|
expect(report.conflicts).toEqual([]);
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
it("never flags a conflict when every claimant self-declared the shared name via shared: true -- e.g. registerSharedSecretsCommand's own /secrets command, or Vehicle Shell's tools_list/tools_man/tools_type, both of which every claimant's own smoke scan sees as 'the first (and only) one' since each runs in its own isolated sandbox", async () => {
|
|
136
|
+
const home = piHome(["npm:pi-a", "npm:pi-b"]);
|
|
137
|
+
installNpmPackage(
|
|
138
|
+
home,
|
|
139
|
+
"pi-a",
|
|
140
|
+
{ extensions: ["extension/index.ts"] },
|
|
141
|
+
{
|
|
142
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerCommand("secrets", { handler() {}, shared: true }); }',
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
installNpmPackage(
|
|
146
|
+
home,
|
|
147
|
+
"pi-b",
|
|
148
|
+
{ extensions: ["extension/index.ts"] },
|
|
149
|
+
{
|
|
150
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerCommand("secrets", { handler() {}, shared: true }); }',
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const report = await runDoctor(home);
|
|
155
|
+
|
|
156
|
+
expect(report.ok).toBe(true);
|
|
157
|
+
expect(report.conflicts).toEqual([]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("still flags a conflict when only SOME claimants self-declared the name shared -- an unmarked claimant is a genuinely ambiguous case, not the coordinated-by-design pattern shared: true exists to exempt", async () => {
|
|
161
|
+
const home = piHome(["npm:pi-a", "npm:pi-b"]);
|
|
162
|
+
installNpmPackage(
|
|
163
|
+
home,
|
|
164
|
+
"pi-a",
|
|
165
|
+
{ extensions: ["extension/index.ts"] },
|
|
166
|
+
{
|
|
167
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerCommand("secrets", { handler() {}, shared: true }); }',
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
installNpmPackage(
|
|
171
|
+
home,
|
|
172
|
+
"pi-b",
|
|
173
|
+
{ extensions: ["extension/index.ts"] },
|
|
174
|
+
{
|
|
175
|
+
"extension/index.ts": 'export default function (pi: any) { pi.registerCommand("secrets", { handler() {} }); }',
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const report = await runDoctor(home);
|
|
180
|
+
|
|
181
|
+
expect(report.ok).toBe(false);
|
|
182
|
+
expect(report.conflicts).toHaveLength(1);
|
|
183
|
+
expect(report.conflicts[0]).toMatchObject({ kind: "command", name: "secrets" });
|
|
184
|
+
});
|
|
185
|
+
|
|
135
186
|
it("surfaces a genuinely crashing extension as a non-ok result without treating it as a conflict", async () => {
|
|
136
187
|
const home = piHome(["npm:pi-broken"]);
|
|
137
188
|
installNpmPackage(
|
|
@@ -112,6 +112,10 @@ describeIfSandboxed("isolated extension smoke runner", () => {
|
|
|
112
112
|
commands: ["hello"],
|
|
113
113
|
shortcuts: ["ctrl+h"],
|
|
114
114
|
flags: ["hello"],
|
|
115
|
+
sharedTools: [],
|
|
116
|
+
sharedCommands: [],
|
|
117
|
+
sharedShortcuts: [],
|
|
118
|
+
sharedFlags: [],
|
|
115
119
|
providers: ["local"],
|
|
116
120
|
events: ["session_start"],
|
|
117
121
|
renderers: [],
|
|
@@ -213,6 +217,24 @@ describeIfSandboxed("isolated extension smoke runner", () => {
|
|
|
213
217
|
expect(result.registrations.commands).toEqual(["object"]);
|
|
214
218
|
});
|
|
215
219
|
|
|
220
|
+
it("never breaks under a real per-UID process count that already exceeds the sandbox's own maxProcesses budget -- confirmed live: prlimit's --nproc is a per-real-UID, system-wide limit, not scoped to this sandbox's own process tree, so a small absolute cap unconditionally breaks the instant the invoking user's REAL total process count exceeds it (routine on a busy multi-session workstation) -- any further thread Bun's own runtime needs internally (its native TS transpiler spawns one) immediately fails, aborting with SIGABRT and zero output, not a catchable error", async () => {
|
|
221
|
+
const dummyProcessCount = 40;
|
|
222
|
+
const dummies = Array.from({ length: dummyProcessCount }, () => Bun.spawn(["/usr/bin/sleep", "20"], { stdin: "ignore", stdout: "ignore", stderr: "ignore" }));
|
|
223
|
+
try {
|
|
224
|
+
const fixture = extension(`export default function (pi: any) {
|
|
225
|
+
pi.registerCommand("hello", { handler() {} });
|
|
226
|
+
}`);
|
|
227
|
+
|
|
228
|
+
const result = await runExtensionSmoke(fixture.root, fixture.path, { maxProcesses: 5 });
|
|
229
|
+
|
|
230
|
+
expect(result.status).toBe("ok");
|
|
231
|
+
expect(result.registrations.commands).toEqual(["hello"]);
|
|
232
|
+
} finally {
|
|
233
|
+
for (const dummy of dummies) dummy.kill("SIGKILL");
|
|
234
|
+
await Promise.all(dummies.map((dummy) => dummy.exited));
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
216
238
|
it("keeps default package checks static and adds smoke results only when requested", async () => {
|
|
217
239
|
const fixture = extension(
|
|
218
240
|
'import { writeFileSync } from "node:fs"; export default function (pi: any) { writeFileSync("executed", "yes"); pi.registerCommand("x", { handler() {} }); }',
|