@fitchmultz/pi-subagents 0.52.15 → 0.52.17

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/agent/host.js +1 -1
  3. package/dist/agent/notifications.js +10 -8
  4. package/dist/agent/observe-layout.js +3 -3
  5. package/dist/agent/parking.js +0 -1
  6. package/dist/agent/process.js +1 -4
  7. package/dist/agent/runner.js +0 -2
  8. package/dist/agent/shutdown.js +0 -1
  9. package/dist/agent/typebox.js +2 -2
  10. package/dist/build-info.js +1 -1
  11. package/dist/pi-intercom/bridge-presence.js +0 -1
  12. package/dist/pi-intercom/bridge-protocol.js +0 -2
  13. package/dist/pi-intercom/bridge-session.js +0 -1
  14. package/dist/pi-intercom/bridge-wake.js +0 -1
  15. package/dist/pi-intercom/broker/client.js +0 -2
  16. package/dist/pi-intercom/broker/spawn.js +0 -3
  17. package/dist/pi-intercom/tool-registration.js +1 -2
  18. package/dist/pi-intercom/validation.js +0 -2
  19. package/dist/shared/cooperative.js +0 -1
  20. package/docs/code-quality.md +96 -68
  21. package/package.json +9 -6
  22. package/scripts/build.mjs +11 -13
  23. package/scripts/check-compat.mjs +3 -2
  24. package/scripts/compat-host.mjs +5 -5
  25. package/scripts/intercom-bridge-credentials.mjs +4 -3
  26. package/scripts/intercom-remote.mjs +0 -1
  27. package/scripts/native-package-smoke-assertions.mjs +25 -23
  28. package/scripts/native-package-smoke.mjs +38 -54
  29. package/scripts/package-smoke.mjs +39 -30
  30. package/scripts/prepare.mjs +16 -10
  31. package/scripts/real-pi-events.mjs +92 -0
  32. package/scripts/real-pi-live.mjs +28 -108
  33. package/scripts/real-pi-processes.mjs +77 -82
  34. package/scripts/real-pi-smoke.mjs +20 -18
  35. package/src/agent/host.ts +1 -1
  36. package/src/agent/notifications.ts +11 -8
  37. package/src/agent/observe-layout.ts +3 -3
  38. package/src/agent/parking.ts +0 -1
  39. package/src/agent/process.ts +1 -4
  40. package/src/agent/runner.ts +0 -2
  41. package/src/agent/shutdown.ts +0 -1
  42. package/src/agent/typebox.ts +2 -2
  43. package/src/pi-intercom/bridge-presence.ts +0 -1
  44. package/src/pi-intercom/bridge-protocol.ts +0 -2
  45. package/src/pi-intercom/bridge-session.ts +0 -1
  46. package/src/pi-intercom/bridge-wake.ts +0 -1
  47. package/src/pi-intercom/broker/client.ts +0 -2
  48. package/src/pi-intercom/broker/spawn.ts +0 -3
  49. package/src/pi-intercom/tool-registration.ts +1 -2
  50. package/src/pi-intercom/validation.ts +0 -2
  51. package/src/shared/cooperative.ts +0 -1
@@ -161,8 +161,7 @@ function issueCertificate(
161
161
  }
162
162
 
163
163
  function readConfig(/** @type {string} */ path) {
164
- /** @type {{ ca?: string, clients?: unknown[], cert?: string, key?: string }} */
165
- // oxlint-disable-next-line typescript/no-unsafe-assignment
164
+ /** @type {unknown} */
166
165
  const config = JSON.parse(readFileSync(path, "utf8"));
167
166
  if (!isRecord(config) || !Array.isArray(config.clients) || typeof config.ca !== "string") {
168
167
  throw new Error("Invalid clients allowlist.");
@@ -178,7 +177,9 @@ function readConfig(/** @type {string} */ path) {
178
177
  }
179
178
  return { ...client, name: client.name, cwd: client.cwd, fingerprint256: client.fingerprint256 };
180
179
  });
181
- return { ...config, ca: config.ca, clients };
180
+ /** @type {Record<string, unknown> & { ca: string, clients: typeof clients }} */
181
+ const validated = { ...config, ca: config.ca, clients };
182
+ return validated;
182
183
  }
183
184
 
184
185
  let args;
@@ -32,7 +32,6 @@ const actions = {
32
32
  send: ["to", "message", "id", "passive", "expects-reply", "reply-to"],
33
33
  };
