@fitchmultz/pi-subagents 0.50.0 → 0.52.0

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 (180) hide show
  1. package/CHANGELOG.md +63 -2
  2. package/README.md +36 -5
  3. package/dist/agent/child-runtime.js +80 -0
  4. package/dist/agent/contracts.js +15 -24
  5. package/dist/agent/host.js +3 -16
  6. package/dist/agent/index.js +212 -192
  7. package/dist/agent/journal-tail.js +115 -156
  8. package/dist/agent/launch.js +20 -8
  9. package/dist/agent/legacy.js +65 -23
  10. package/dist/agent/notifications.js +138 -47
  11. package/dist/agent/observation-state.js +246 -0
  12. package/dist/agent/observation-view.js +258 -0
  13. package/dist/agent/observation.js +350 -0
  14. package/dist/agent/observe-card.js +243 -0
  15. package/dist/agent/observe-chrome.js +85 -0
  16. package/dist/agent/observe-client.js +285 -0
  17. package/dist/agent/observe-layout.js +65 -0
  18. package/dist/agent/observe-reading.js +84 -0
  19. package/dist/agent/observe-screen.js +400 -96
  20. package/dist/agent/observe-transcript.js +382 -0
  21. package/dist/agent/observe.js +250 -43
  22. package/dist/agent/parking.js +253 -0
  23. package/dist/agent/process.js +228 -28
  24. package/dist/agent/receipts.js +92 -20
  25. package/dist/agent/runner.js +229 -126
  26. package/dist/agent/shell.js +2 -1
  27. package/dist/agent/storage.js +47 -12
  28. package/dist/agent/tool-schemas.js +20 -0
  29. package/dist/agent/typebox.js +10 -5
  30. package/dist/agent/usage.js +0 -115
  31. package/dist/agent/view.js +46 -16
  32. package/dist/agent/wake.js +99 -0
  33. package/dist/build-info.js +1 -1
  34. package/dist/intercom-client.d.ts +26 -0
  35. package/dist/intercom-client.js +45 -0
  36. package/dist/pi-intercom/agent-dir.d.ts +1 -0
  37. package/dist/pi-intercom/bridge-session.js +2 -1
  38. package/dist/pi-intercom/broker/broker.js +62 -43
  39. package/dist/pi-intercom/broker/client-last.d.ts +24 -0
  40. package/dist/pi-intercom/broker/client-last.js +42 -5
  41. package/dist/pi-intercom/broker/client-park.d.ts +9 -0
  42. package/dist/pi-intercom/broker/client-park.js +21 -0
  43. package/dist/pi-intercom/broker/client.d.ts +59 -0
  44. package/dist/pi-intercom/broker/client.js +55 -29
  45. package/dist/pi-intercom/broker/connection.d.ts +29 -0
  46. package/dist/pi-intercom/broker/framing.d.ts +17 -0
  47. package/dist/pi-intercom/broker/framing.js +10 -7
  48. package/dist/pi-intercom/broker/guard.js +23 -0
  49. package/dist/pi-intercom/broker/last.d.ts +30 -0
  50. package/dist/pi-intercom/broker/last.js +87 -40
  51. package/dist/pi-intercom/broker/parked.js +107 -0
  52. package/dist/pi-intercom/broker/paths.d.ts +13 -0
  53. package/dist/pi-intercom/broker/paths.js +35 -12
  54. package/dist/pi-intercom/broker/pid.js +5 -1
  55. package/dist/pi-intercom/broker/server-connection.js +20 -1
  56. package/dist/pi-intercom/broker/snapshot-reader.d.ts +9 -0
  57. package/dist/pi-intercom/broker/snapshots.d.ts +29 -0
  58. package/dist/pi-intercom/broker/targets.d.ts +9 -0
  59. package/dist/pi-intercom/connection.js +71 -20
  60. package/dist/pi-intercom/identity.js +6 -5
  61. package/dist/pi-intercom/inbound-delivery.js +16 -60
  62. package/dist/pi-intercom/inbound-journal.js +83 -6
  63. package/dist/pi-intercom/inbound-record.js +18 -1
  64. package/dist/pi-intercom/inbound.js +22 -37
  65. package/dist/pi-intercom/index.js +41 -0
  66. package/dist/pi-intercom/last-message.js +4 -16
  67. package/dist/pi-intercom/lifecycle.js +88 -3
  68. package/dist/pi-intercom/outbound.js +5 -15
  69. package/dist/pi-intercom/reply-tracker.js +7 -6
  70. package/dist/pi-intercom/reply-wait.js +2 -2
  71. package/dist/pi-intercom/session-targets.d.ts +24 -0
  72. package/dist/pi-intercom/tool-registration.js +4 -1
  73. package/dist/pi-intercom/tool-results.js +5 -7
  74. package/dist/pi-intercom/tool-schemas.js +12 -26
  75. package/dist/pi-intercom/types.d.ts +212 -0
  76. package/dist/pi-intercom/types.js +54 -3
  77. package/dist/pi-intercom/validation.d.ts +12 -0
  78. package/dist/pi-intercom/validation.js +3 -0
  79. package/dist/shared/bounded-json.js +151 -0
  80. package/dist/shared/compact-cards.js +66 -0
  81. package/dist/shared/journal-frames.js +3 -6
  82. package/dist/shared/json-projection.js +267 -129
  83. package/dist/shared/native-journal.js +123 -56
  84. package/dist/shared/parking.js +12 -0
  85. package/dist/shared/post-exit-stdio-guard.js +120 -7
  86. package/dist/shared/tui-action.js +29 -0
  87. package/dist/shared/unknown.d.ts +9 -0
  88. package/docs/code-quality.md +5 -3
  89. package/docs/development.md +42 -3
  90. package/docs/intercom-bridge.md +9 -4
  91. package/docs/intercom.md +228 -7
  92. package/docs/performance.md +118 -14
  93. package/docs/reference.md +168 -20
  94. package/docs/retired-history-maintenance.md +252 -0
  95. package/package.json +28 -15
  96. package/scripts/archive-retired-history.mjs +464 -0
  97. package/scripts/build.mjs +48 -28
  98. package/scripts/measure-size.mjs +1 -1
  99. package/scripts/native-package-smoke.mjs +153 -12
  100. package/scripts/package-smoke.mjs +80 -11
  101. package/scripts/prepare.mjs +2 -2
  102. package/scripts/real-pi-live.mjs +377 -0
  103. package/scripts/real-pi-processes.mjs +6 -1
  104. package/scripts/real-pi-smoke.mjs +6 -282
  105. package/scripts/run-tests.mjs +4 -2
  106. package/scripts/typescript.mjs +35 -0
  107. package/skills/pi-intercom/SKILL.md +2 -1
  108. package/skills/pi-subagents/SKILL.md +23 -5
  109. package/src/agent/child-runtime.ts +105 -0
  110. package/src/agent/contracts.ts +18 -34
  111. package/src/agent/host.ts +39 -20
  112. package/src/agent/index.ts +234 -207
  113. package/src/agent/journal-tail.ts +141 -153
  114. package/src/agent/launch.ts +33 -9
  115. package/src/agent/legacy.ts +85 -31
  116. package/src/agent/notifications.ts +192 -67
  117. package/src/agent/observation-state.ts +277 -0
  118. package/src/agent/observation-view.ts +267 -0
  119. package/src/agent/observation.ts +409 -0
  120. package/src/agent/observe-card.ts +304 -0
  121. package/src/agent/observe-chrome.ts +170 -0
  122. package/src/agent/observe-client.ts +318 -0
  123. package/src/agent/observe-layout.ts +79 -0
  124. package/src/agent/observe-reading.ts +97 -0
  125. package/src/agent/observe-screen.ts +443 -129
  126. package/src/agent/observe-transcript.ts +434 -0
  127. package/src/agent/observe.ts +304 -71
  128. package/src/agent/parking.ts +341 -0
  129. package/src/agent/process.ts +280 -37
  130. package/src/agent/receipts.ts +114 -24
  131. package/src/agent/runner.ts +227 -149
  132. package/src/agent/shell.ts +2 -1
  133. package/src/agent/storage.ts +44 -11
  134. package/src/agent/tool-schemas.ts +33 -0
  135. package/src/agent/typebox.ts +12 -7
  136. package/src/agent/usage.ts +0 -130
  137. package/src/agent/view.ts +70 -20
  138. package/src/agent/wake.ts +107 -0
  139. package/src/intercom-client.ts +108 -0
  140. package/src/pi-intercom/bridge-session.ts +2 -2
  141. package/src/pi-intercom/broker/broker.ts +64 -53
  142. package/src/pi-intercom/broker/client-last.ts +65 -12
  143. package/src/pi-intercom/broker/client-park.ts +23 -0
  144. package/src/pi-intercom/broker/client.ts +63 -45
  145. package/src/pi-intercom/broker/framing.ts +11 -8
  146. package/src/pi-intercom/broker/guard.ts +33 -0
  147. package/src/pi-intercom/broker/last.ts +131 -43
  148. package/src/pi-intercom/broker/parked.ts +130 -0
  149. package/src/pi-intercom/broker/paths.ts +39 -12
  150. package/src/pi-intercom/broker/pid.ts +6 -1
  151. package/src/pi-intercom/broker/server-connection.ts +27 -2
  152. package/src/pi-intercom/connection.ts +103 -39
  153. package/src/pi-intercom/identity.ts +8 -5
  154. package/src/pi-intercom/inbound-delivery.ts +20 -66
  155. package/src/pi-intercom/inbound-journal.ts +103 -7
  156. package/src/pi-intercom/inbound-record.ts +21 -4
  157. package/src/pi-intercom/inbound.ts +32 -49
  158. package/src/pi-intercom/index.ts +44 -0
  159. package/src/pi-intercom/last-message.ts +11 -20
  160. package/src/pi-intercom/lifecycle.ts +117 -14
  161. package/src/pi-intercom/outbound.ts +7 -16
  162. package/src/pi-intercom/reply-tracker.ts +8 -6
  163. package/src/pi-intercom/reply-wait.ts +3 -2
  164. package/src/pi-intercom/runtime-types.ts +0 -1
  165. package/src/pi-intercom/tool-registration.ts +4 -1
  166. package/src/pi-intercom/tool-results.ts +10 -9
  167. package/src/pi-intercom/tool-schemas.ts +12 -44
  168. package/src/pi-intercom/types.ts +115 -4
  169. package/src/pi-intercom/validation.ts +4 -0
  170. package/src/shared/bounded-json.ts +162 -0
  171. package/src/shared/compact-cards.ts +118 -0
  172. package/src/shared/journal-frames.ts +3 -19
  173. package/src/shared/json-projection.ts +267 -147
  174. package/src/shared/native-journal.ts +157 -75
  175. package/src/shared/parking.ts +22 -0
  176. package/src/shared/post-exit-stdio-guard.ts +165 -7
  177. package/src/shared/tui-action.ts +39 -0
  178. package/tsconfig.client.json +8 -0
  179. package/tsconfig.json +1 -0
  180. package/src/shared/stream-json.d.ts +0 -10