34
34
  function messageId(/** @type {string | undefined} */ id) {
35
- // oxlint-disable-next-line no-control-regex
36
35
  const controls = /[\u0000-\u001f\u007f-\u009f]/;
37
36
  if (id !== undefined && (id.trim() === "" || id.length > 512 || controls.test(id))) {
38
37
  throw new Error("Invalid message ID.");
@@ -1,6 +1,22 @@
1
1
  // Journal and observation assertions shared with the packed-native smoke script.
2
2
  import assert from "node:assert/strict";
3
3
  import { readFileSync } from "node:fs";
4
+ import { isRecord, isUnknownArray } from "../src/shared/unknown.ts";
5
+
6
+ function jsonRecords(/** @type {string} */ file) {
7
+ return readFileSync(file, "utf8")
8
+ .trimEnd()
9
+ .split("\n")
10
+ .map((line) => {
11
+ /** @type {unknown} */ const entry = JSON.parse(line);
12
+ assert.ok(isRecord(entry));
13
+ return entry;
14
+ });
15
+ }
16
+
17
+ function containsText(/** @type {unknown} */ content, /** @type {string} */ text) {
18
+ return isUnknownArray(content) && content.some((part) => isRecord(part) && part.text === text);
19
+ }
4
20
 
5
21
  /**
6
22
  * Assert the child journal records its native tool result and completion entry.
@@ -9,23 +25,15 @@ import { readFileSync } from "node:fs";
9
25
  * @param {string} completionText
10
26
  */
11
27
  export function assertChildJournal(sessionFile, terminalEntryId, completionText) {
12
- const childEntries = readFileSync(sessionFile, "utf8")
13
- .trimEnd()
14
- .split("\n")
15
- .map((line) =>
16
- /** @type {{ id?: string, type?: string, message?: { role?: string, toolName?: string, content?: { text?: string }[] } }} */ (
17
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- journal line shape, guarded below
18
- JSON.parse(line)
19
- ),
20
- );
21
- // oxlint-disable typescript/no-unnecessary-condition -- journal fields are optional under tsc
28
+ const childEntries = jsonRecords(sessionFile);
22
29
  assert.ok(
23
30
  childEntries.some(
24
31
  (entry) =>
25
32
  entry.type === "message" &&
26
- entry.message?.role === "toolResult" &&
27
- entry.message?.toolName === "packed_probe" &&
28
- entry.message?.content?.some((part) => part.text === "PACKED_TOOL_RESULT") === true,
33
+ isRecord(entry.message) &&
34
+ entry.message.role === "toolResult" &&
35
+ entry.message.toolName === "packed_probe" &&
36
+ containsText(entry.message.content, "PACKED_TOOL_RESULT"),
29
37
  ),
30
38
  "actual child journal contains its native tool result",
31
39
  );
@@ -34,12 +42,12 @@ export function assertChildJournal(sessionFile, terminalEntryId, completionText)
34
42
  (entry) =>
35
43
  entry.id === terminalEntryId &&
36
44
  entry.type === "message" &&
37
- entry.message?.role === "assistant" &&
38
- entry.message?.content?.some((part) => part.text === completionText) === true,
45
+ isRecord(entry.message) &&
46
+ entry.message.role === "assistant" &&
47
+ containsText(entry.message.content, completionText),
39
48
  ),
40
49
  "completion references an actually published child assistant entry",
41
50
  );
42
- // oxlint-enable typescript/no-unnecessary-condition
43
51
  }
44
52
 
45
53
  /**
@@ -47,13 +55,7 @@ export function assertChildJournal(sessionFile, terminalEntryId, completionText)
47
55
  * @param {string} observations
48
56
  */
49
57
  export function assertObservations(observations) {
50
- /** @type {Record<string, unknown>[]} */
51
- // oxlint-disable-next-line typescript/no-unsafe-assignment
52
- const observed = readFileSync(observations, "utf8")
53
- .trimEnd()
54
- .split("\n")
55
- // oxlint-disable-next-line typescript/no-unsafe-return
56
- .map((line) => JSON.parse(line));
58
+ const observed = jsonRecords(observations);
57
59
  const matches = (/** @type {(item: Record<string, unknown>) => boolean} */ predicate) =>
58
60
  observed.filter(predicate).length;
59
61
  assert.equal(
@@ -19,9 +19,10 @@ import { createRequire } from "node:module";
19
19
  import { hostCli, hostIndex, hostRoot } from "./compat-host.mjs";
20
20
  import { SmokeProcesses } from "./real-pi-processes.mjs";
21
21
  import { assertChildJournal, assertObservations } from "./native-package-smoke-assertions.mjs";
22
+ import { isRecord, isUnknownArray } from "../src/shared/unknown.ts";
23
+ import { isContextTarget } from "../src/pi-intercom/types.ts";
22
24
 
23
25
  /** @type {(file: string, args: readonly string[], options: import("node:child_process").ExecFileOptions) => Promise<{ stdout: string, stderr: string }>} */
24
- // oxlint-disable-next-line typescript/strict-void-return
25
26
  const execFile = promisify(execFileCallback);
26
27
 
27
28
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
@@ -145,20 +146,9 @@ writeFileSync(
145
146
  retry: { enabled: false },
146
147
  }),
147
148
  );
148
- /** @type {typeof import("@earendil-works/pi-coding-agent")} */
149
- // oxlint-disable-next-line typescript/no-unsafe-assignment
150
- const sdk = await import(pathToFileURL(hostIndex).href);
151
- /** @type {typeof import("@earendil-works/pi-ai")} */
152
- // oxlint-disable-next-line typescript/no-unsafe-assignment
153
- const ai = await import(import.meta.resolve("@earendil-works/pi-ai"));
154
- /**
155
- * The intercom context snapshot the consumer fixture answers with.
156
- * @typedef {object} IntercomContextSnapshot
157
- * @property {boolean | undefined} connected
158
- * @property {string | undefined} peerId
159
- * @property {string | undefined} rootSessionId
160
- * @property {number | undefined} incarnation
161
- */
149
+ // compat-host verifies this specifier resolves to the selected native graph.
150
+ const sdk = await import("@earendil-works/pi-coding-agent");
151
+ const ai = await import("@earendil-works/pi-ai");
162
152
  const selected = ai.fauxProvider({ provider: "packed-probe", models: [{ id: "native" }] });
163
153
  const processes = new SmokeProcesses({
164
154
  cwd: root,
@@ -191,7 +181,6 @@ try {
191
181
  while (!existsSync(join(runtimeDir, "broker.pid"))) {
192
182
  assert.ok(brokerError === undefined && Date.now() < deadline, brokerReadinessMessage());
193
183
  // Observe the broker's publication, not a sleep-based startup assumption.
194
- // oxlint-disable-next-line no-await-in-loop
195
184
  await delay(25);
196
185
  }
197
186
  const settingsManager = sdk.SettingsManager.create(root, agentDir, { projectTrusted: true });
@@ -249,7 +238,6 @@ try {
249
238
  },
250
239
  });
251
240
  assert.deepEqual(
252
- // oxlint-disable-next-line typescript/no-unnecessary-condition -- assert generic inference already matches
253
241
  [...session.getActiveToolNames()].sort(),
254
242
  expectedTools,
255
243
  "exactly three eager package tools",
@@ -263,9 +251,8 @@ try {
263
251
  const status = await execute("intercom", { action: "status" });
264
252
  assert.match(JSON.stringify(status.content), /Connected: Yes/);
265
253
  const runs = await execute("agent_runs", { action: "list" });
266
- /** @type {{ total: number, runs: unknown[] }} */
267
- // oxlint-disable-next-line typescript/no-unsafe-assignment
268
- const listDetails = runs.details;
254
+ /** @type {unknown} */ const listDetails = runs.details;
255
+ assert.ok(isRecord(listDetails));
269
256
  assert.equal(listDetails.total, 0);
270
257
  assert.equal(session.model?.provider, "packed-probe");
271
258
  assert.equal(session.model.id, "native");
@@ -274,9 +261,10 @@ try {
274
261
  events.push(event);
275
262
  });
276
263
  watchdog = setTimeout(() => {
277
- // Fire-and-forget by design: the watchdog only interrupts a stuck turn.
278
- // oxlint-disable-next-line typescript/no-floating-promises
279
- void session?.abort();
264
+ // Interrupt a stuck turn, retaining abort failures in the smoke's error evidence.
265
+ session?.abort().catch((/** @type {unknown} */ error) => {
266
+ errors.push(error);
267
+ });
280
268
  }, 30_000);
281
269
  await session.prompt("Exercise the packed native delegate.", { expandPromptTemplates: false });
282
270
  const relevantEvents = () =>
@@ -299,7 +287,6 @@ try {
299
287
  Date.now() < completionDeadline,
300
288
  `native completion notification resumes the parent: ${JSON.stringify(session.messages.slice(-3))}`,
301
289
  );
302
- // oxlint-disable-next-line no-await-in-loop
303
290
  await delay(25);
304
291
  }
305
292
  await session.waitForIdle();
@@ -313,20 +300,24 @@ try {
313
300
  (event) => event.type === "tool_execution_start" && event.toolName === "delegate",
314
301
  );
315
302
  assert.equal(starts.length, 1, "one real native delegate call, no fabricated wire events");
316
- const receipt =
317
- /** @type {{ isError?: boolean, result?: { details?: { receipt?: string, id?: string } } } | undefined} */ (
318
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- the packed event shape is not exported by the SDK
319
- events.find((event) => event.type === "tool_execution_end" && event.toolName === "delegate")
320
- );
321
- assert.ok(receipt !== undefined && receipt.isError !== true, JSON.stringify(receipt));
322
- assert.equal(receipt.result?.details?.receipt, "launch");
323
- /** @type {{ status: string, output: { text: string }, sessionFile: string, result?: { terminalEntryId?: string } }} */
324
- // oxlint-disable-next-line typescript/no-unnecessary-condition, typescript/no-unsafe-assignment -- cast-type optionality is not modelled by the checker
325
- const run = (await execute("agent_runs", { action: "inspect", id: receipt.result?.details?.id }))
303
+ const receipt = events.find(
304
+ (event) => event.type === "tool_execution_end" && event.toolName === "delegate",
305
+ );
306
+ assert.ok(receipt?.type === "tool_execution_end" && !receipt.isError, JSON.stringify(receipt));
307
+ /** @type {unknown} */ const launchResult = receipt.result;
308
+ assert.ok(isRecord(launchResult) && isRecord(launchResult.details));
309
+ assert.equal(launchResult.details.receipt, "launch");
310
+ assert.ok(typeof launchResult.details.id === "string");
311
+ /** @type {unknown} */
312
+ const run = (await execute("agent_runs", { action: "inspect", id: launchResult.details.id }))
326
313
  .details;
314
+ assert.ok(isRecord(run) && isRecord(run.output) && typeof run.sessionFile === "string");
327
315
  assert.equal(run.status, "completed");
328
316
  assert.equal(run.output.text, "PACKED_NATIVE_COMPLETE");
329
- assertChildJournal(run.sessionFile, run.result?.terminalEntryId, "PACKED_NATIVE_COMPLETE");
317
+ assert.ok(run.result === undefined || isRecord(run.result));
318
+ const terminalEntryId = run.result?.terminalEntryId;
319
+ assert.ok(terminalEntryId === undefined || typeof terminalEntryId === "string");
320
+ assertChildJournal(run.sessionFile, terminalEntryId, "PACKED_NATIVE_COMPLETE");
330
321
  assertObservations(observations);
331
322
  assert.equal(networkAttempts, 0);
332
323
  assert.deepEqual(errors, []);
@@ -334,35 +325,30 @@ try {
334
325
  if (consumerScript !== undefined) {
335
326
  assert.ok(intercomEvents !== undefined, "expected the consumer observer extension");
336
327
  assert.ok(consumerDir !== undefined, "expected the consumer script directory argument");
337
- /** @type {IntercomContextSnapshot | undefined} */ let target;
328
+ /** @type {unknown} */ let target;
338
329
  intercomEvents.emit("pi-intercom:context-query", {
339
330
  version: 1,
340
- respond: (/** @type {IntercomContextSnapshot} */ snapshot) => {
331
+ respond: (/** @type {unknown} */ snapshot) => {
341
332
  target = snapshot;
342
333
  },
343
334
  });
344
- assert.ok(
345
- target?.connected === true &&
346
- target.peerId !== undefined &&
347
- target.rootSessionId !== undefined &&
348
- target.incarnation !== undefined,
349
- );
335
+ assert.ok(isRecord(target) && target.connected === true && isContextTarget(target));
350
336
  const guard = {
351
337
  rootSessionId: target.rootSessionId,
352
338
  peerId: target.peerId,
353
339
  incarnation: target.incarnation,
354
340
  applicationGuard: "package-consent-1",
355
341
  };
356
- /** @type {{ applicationGuard?: string } | undefined} */ let consent;
342
+ /** @type {unknown} */ let consent;
357
343
  intercomEvents.emit("pi-intercom:application-guard", {
358
344
  version: 1,
359
345
  target: guard,
360
346
  revision: guard.applicationGuard,
361
- respond: (/** @type {{ applicationGuard?: string }} */ result) => {
347
+ respond: (/** @type {unknown} */ result) => {
362
348
  consent = result;
363
349
  },
364
350
  });
365
- assert.ok(consent !== undefined, "the consumer answered the guard query");
351
+ assert.ok(isRecord(consent), "the consumer answered the guard query");
366
352
  assert.equal(consent.applicationGuard, guard.applicationGuard);
367
353
  const consumerHome = join(root, "consumer-home");
368
354
  mkdirSync(consumerHome);
@@ -376,7 +362,7 @@ try {
376
362
  PI_INTERCOM_RUNTIME_DIR: runtimeDir,
377
363
  S3_ROOT: guard.rootSessionId,
378
364
  S3_PEER: guard.peerId,
379
- S3_INCARNATION: String(guard.incarnation),
365
+ S3_INCARNATION: guard.incarnation,
380
366
  S3_GUARD: guard.applicationGuard,
381
367
  S3_BROKER_PID: String(brokerPid),
382
368
  },
@@ -391,7 +377,7 @@ try {
391
377
  "--permission",
392
378
  "--allow-fs-read=*",
393
379
  "--allow-fs-write=*",
394
- "--input-type=module",
380
+ "--input-type=module-typescript",
395
381
  "-e",
396
382
  readFileSync(consumerScript, "utf8"),
397
383
  ],
@@ -399,7 +385,6 @@ try {
399
385
  );
400
386
  process.stdout.write(stdout);
401
387
  process.stderr.write(stderr);
402
- // oxlint-disable-next-line typescript/no-unnecessary-condition -- assert generic inference already matches
403
388
  assert.equal(events.filter((event) => event.type === "turn_start").length, turns);
404
389
  } finally {
405
390
  // The consumer's failure/timeout must never leave this fixture's broker stopped.
@@ -472,11 +457,10 @@ export default function(pi) {
472
457
  "",
473
458
  );
474
459
  assert.doesNotMatch(output, /Failed to load extension|ERR_INTERNAL_ASSERTION|Extension error/);
475
- /** @type {{ tools: string[], sdkReused: boolean }} */
476
- // oxlint-disable-next-line typescript/no-unsafe-assignment
477
- const cli = JSON.parse(readFileSync(marker, "utf8"));
478
- // oxlint-disable-next-line typescript/no-unnecessary-condition -- assert generic inference already matches
479
- assert.deepEqual(cli.tools.sort(), expectedTools);
460
+ /** @type {unknown} */ const cli = JSON.parse(readFileSync(marker, "utf8"));
461
+ assert.ok(isRecord(cli) && isUnknownArray(cli.tools));
462
+ assert.ok(cli.tools.every((tool) => typeof tool === "string"));
463
+ assert.deepEqual([...cli.tools].sort(), expectedTools);
480
464
  if (!existsSync(join(packageRoot, "node_modules/@earendil-works/pi-coding-agent"))) {
481
465
  assert.equal(cli.sdkReused, true, "production-only installs reuse Pi's loaded SDK");
482
466
  }
@@ -16,6 +16,9 @@ import { dirname, join, resolve } from "node:path";
16
16
  import { fileURLToPath, pathToFileURL } from "node:url";
17
17
  import { hostRoot } from "./compat-host.mjs";
18
18
  import { compilerInvocation } from "./typescript.mjs";
19
+ import { isRecord, isUnknownArray } from "../src/shared/unknown.ts";
20
+ import { Type } from "typebox";
21
+ import { Assert } from "typebox/value";
19
22
 
20
23
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
21
24
  console.log(
@@ -25,9 +28,11 @@ if (process.argv.includes("--help") || process.argv.includes("-h")) {
25
28
  }
26
29
  assert.ok(process.argv.length <= 3, "Expected at most one package tarball; use --help");
27
30
 
28
- /** @type {{ name: string, version: string, pi: { extensions?: string[], skills?: string[], agents?: unknown, prompts?: unknown } }} */
29
- // oxlint-disable-next-line typescript/no-unsafe-assignment
31
+ /** @type {unknown} */
30
32
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
33
+ assert.ok(isRecord(packageJson));
34
+ assert.ok(typeof packageJson.name === "string" && typeof packageJson.version === "string");
35
+ const packageName = packageJson.name;
31
36
  const candidate = process.argv[2] !== undefined ? resolve(process.argv[2]) : undefined;
32
37
  const root = mkdtempSync(join(tmpdir(), "ps-package-"));
33
38
  const packDir = join(root, "pack");
@@ -55,9 +60,8 @@ function run(
55
60
  return result.stdout;
56
61
  }
57
62
 
58
- function verifyManifest(
59
- /** @type {{ name?: string, private?: boolean, publishConfig?: unknown, bin?: unknown, pi: { extensions?: string[], skills?: string[], agents?: unknown, prompts?: unknown } }} */ manifest,
60
- ) {
63
+ function verifyManifest(/** @type {unknown} */ manifest) {
64
+ assert.ok(isRecord(manifest) && isRecord(manifest.pi));
61
65
  assert.equal(manifest.name, "@fitchmultz/pi-subagents");
62
66
  assert.notEqual(manifest.private, true);
63
67
  assert.deepEqual(manifest.publishConfig, { access: "public" });
@@ -87,7 +91,7 @@ function installProduction(/** @type {{ filename?: string, files?: { path: strin
87
91
  ],
88
92
  installDir,
89
93
  );
90
- const installed = join(installDir, "node_modules", packageJson.name);
94
+ const installed = join(installDir, "node_modules", packageName);
91
95
  const standalone = join(root, "standalone");
92
96
  cpSync(installed, standalone, { recursive: true });
93
97
  run("npm", ["install", "--ignore-scripts", "--omit=dev", "--legacy-peer-deps"], standalone);
@@ -143,16 +147,14 @@ function verifyDeclarations(/** @type {string} */ consumerDir) {
143
147
  async function verifyRuntime(
144
148
  /** @type {{ packageRoot: string, consumerDir: string }} */ { packageRoot, consumerDir },
145
149
  ) {
146
- /** @type {{ name?: string, private?: boolean, publishConfig?: unknown, bin?: unknown, pi: { extensions?: string[], skills?: string[], agents?: unknown, prompts?: unknown } }} */
147
- // oxlint-disable-next-line typescript/no-unsafe-assignment
150
+ /** @type {unknown} */
148
151
  const manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
149
152
  verifyManifest(manifest);
150
153
  verifyDeclarations(consumerDir);
151
- /** @type {typeof import("@earendil-works/pi-coding-agent")} */
152
- // oxlint-disable-next-line typescript/no-unsafe-assignment
153
- const sdk = await import(pathToFileURL(join(hostRoot, "dist/index.js")).href);
154
+ // compat-host verifies this specifier resolves to the selected native graph.
155
+ const sdk = await import("@earendil-works/pi-coding-agent");
154
156
  /** @type {typeof import("../src/agent/host.ts")} */
155
- // oxlint-disable-next-line typescript/no-unsafe-assignment
157
+ // oxlint-disable-next-line typescript/no-unsafe-assignment -- QX-004: load the selected runtime artifact with its authoritative source contract.
156
158
  const nativeHost = await import(pathToFileURL(join(packageRoot, "dist/agent/host.js")).href);
157
159
  assert.equal(
158
160
  await nativeHost.loadSDK(hostRoot),
@@ -182,11 +184,10 @@ assert.equal(native.Assert, hostValue.Assert, "detached validation uses the sele
182
184
  },
183
185
  );
184
186
  /** @type {typeof import("../src/pi-intercom/broker/spawn.ts")} */
185
- // oxlint-disable-next-line typescript/no-unsafe-assignment
187
+ // oxlint-disable-next-line typescript/no-unsafe-assignment -- QX-004: load the selected runtime artifact with its authoritative source contract.
186
188
  const broker = await import(
187
189
  pathToFileURL(join(packageRoot, "dist/pi-intercom/broker/spawn.js")).href
188
190
  );
189
- // oxlint-disable-next-line typescript/no-unnecessary-condition -- assert generic inference already matches
190
191
  assert.equal(realpathSync(broker.getBrokerSpawnOptions().cwd), realpathSync(packageRoot));
191
192
  const help = run(
192
193
  process.execPath,
@@ -207,7 +208,9 @@ assert.equal(native.Assert, hostValue.Assert, "detached validation uses the sele
207
208
  fileURLToPath(new URL("./native-package-smoke.mjs", import.meta.url)),
208
209
  packageRoot,
209
210
  consumerDir,
210
- fileURLToPath(new URL("../test/fixtures/intercom-client-consumer.mjs", import.meta.url)),
211
+ fileURLToPath(
212
+ new URL("../test/fixtures/intercom-client-consumer.test-d.mts", import.meta.url),
213
+ ),
211
214
  ],
212
215
  root,
213
216
  {
@@ -225,25 +228,32 @@ assert.equal(native.Assert, hostValue.Assert, "detached validation uses the sele
225
228
  try {
226
229
  verifyManifest(packageJson);
227
230
  process.env.PI_PACKAGE_DIR = hostRoot;
228
- /** @type {{ name: string, version: string, filename: string, files: { path: string }[] }[]} */
229
- const packReports = Object.values(
230
- // oxlint-disable-next-line typescript/no-unsafe-argument
231
- JSON.parse(
232
- run("npm", [
233
- "pack",
234
- "--json",
235
- "--pack-destination",
236
- packDir,
237
- ...(candidate !== undefined ? [candidate] : []),
238
- ]),
239
- ),
231
+ const packSchema = Type.Array(
232
+ Type.Object({
233
+ name: Type.String(),
234
+ version: Type.String(),
235
+ filename: Type.String(),
236
+ files: Type.Array(Type.Object({ path: Type.String() })),
237
+ }),
238
+ );
239
+ /** @type {unknown} */
240
+ const report = JSON.parse(
241
+ run("npm", [
242
+ "pack",
243
+ "--json",
244
+ "--pack-destination",
245
+ packDir,
246
+ ...(candidate !== undefined ? [candidate] : []),
247
+ ]),
240
248
  );
249
+ // npm 12 reports by package name; earlier npm versions report an array.
250
+ assert.ok(isRecord(report) || isUnknownArray(report));
251
+ const packReports = Object.values(report);
252
+ Assert(packSchema, packReports);
241
253
  const pack = packReports[0];
242
254
  assert.ok(pack !== undefined && Array.isArray(pack.files), "npm pack must report files");
243
255
  if (candidate !== undefined) {
244
- // oxlint-disable-next-line typescript/no-unnecessary-condition -- assert generic inference already matches
245
256
  assert.equal(pack.name, packageJson.name);
246
- // oxlint-disable-next-line typescript/no-unnecessary-condition -- assert generic inference already matches
247
257
  assert.equal(pack.version, packageJson.version, "Candidate version differs from source");
248
258
  assert.ok(
249
259
  readFileSync(join(packDir, pack.filename)).equals(readFileSync(candidate)),
@@ -287,7 +297,6 @@ try {
287
297
  }
288
298
  for (const packageRoot of installProduction(pack)) {
289
299
  // Qualify both the npm node_modules location and standalone git-style production graph.
290
- // oxlint-disable-next-line no-await-in-loop
291
300
  await verifyRuntime(packageRoot);
292
301
  }
293
302
  console.log(
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { execFile as execFileCallback } from "node:child_process";
12
+ import assert from "node:assert/strict";
12
13
  import { existsSync } from "node:fs";
13
14
  import { mkdtemp, readFile, rm } from "node:fs/promises";
14
15
  import { tmpdir } from "node:os";
@@ -17,7 +18,6 @@ import process from "node:process";
17
18
  import { promisify } from "node:util";
18
19
 
19
20
  /** @type {(file: string, args: readonly string[], options: import("node:child_process").ExecFileOptions) => Promise<{ stdout: string, stderr: string }>} */
20
- // oxlint-disable-next-line typescript/strict-void-return
21
21
  const execFile = promisify(execFileCallback);
22
22
  const cwd = process.cwd();
23
23
  const buildScript = join(cwd, "scripts", "build.mjs");
@@ -57,10 +57,16 @@ async function main() {
57
57
  if (npmExecPath === undefined || npmExecPath === "") {
58
58
  throw new Error(`npm_execpath is not set; run "npm install" manually in ${cwd} and retry.`);
59
59
  }
60
- /** @type {{ devDependencies: { typescript: string } }} */
61
- // oxlint-disable-next-line typescript/no-unsafe-assignment
60
+ /** @type {unknown} */
62
61
  const pkg = JSON.parse(await readFile(join(cwd, "package.json"), "utf8"));
62
+ assert.ok(typeof pkg === "object" && pkg !== null && "devDependencies" in pkg);
63
63
  const { devDependencies } = pkg;
64
+ assert.ok(
65
+ typeof devDependencies === "object" &&
66
+ devDependencies !== null &&
67
+ "typescript" in devDependencies &&
68
+ typeof devDependencies.typescript === "string",
69
+ );
64
70
  const prefix = await mkdtemp(join(tmpdir(), "pi-subagents-tsc-"));
65
71
  try {
66
72
  await runNode([
@@ -82,13 +88,13 @@ async function main() {
82
88
  }
83
89
 
84
90
  main().catch((/** @type {unknown} */ error) => {
85
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- exec rejections carry partial output
86
- const output = /** @type {{ stdout?: string, stderr?: string }} */ (error);
87
- if (output.stdout !== undefined && output.stdout !== "") {
88
- process.stdout.write(output.stdout);
89
- }
90
- if (output.stderr !== undefined && output.stderr !== "") {
91
- process.stderr.write(output.stderr);
91
+ if (typeof error === "object" && error !== null) {
92
+ if ("stdout" in error && typeof error.stdout === "string") {
93
+ process.stdout.write(error.stdout);
94
+ }
95
+ if ("stderr" in error && typeof error.stderr === "string") {
96
+ process.stderr.write(error.stderr);
97
+ }
92
98
  }
93
99
  console.error(error instanceof Error ? error.message : JSON.stringify(error));
94
100
  process.exitCode = 1;
@@ -0,0 +1,92 @@
1
+ // Decode the RPC evidence used by the standalone live smoke without requiring local Pi peers.
2
+ import assert from "node:assert/strict";
3
+ import { isRecord, isUnknownArray } from "../src/shared/unknown.ts";
4
+
5
+ /** @typedef {Record<string, unknown>} RpcEvent */
6
+ /**
7
+ * The fields consumed from a native launch/continuation receipt, plus stream position.
8
+ * @typedef {{ [K in "id" | "name" | "sessionFile" | "context" | "predecessor"]: import("../src/agent/contracts.ts").Launch[K] | undefined } & { receipt: string, tool: string, index: number }} SessionReceipt
9
+ */
10
+ /** @typedef {ReturnType<typeof completionNotice>} SessionNotice */
11
+ /**
12
+ * @typedef {object} SessionView
13
+ * @property {RpcEvent[]} events
14
+ * @property {SessionReceipt[]} receipts
15
+ * @property {Map<string | undefined, SessionNotice>} notices
16
+ * @property {{ index: number, text: string, stopReason: string | undefined }[]} replies
17
+ */
18
+
19
+ /**
20
+ * @param {Record<string, unknown>} details
21
+ * @param {unknown} tool
22
+ * @param {number} index
23
+ * @returns {SessionReceipt}
24
+ */
25
+ function sessionReceipt(details, tool, index) {
26
+ const { id, name, sessionFile, context, predecessor, receipt } = details;
27
+ assert.ok(id === undefined || typeof id === "string");
28
+ assert.ok(name === undefined || typeof name === "string");
29
+ assert.ok(sessionFile === undefined || typeof sessionFile === "string");
30
+ assert.ok(context === undefined || context === "fresh" || context === "fork");
31
+ assert.ok(predecessor === undefined || typeof predecessor === "string");
32
+ assert.ok(typeof receipt === "string" && typeof tool === "string");
33
+ return { id, name, sessionFile, context, predecessor, receipt, tool, index };
34
+ }
35
+
36
+ function completionNotice(/** @type {unknown} */ details, /** @type {number} */ index) {
37
+ assert.ok(isRecord(details));
38
+ const { id, result, page } = details;
39
+ assert.ok(id === undefined || typeof id === "string");
40
+ assert.ok(result === undefined || isRecord(result));
41
+ assert.ok(page === undefined || isRecord(page));
42
+ const status = result?.status;
43
+ const text = page?.text;
44
+ assert.ok(status === undefined || typeof status === "string");
45
+ assert.ok(text === undefined || typeof text === "string");
46
+ return { id, index, result: { status }, page: { text } };
47
+ }
48
+
49
+ function addMessage(
50
+ /** @type {SessionView} */ view,
51
+ /** @type {unknown} */ message,
52
+ /** @type {number} */ index,
53
+ ) {
54
+ if (!isRecord(message)) {
55
+ return;
56
+ }
57
+ if (message.customType === "agent-completion" && message.details !== undefined) {
58
+ const notice = completionNotice(message.details, index);
59
+ view.notices.set(notice.id, notice);
60
+ } else if (message.role === "assistant") {
61
+ const content = message.content ?? [];
62
+ assert.ok(isUnknownArray(content));
63
+ const text = content
64
+ .filter(isRecord)
65
+ .filter((part) => part.type === "text")
66
+ .map((part) => {
67
+ assert.ok(part.text === undefined || typeof part.text === "string");
68
+ return part.text;
69
+ });
70
+ assert.ok(message.stopReason === undefined || typeof message.stopReason === "string");
71
+ view.replies.push({ index, text: text.join("\n"), stopReason: message.stopReason });
72
+ }
73
+ }
74
+
75
+ /** @param {RpcEvent[]} events @returns {SessionView} */
76
+ export function sessionView(events) {
77
+ /** @type {SessionView} */
78
+ const view = { events, receipts: [], notices: new Map(), replies: [] };
79
+ for (const [index, event] of events.entries()) {
80
+ const details = isRecord(event.result) ? event.result.details : undefined;
81
+ if (event.type === "message_end") {
82
+ addMessage(view, event.message, index);
83
+ } else if (
84
+ event.type === "tool_execution_end" &&
85
+ isRecord(details) &&
86
+ (details.receipt === "launch" || details.receipt === "continuation")
87
+ ) {
88
+ view.receipts.push(sessionReceipt(details, event.toolName, index));
89
+ }
90
+ }
91
+ return view;
92
+ }