@@ -1,5 +1,7 @@
1
1
  // Runtime-only package probe: selected native SDK/CLI, real packed actor and private broker.
2
2
  import assert from "node:assert/strict";
3
+ import { execFile as execFileCallback } from "node:child_process";
4
+ import { promisify } from "node:util";
3
5
  import {
4
6
  mkdtempSync,
5
7
  mkdirSync,
@@ -10,13 +12,15 @@ import {
10
12
  existsSync,
11
13
  } from "node:fs";
12
14
  import { tmpdir } from "node:os";
13
- import { join, resolve } from "node:path";
15
+ import { dirname, join, resolve } from "node:path";
14
16
  import { setTimeout as delay } from "node:timers/promises";
15
17
  import { pathToFileURL } from "node:url";
16
18
  import { createRequire } from "node:module";
17
19
  import { hostCli, hostIndex, hostRoot } from "./compat-host.mjs";
18
20
  import { SmokeProcesses } from "./real-pi-processes.mjs";
19
21
 
22
+ const execFile = promisify(execFileCallback);
23
+
20
24
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
21
25
  console.log(
22
26
  "Usage: node scripts/native-package-smoke.mjs [package-root]\n\nLoad the single compiled entry using the selected native host. Exercise all three eager package tools and a real actor prompt/toolResult/completion with a credential-free provider, then verify bundled CLI startup and shutdown. No paid model calls.\nExit codes: 0 passed; 1 native execution or cleanup failed.",
@@ -25,6 +29,8 @@ if (process.argv.includes("--help") || process.argv.includes("-h")) {
25
29
  }
26
30
 
27
31
  const packageRoot = realpathSync(resolve(process.argv[2] ?? "."));
32
+ const consumerDir = process.argv[3];
33
+ const consumerScript = process.argv[4];
28
34
  const root = mkdtempSync(join(tmpdir(), "ps-native-"));
29
35
  const agentDir = join(root, "agent");
30
36
  mkdirSync(join(agentDir, "extensions"), { recursive: true });
@@ -105,16 +111,24 @@ export default function(pi) {
105
111
  assert.deepEqual([...tools].sort(), ${JSON.stringify(expectedTools)});
106
112
  }
107
113
  observe({ tools, requested: true });
108
- return fauxAssistantMessage(fauxToolCall(child ? "packed_probe" : "delegate", child ? { value: "native-tool-input" } : { prompt: ${JSON.stringify(prompt)}, name: "packed-native", async: false }), { stopReason: "toolUse" });
114
+ return fauxAssistantMessage(fauxToolCall(child ? "packed_probe" : "delegate", child ? { value: "native-tool-input" } : { prompt: ${JSON.stringify(prompt)}, name: "packed-native" }), { stopReason: "toolUse" });
109
115
  },
110
116
  (context) => {
111
117
  const result = context.messages.findLast(message => message.role === "toolResult");
112
118
  assert.equal(result?.isError ?? false, false);
113
119
  assert.equal(result?.toolName, child ? "packed_probe" : "delegate");
114
- assert.ok(result.content.some(part => part.type === "text" && part.text.includes(child ? "PACKED_TOOL_RESULT" : "PACKED_NATIVE_COMPLETE")));
120
+ // The parent gets only an honest launch receipt; the child's outcome arrives later.
121
+ assert.ok(result.content.some(part => part.type === "text" && (child ? part.text.includes("PACKED_TOOL_RESULT") : part.text.includes("Launch accepted") && !part.text.includes("PACKED_NATIVE_COMPLETE"))));
115
122
  observe({ consumedResult: true, calls: faux.state.callCount });
116
- return fauxAssistantMessage(child ? "PACKED_NATIVE_COMPLETE" : "PACKED_PARENT_COMPLETE");
117
- }
123
+ return fauxAssistantMessage(child ? "PACKED_NATIVE_COMPLETE" : "PACKED_LAUNCH_RECEIPT_SEEN");
124
+ },
125
+ ...(child ? [] : [(context) => {
126
+ const notice = context.messages.at(-1);
127
+ const text = typeof notice.content === "string" ? notice.content : notice.content.map(part => part.text ?? "").join("");
128
+ assert.ok(text.includes(": completed. \\nPACKED_NATIVE_COMPLETE\\n"), text);
129
+ observe({ consumedCompletion: true, calls: faux.state.callCount });
130
+ return fauxAssistantMessage("PACKED_PARENT_COMPLETE");
131
+ }])
118
132
  ]);
119
133
  pi.registerProvider(faux.provider);
120
134
  }
@@ -141,6 +155,7 @@ const processes = new SmokeProcesses({
141
155
  let session;
142
156
  let watchdog;
143
157
  let brokerError;
158
+ let intercomEvents;
144
159
  const brokerRun = processes
145
160
  .run("private broker", process.execPath, [join(packageRoot, "dist/pi-intercom/broker/broker.js")])
146
161
  .catch((error) => {
@@ -166,14 +181,21 @@ try {
166
181
  noContextFiles: true,
167
182
  noPromptTemplates: true,
168
183
  noThemes: true,
184
+ extensionFactories: consumerScript
185
+ ? [
186
+ (pi) => {
187
+ intercomEvents = pi.events;
188
+ },
189
+ ]
190
+ : [],
169
191
  });
170
192
  await loader.reload();
171
193
  assert.deepEqual(loader.getExtensions().errors, []);
172
194
  assert.deepEqual(loader.getExtensions().warnings ?? [], []);
173
195
  assert.equal(
174
196
  loader.getExtensions().extensions.length,
175
- 2,
176
- "one package entry plus credential-free provider fixture",
197
+ consumerScript ? 3 : 2,
198
+ "one package entry plus credential-free provider and optional client observer fixtures",
177
199
  );
178
200
  const modelRuntime = await sdk.ModelRuntime.create({
179
201
  credentials: new ai.InMemoryCredentialStore(),
@@ -218,6 +240,29 @@ try {
218
240
  }, 30_000);
219
241
  await session.prompt("Exercise the packed native delegate.", { expandPromptTemplates: false });
220
242
  await session.waitForIdle();
243
+ assert.equal(
244
+ session.getLastAssistantText(),
245
+ "PACKED_LAUNCH_RECEIPT_SEEN",
246
+ JSON.stringify({
247
+ errors,
248
+ messages: events.filter(
249
+ (event) =>
250
+ (event.type === "message_end" && event.message?.role === "assistant") ||
251
+ event.type === "tool_execution_end",
252
+ ),
253
+ }),
254
+ );
255
+ const completionDeadline = Date.now() + 30_000;
256
+ while (session.getLastAssistantText() !== "PACKED_PARENT_COMPLETE") {
257
+ // Observe the native completion notification turn, not a fixed delay.
258
+ assert.ok(
259
+ Date.now() < completionDeadline,
260
+ `native completion notification resumes the parent: ${JSON.stringify(session.messages.slice(-3))}`,
261
+ );
262
+ // oxlint-disable-next-line no-await-in-loop
263
+ await delay(25);
264
+ }
265
+ await session.waitForIdle();
221
266
  clearTimeout(watchdog);
222
267
  assert.equal(
223
268
  session.getLastAssistantText(),
@@ -235,11 +280,13 @@ try {
235
280
  (event) => event.type === "tool_execution_start" && event.toolName === "delegate",
236
281
  );
237
282
  assert.equal(starts.length, 1, "one real native delegate call, no fabricated wire events");
238
- const completion = events.find(
283
+ const receipt = events.find(
239
284
  (event) => event.type === "tool_execution_end" && event.toolName === "delegate",
240
285
  );
241
- assert.ok(completion && !completion.isError, JSON.stringify(completion));
242
- const run = completion.result.details;
286
+ assert.ok(receipt && !receipt.isError, JSON.stringify(receipt));
287
+ assert.equal(receipt.result.details.receipt, "launch");
288
+ const run = (await execute("agent_runs", { action: "inspect", id: receipt.result.details.id }))
289
+ .details;
243
290
  assert.equal(run.status, "completed");
244
291
  assert.equal(run.output.text, "PACKED_NATIVE_COMPLETE");
245
292
  const childEntries = readFileSync(run.sessionFile, "utf8")
@@ -280,6 +327,11 @@ try {
280
327
  observed.filter((item) => !item.child && item.consumedResult && item.calls === 2).length,
281
328
  1,
282
329
  );
330
+ assert.equal(
331
+ observed.filter((item) => !item.child && item.consumedCompletion && item.calls === 3).length,
332
+ 1,
333
+ "the parent is resumed exactly once by the native completion notification",
334
+ );
283
335
  assert.equal(networkAttempts, 0);
284
336
  assert.equal(
285
337
  observed.some((item) => item.networkAttempt),
@@ -287,13 +339,102 @@ try {
287
339
  );
288
340
  assert.deepEqual(errors, []);
289
341
 
342
+ if (consumerScript) {
343
+ let target;
344
+ intercomEvents.emit("pi-intercom:context-query", {
345
+ version: 1,
346
+ respond: (snapshot) => {
347
+ target = snapshot;
348
+ },
349
+ });
350
+ assert.ok(target?.connected && target.peerId && target.rootSessionId && target.incarnation);
351
+ const guard = {
352
+ rootSessionId: target.rootSessionId,
353
+ peerId: target.peerId,
354
+ incarnation: target.incarnation,
355
+ applicationGuard: "package-consent-1",
356
+ };
357
+ let consent;
358
+ intercomEvents.emit("pi-intercom:application-guard", {
359
+ version: 1,
360
+ target: guard,
361
+ revision: guard.applicationGuard,
362
+ respond: (result) => {
363
+ consent = result;
364
+ },
365
+ });
366
+ assert.equal(consent.applicationGuard, guard.applicationGuard);
367
+ const consumerHome = join(root, "consumer-home");
368
+ mkdirSync(consumerHome);
369
+ const brokerPid = Number(
370
+ readFileSync(join(process.env.PI_INTERCOM_RUNTIME_DIR, "broker.pid"), "utf8").split("\n")[0],
371
+ );
372
+ const consumerOptions = {
373
+ cwd: consumerDir,
374
+ env: {
375
+ HOME: consumerHome,
376
+ PATH: process.env.PATH,
377
+ PI_CODING_AGENT_DIR: join(consumerHome, "agent"),
378
+ PI_INTERCOM_RUNTIME_DIR: process.env.PI_INTERCOM_RUNTIME_DIR,
379
+ S3_ROOT: guard.rootSessionId,
380
+ S3_PEER: guard.peerId,
381
+ S3_INCARNATION: guard.incarnation,
382
+ S3_GUARD: guard.applicationGuard,
383
+ S3_BROKER_PID: String(brokerPid),
384
+ },
385
+ timeout: 30_000,
386
+ maxBuffer: 1024 * 1024,
387
+ };
388
+ const turns = events.filter((event) => event.type === "turn_start").length;
389
+ try {
390
+ const { stdout, stderr } = await execFile(
391
+ process.execPath,
392
+ [
393
+ "--permission",
394
+ "--allow-fs-read=*",
395
+ "--allow-fs-write=*",
396
+ "--input-type=module",
397
+ "-e",
398
+ readFileSync(consumerScript, "utf8"),
399
+ ],
400
+ consumerOptions,
401
+ );
402
+ process.stdout.write(stdout);
403
+ process.stderr.write(stderr);
404
+ assert.equal(events.filter((event) => event.type === "turn_start").length, turns);
405
+ } finally {
406
+ // The consumer's failure/timeout must never leave this fixture's broker stopped.
407
+ process.kill(brokerPid, "SIGCONT");
408
+ }
409
+ }
410
+
290
411
  const marker = join(root, "cli.json");
291
412
  const observer = join(root, "observer.mjs");
292
413
  writeFileSync(
293
414
  observer,
294
- `import { writeFileSync } from "node:fs"; export default function(pi) { pi.on("session_start", (_event, ctx) => { writeFileSync(${JSON.stringify(marker)}, JSON.stringify(pi.getActiveTools())); ctx.shutdown(); }); }`,
415
+ `import assert from "node:assert/strict";
416
+ import { writeFileSync } from "node:fs";
417
+ import { Type } from "typebox";
418
+ import { Assert } from "typebox/value";
419
+ import { Type as AgentType, Assert as AgentAssert } from ${JSON.stringify(pathToFileURL(join(packageRoot, "dist/agent/typebox.js")).href)};
420
+ export default function(pi) {
421
+ // Native development graphs can resolve local peers before Jiti runs.
422
+ // Production-only installs must instead reuse the bundled host's virtual modules.
423
+ if (${!existsSync(join(packageRoot, "node_modules/typebox"))}) {
424
+ assert.equal(AgentType, Type, "extension schemas reuse Pi's TypeBox instance");
425
+ assert.equal(AgentAssert, Assert, "extension validation reuses Pi's TypeBox instance");
426
+ }
427
+ pi.on("session_start", (_event, ctx) => {
428
+ writeFileSync(${JSON.stringify(marker)}, JSON.stringify(pi.getActiveTools()));
429
+ ctx.shutdown();
430
+ });
431
+ }`,
295
432
  );
296
- const cliEnv = { ...process.env, PATH: "/usr/bin:/bin" };
433
+ const cliEnv = {
434
+ ...process.env,
435
+ PATH:
436
+ process.platform === "android" ? `${dirname(process.execPath)}:/system/bin` : "/usr/bin:/bin",
437
+ };
297
438
  delete cliEnv.PI_PACKAGE_DIR;
298
439
  const cliProcesses = new SmokeProcesses({
299
440
  cwd: root,
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import assert from "node:assert/strict";
3
3
  import { spawnSync } from "node:child_process";
4
- import { createRequire } from "node:module";
5
4
  import {
6
5
  cpSync,
7
6
  existsSync,
@@ -13,9 +12,10 @@ import {
13
12
  writeFileSync,
14
13
  } from "node:fs";
15
14
  import { tmpdir } from "node:os";
16
- import { join } from "node:path";
15
+ import { dirname, join } from "node:path";
17
16
  import { fileURLToPath, pathToFileURL } from "node:url";
18
17
  import { hostRoot } from "./compat-host.mjs";
18
+ import { compilerInvocation } from "./typescript.mjs";
19
19
 
20
20
  if (process.argv.includes("--help") || process.argv.includes("-h")) {
21
21
  console.log(
@@ -95,12 +95,42 @@ function installProduction(pack) {
95
95
  );
96
96
  }
97
97
  }
98
- return [installed, standalone];
98
+ return [
99
+ { packageRoot: installed, consumerDir: installDir },
100
+ { packageRoot: standalone, consumerDir: standalone },
101
+ ];
102
+ }
103
+
104
+ function verifyDeclarations(consumerDir) {
105
+ const contract = join(consumerDir, "public-client.test-d.ts");
106
+ cpSync(new URL("../test/fixtures/intercom-client-contract.test-d.ts", import.meta.url), contract);
107
+ const compiler = compilerInvocation();
108
+ run(
109
+ compiler.command,
110
+ [
111
+ ...compiler.args,
112
+ "--ignoreConfig",
113
+ "--noEmit",
114
+ "--strict",
115
+ "--target",
116
+ "ES2025",
117
+ "--module",
118
+ "NodeNext",
119
+ "--moduleResolution",
120
+ "NodeNext",
121
+ "--types",
122
+ "node",
123
+ "--typeRoots",
124
+ fileURLToPath(new URL("../node_modules/@types", import.meta.url)),
125
+ contract,
126
+ ],
127
+ consumerDir,
128
+ );
99
129
  }
100
130
 
101
- async function verifyRuntime(packageRoot) {
131
+ async function verifyRuntime({ packageRoot, consumerDir }) {
102
132
  verifyManifest(JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")));
103
- const hostRequire = createRequire(join(hostRoot, "dist/index.js"));
133
+ verifyDeclarations(consumerDir);
104
134
  const sdk = await import(pathToFileURL(join(hostRoot, "dist/index.js")).href);
105
135
  const nativeHost = await import(pathToFileURL(join(packageRoot, "dist/agent/host.js")).href);
106
136
  assert.equal(
@@ -108,11 +138,28 @@ async function verifyRuntime(packageRoot) {
108
138
  sdk,
109
139
  "actor must use the exact selected host SDK",
110
140
  );
111
- const hostTypebox = await import(pathToFileURL(hostRequire.resolve("typebox")).href);
112
- const nativeTypebox = await import(
113
- pathToFileURL(join(packageRoot, "dist/agent/typebox.js")).href
141
+ run(
142
+ process.execPath,
143
+ [
144
+ "--input-type=module",
145
+ "-e",
146
+ `import assert from "node:assert/strict";
147
+ import { createRequire } from "node:module";
148
+ import { pathToFileURL } from "node:url";
149
+ const require = createRequire(${JSON.stringify(join(hostRoot, "dist/index.js"))});
150
+ const hostTypebox = await import(pathToFileURL(require.resolve("typebox")).href);
151
+ const hostValue = await import(pathToFileURL(require.resolve("typebox/value")).href);
152
+ const native = await import(${JSON.stringify(pathToFileURL(join(packageRoot, "dist/agent/typebox.js")).href)});
153
+ assert.equal(native.Type, hostTypebox.Type, "detached schemas use the selected host");
154
+ assert.equal(native.Assert, hostValue.Assert, "detached validation uses the selected host");`,
155
+ ],
156
+ root,
157
+ {
158
+ ...process.env,
159
+ PI_INTERCOM_ROOT_SESSION_ID: "package-smoke",
160
+ PI_INTERCOM_PARENT_SESSION_ID: "package-smoke",
161
+ },
114
162
  );
115
- assert.equal(nativeTypebox.Type, hostTypebox.Type, "shared schemas use the selected host");
116
163
  const broker = await import(
117
164
  pathToFileURL(join(packageRoot, "dist/pi-intercom/broker/spawn.js")).href
118
165
  );
@@ -123,12 +170,30 @@ async function verifyRuntime(packageRoot) {
123
170
  root,
124
171
  );
125
172
  assert.match(help, /Usage: node scripts\/intercom-bridge-credentials.mjs/);
173
+ const maintenanceHelp = run(
174
+ process.execPath,
175
+ [join(packageRoot, "scripts/archive-retired-history.mjs"), "--help"],
176
+ root,
177
+ );
178
+ assert.match(maintenanceHelp, /Offline-only opt-in maintenance/);
126
179
  process.stdout.write(
127
180
  run(
128
181
  process.execPath,
129
- [fileURLToPath(new URL("./native-package-smoke.mjs", import.meta.url)), packageRoot],
182
+ [
183
+ fileURLToPath(new URL("./native-package-smoke.mjs", import.meta.url)),
184
+ packageRoot,
185
+ consumerDir,
186
+ fileURLToPath(new URL("../test/fixtures/intercom-client-consumer.mjs", import.meta.url)),
187
+ ],
130
188
  root,
131
- { ...process.env, PI_PACKAGE_DIR: hostRoot, PATH: "/usr/bin:/bin" },
189
+ {
190
+ ...process.env,
191
+ PI_PACKAGE_DIR: hostRoot,
192
+ PATH:
193
+ process.platform === "android"
194
+ ? `${dirname(process.execPath)}:/system/bin`
195
+ : "/usr/bin:/bin",
196
+ },
132
197
  ),
133
198
  );
134
199
  }
@@ -146,6 +211,8 @@ try {
146
211
  "LICENSE",
147
212
  "README.md",
148
213
  "dist/index.js",
214
+ "dist/intercom-client.js",
215
+ "dist/intercom-client.d.ts",
149
216
  "dist/build-info.js",
150
217
  "dist/agent/runner.js",
151
218
  "dist/pi-intercom/broker/broker.js",
@@ -156,6 +223,8 @@ try {
156
223
  "scripts/build.mjs",
157
224
  "scripts/real-pi-smoke.mjs",
158
225
  "scripts/intercom-remote.mjs",
226
+ "scripts/archive-retired-history.mjs",
227
+ "docs/retired-history-maintenance.md",
159
228
  "skills/pi-subagents/SKILL.md",
160
229
  "skills/pi-intercom/SKILL.md",
161
230
  ]) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Purpose: Build generated dist output for GitHub/source installs even when Pi invokes npm install --omit=dev.
4
- * Responsibilities: Build with the local compiler when present; otherwise install only the pinned
4
+ * Responsibilities: Build with native Android tsgo or the local compiler; otherwise install only the pinned
5
5
  * `typescript` into a throwaway prefix, build with it, and delete the prefix. Emit needs no type
6
6
  * information (tsconfig.build.json sets noCheck), so the Pi host graph is never installed here.
7
7
  * Scope: Package install lifecycle only; runtime behavior remains owned by scripts/build.mjs.
@@ -46,7 +46,7 @@ async function runNode(args) {
46
46
  }
47
47
 
48
48
  async function main() {
49
- if (existsSync(tscBin(cwd))) {
49
+ if (process.platform === "android" || existsSync(tscBin(cwd))) {
50
50
  await runNode([buildScript]);
51
51
  return;
52
52
  }