@fitchmultz/pi-subagents 0.52.16 → 0.52.18
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/CHANGELOG.md +30 -0
- package/dist/agent/host.js +1 -1
- package/dist/agent/notifications.js +10 -8
- package/dist/agent/observe-layout.js +3 -3
- package/dist/agent/parking.js +0 -1
- package/dist/agent/process.js +1 -4
- package/dist/agent/runner.js +0 -2
- package/dist/agent/shutdown.js +0 -1
- package/dist/agent/typebox.js +2 -2
- package/dist/build-info.js +1 -1
- package/dist/pi-intercom/bridge-presence.js +0 -1
- package/dist/pi-intercom/bridge-protocol.js +0 -2
- package/dist/pi-intercom/bridge-session.js +0 -1
- package/dist/pi-intercom/bridge-wake.js +0 -1
- package/dist/pi-intercom/broker/client.js +0 -2
- package/dist/pi-intercom/broker/spawn.js +0 -3
- package/dist/pi-intercom/inbound-delivery.js +2 -6
- package/dist/pi-intercom/reply-tracker.js +47 -55
- package/dist/pi-intercom/session-events.js +2 -0
- package/dist/pi-intercom/tool-registration.js +1 -2
- package/dist/pi-intercom/validation.js +0 -2
- package/dist/shared/cooperative.js +0 -1
- package/docs/code-quality.md +96 -68
- package/docs/intercom.md +6 -2
- package/package.json +9 -6
- package/scripts/build.mjs +11 -13
- package/scripts/check-compat.mjs +3 -2
- package/scripts/compat-host.mjs +5 -5
- package/scripts/intercom-bridge-credentials.mjs +4 -3
- package/scripts/intercom-remote.mjs +0 -1
- package/scripts/native-package-smoke-assertions.mjs +25 -23
- package/scripts/native-package-smoke.mjs +38 -54
- package/scripts/package-smoke.mjs +39 -30
- package/scripts/prepare.mjs +16 -10
- package/scripts/real-pi-events.mjs +92 -0
- package/scripts/real-pi-live.mjs +28 -108
- package/scripts/real-pi-processes.mjs +77 -82
- package/scripts/real-pi-smoke.mjs +20 -18
- package/src/agent/host.ts +1 -1
- package/src/agent/notifications.ts +11 -8
- package/src/agent/observe-layout.ts +3 -3
- package/src/agent/parking.ts +0 -1
- package/src/agent/process.ts +1 -4
- package/src/agent/runner.ts +0 -2
- package/src/agent/shutdown.ts +0 -1
- package/src/agent/typebox.ts +2 -2
- package/src/pi-intercom/bridge-presence.ts +0 -1
- package/src/pi-intercom/bridge-protocol.ts +0 -2
- package/src/pi-intercom/bridge-session.ts +0 -1
- package/src/pi-intercom/bridge-wake.ts +0 -1
- package/src/pi-intercom/broker/client.ts +0 -2
- package/src/pi-intercom/broker/spawn.ts +0 -3
- package/src/pi-intercom/inbound-delivery.ts +2 -6
- package/src/pi-intercom/reply-tracker.ts +50 -58
- package/src/pi-intercom/session-events.ts +3 -1
- package/src/pi-intercom/tool-registration.ts +1 -2
- package/src/pi-intercom/validation.ts +0 -2
- package/src/shared/cooperative.ts +0 -1
package/docs/code-quality.md
CHANGED
|
@@ -1,85 +1,113 @@
|
|
|
1
1
|
# Code quality
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
The [Strict Oxlint/Oxfmt Engineering Standard v5.2](strict-oxlint-oxfmt-standard.md)
|
|
4
|
+
owns the policy. [Quality exceptions](quality-exceptions.md) records the few
|
|
5
|
+
operations that require different treatment. No finding budget or suppression
|
|
6
|
+
baseline is accepted.
|
|
7
|
+
|
|
8
|
+
## Commands
|
|
9
|
+
|
|
10
|
+
Use the committed npm lockfile. Local checks are:
|
|
5
11
|
|
|
6
12
|
```sh
|
|
7
13
|
npm ci --ignore-scripts
|
|
8
14
|
npm run lint
|
|
9
|
-
npm run lint:fix
|
|
10
|
-
npm run format
|
|
11
15
|
npm run format:check
|
|
12
16
|
npm run typecheck
|
|
13
|
-
npm run
|
|
17
|
+
npm run typecheck:declarations
|
|
18
|
+
npm run check:quality
|
|
19
|
+
npm run build
|
|
14
20
|
```
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
remain checked by TypeScript. `typescript/prefer-readonly-parameter-types` is
|
|
19
|
-
disabled: the published backend cannot express the existing native SDK aliases
|
|
20
|
-
with package-scoped allowances. The project does not patch the checker or add
|
|
21
|
-
production wrappers to satisfy that rule.
|
|
22
|
+
`npm run lint:fix` and `npm run format` are writing conveniences.
|
|
23
|
+
`npm run lint:agent` is another output format for the same lint gate.
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
Full `npm run ci` remains hosted-only. It runs each independent gate even when
|
|
26
|
+
another fails: native lint, format, implementation types, handwritten declaration
|
|
27
|
+
types, policy and coverage, package size, package/install smokes, unit tests, and
|
|
28
|
+
integration tests. Package smokes build the actual artifacts before testing them.
|
|
29
|
+
The aggregate fails if any gate fails.
|
|
30
|
+
|
|
31
|
+
CI qualifies official Pi on Linux and the maintained fork on Linux and macOS.
|
|
32
|
+
Those are distinct runtime graphs, not interchangeable version labels. Android
|
|
33
|
+
has no published oxlint-tsgolint backend; native semantic qualification runs on
|
|
34
|
+
a supported host. A skipped local native probe is not hosted acceptance.
|
|
35
|
+
|
|
36
|
+
## Coverage and enforcement
|
|
37
|
+
|
|
38
|
+
`quality/check.ts` compares the effective policy with the normative JSON in the
|
|
39
|
+
standard and the explicit repository mappings. It checks native lint membership,
|
|
40
|
+
resolved compiler membership, formatter ownership, and required command scope.
|
|
41
|
+
The inventory includes ignored files outside generated, dependency, and private
|
|
42
|
+
runtime trees, so hiding maintained code with a Git ignore does not exempt it.
|
|
43
|
+
|
|
44
|
+
The compiler's own resolution checks production dependencies on test support,
|
|
45
|
+
including aliases, re-exports, and literal dynamic imports. Oxc's parser identifies
|
|
46
|
+
actual comments and operation-bound directives. Strings containing example
|
|
47
|
+
directives are data. Copied, moved, duplicate, stale, and wrong-operation
|
|
48
|
+
directives fail; unused directives also fail native lint.
|
|
49
|
+
|
|
50
|
+
The source profiles are:
|
|
27
51
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
- `src/**`, `scripts/**`, `quality/**`, and `.github/scripts/**` are production
|
|
53
|
+
implementation. Mixed-use helpers retain that profile.
|
|
54
|
+
- `test/**` contains tests, fixtures, and exclusively test-used support.
|
|
55
|
+
- `*.test-d.{ts,mts,cts}` are compiler-checked negative consumer contracts with
|
|
56
|
+
explained expected failures and passing counterparts.
|
|
57
|
+
- Handwritten `*.d.{ts,mts,cts}` files receive the declaration profile and an
|
|
58
|
+
actual `skipLibCheck:false` check through `quality/declarations.ts`.
|
|
31
59
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
60
|
+
The root `tsconfig.json` checks all maintained TS and JS with the full required
|
|
61
|
+
strict options. The application retains `skipLibCheck:true` because the installed
|
|
62
|
+
SDK dependency closure includes incompatible third-party declaration imports.
|
|
63
|
+
The declaration gate checks any handwritten declarations and their real dependency
|
|
64
|
+
closure without that option or permissive stubs. There are currently no handwritten
|
|
65
|
+
declaration files.
|
|
36
66
|
|
|
37
|
-
|
|
38
|
-
substitutes the
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
67
|
+
`tsconfig.build.json` and `tsconfig.client.json` are emission configurations, not
|
|
68
|
+
substitutes for the checked root project. Build-swap tests, packed-consumer type
|
|
69
|
+
tests, and actual package/native-install smokes verify their outputs.
|
|
70
|
+
|
|
71
|
+
Production limits are complexity 10, depth 3, parameters 4, statements 40,
|
|
72
|
+
function lines 80, and file lines 500. Tests retain complexity 15, depth 4, and
|
|
73
|
+
parameters 6 without statement or line limits. These are the standard's profiles,
|
|
74
|
+
not local exceptions. There is no React, DOM JSX, Jest, or Vitest implementation;
|
|
75
|
+
Node's test runner owns execution. A real failing assertion is qualified through
|
|
76
|
+
the repository test command.
|
|
77
|
+
|
|
78
|
+
Oxfmt owns supported maintained files, with width 100, two spaces, LF, double
|
|
79
|
+
quotes, and no import/package/Tailwind/JSDoc transformations. npm owns
|
|
80
|
+
`package-lock.json`; the build owns `dist/**` and temporary publication trees.
|
|
81
|
+
Captured `docs/evidence/*.log` files retain original command bytes. Git, Pi state,
|
|
82
|
+
dependency trees, npm cache, and OS metadata are not maintained source.
|
|
83
|
+
No committed generated output requires a separate freshness generator.
|
|
84
|
+
|
|
85
|
+
The existing viewer import restriction remains: lazy viewer values must come
|
|
86
|
+
from the selected host through `observe-layout.ts`. It strengthens the base
|
|
87
|
+
policy and protects runtime identity. The existing 1 MiB production-byte ceiling
|
|
88
|
+
also remains. Neither is a new requirement of this standard.
|
|
42
89
|
|
|
43
90
|
## Adoption record
|
|
44
91
|
|
|
45
|
-
Repository: `fitchmultz/pi-subagents`.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
`
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
4, parameters 6, no size limits) and declaration files with size limits off.
|
|
68
|
-
|
|
69
|
-
Formatter and generator ownership: oxfmt formats all maintained source at
|
|
70
|
-
width 100; npm owns lockfile formatting; build outputs keep their own bytes.
|
|
71
|
-
|
|
72
|
-
Migration status: complete. The toolchain, baseline configuration, strict
|
|
73
|
-
TypeScript compiler options, checked-JavaScript coverage and this records
|
|
74
|
-
policy landed as PRs #319, #320, #321, #325 and the records PR; the policy
|
|
75
|
-
integrity keeper `test/unit/strict-oxlint-policy.test.ts` pins the effective
|
|
76
|
-
configuration, the four-entry overrides inventory and the directive rule-name
|
|
77
|
-
inventory against drift.
|
|
78
|
-
|
|
79
|
-
Integrated verification: hosted `npm run ci` on the merge of each part, with
|
|
80
|
-
the single-agent-runtime, agent-receipts, pi-intercom-native-replay and
|
|
81
|
-
retired-history-archive keepers exercising the native contracts. Locally
|
|
82
|
-
verified state for the checked-JavaScript part is recorded in PR #325; the
|
|
83
|
-
retired-history-archive suite requires passwordless `sudo` and
|
|
84
|
-
`scripts/package-smoke.mjs` requires a short Unix socket directory, so hosted
|
|
85
|
-
CI is the authoritative gate for both.
|
|
92
|
+
Repository: `fitchmultz/pi-subagents`. Standard: v5.2, 3 November 2026.
|
|
93
|
+
Runtime: Node `>=24.21.0`, npm, committed lockfile.
|
|
94
|
+
Qualified tools: Oxlint 1.91.0, oxlint-tsgolint 7.0.2006, Oxfmt 0.76.0,
|
|
95
|
+
TypeScript 7.0.2, oxc-parser 0.157.0, and `@types/node` 24.19.2.
|
|
96
|
+
|
|
97
|
+
The adoption starts at `7f449e2dcc8e7e82432bea79a2709f0b49256239`.
|
|
98
|
+
The adoption PR records the exact integrated head and hosted results; CI verifies
|
|
99
|
+
the source SHA before qualification. Local M1 evidence uses Node 24.22.1 and
|
|
100
|
+
official Pi 1.5.0; separate native fork coverage runs against its selected graph.
|
|
101
|
+
|
|
102
|
+
Qualification lives in `strict-oxlint-qualification.test.ts`,
|
|
103
|
+
`lint-node-test-allowance.test.ts`, `quality-coverage.test.ts`,
|
|
104
|
+
`strict-oxlint-policy.test.ts`, and `quality-acceptance.test.ts`.
|
|
105
|
+
These cover semantic and category rules, all six structural boundaries, Node
|
|
106
|
+
registration origins, operation attachment, strict JS/TS/declaration checking,
|
|
107
|
+
formatting idempotence, coverage omissions, and acceptance exit propagation.
|
|
108
|
+
The adoption also exercised each aggregate gate's failure independently; ordinary
|
|
109
|
+
regression coverage retains one representative failure plus the all-green case.
|
|
110
|
+
|
|
111
|
+
The policy and source repairs are integrated. Completion requires the adoption
|
|
112
|
+
PR's exact-head hosted checks and release verification; local passes alone do not
|
|
113
|
+
establish complete adoption.
|
package/docs/intercom.md
CHANGED
|
@@ -330,8 +330,12 @@ not an end-to-end exactly-once guarantee for arbitrary tool side effects.
|
|
|
330
330
|
|
|
331
331
|
Idle recovery preserves admission order within the non-passive backlog and wakes
|
|
332
332
|
once on its last item; passive breadcrumbs are appended before that waking work.
|
|
333
|
-
The first eligible ask remains the implicit reply target, independently
|
|
334
|
-
message triggers the turn.
|
|
333
|
+
The first unresolved eligible ask remains the implicit reply target, independently
|
|
334
|
+
of which message triggers the turn. Reply attribution refers to the unresolved
|
|
335
|
+
ask by ID. Placing delayed input does not revive an already replied, expired, or
|
|
336
|
+
retired reply obligation. A failed turn can retire only an ask whose input Pi has
|
|
337
|
+
consumed; queued input remains answerable after abort recovery.
|
|
338
|
+
A busy recipient's steer is handed to Pi's native
|
|
335
339
|
queue and consumed at its next tool boundary; no extension probe asks another
|
|
336
340
|
tool to give up the turn.
|
|
337
341
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitchmultz/pi-subagents",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.18",
|
|
4
4
|
"description": "Pi extension for subagent orchestration and local session coordination",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -68,8 +68,10 @@
|
|
|
68
68
|
"lint:agent": "oxlint --type-aware --deny-warnings --format=agent .",
|
|
69
69
|
"format": "oxfmt --write .",
|
|
70
70
|
"format:check": "oxfmt --check .",
|
|
71
|
-
"ci": "
|
|
72
|
-
"smoke:real-pi": "node scripts/real-pi-smoke.mjs"
|
|
71
|
+
"ci": "node quality/acceptance.ts",
|
|
72
|
+
"smoke:real-pi": "node scripts/real-pi-smoke.mjs",
|
|
73
|
+
"check:quality": "node quality/check.ts",
|
|
74
|
+
"typecheck:declarations": "node quality/declarations.ts"
|
|
73
75
|
},
|
|
74
76
|
"pi": {
|
|
75
77
|
"extensions": [
|
|
@@ -110,9 +112,10 @@
|
|
|
110
112
|
"@earendil-works/pi-tui": "1.5.0",
|
|
111
113
|
"@types/node": "24.19.2",
|
|
112
114
|
"@types/sinon": "22.0.2",
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"oxlint
|
|
115
|
+
"oxc-parser": "0.157.0",
|
|
116
|
+
"oxfmt": "0.76.0",
|
|
117
|
+
"oxlint": "1.91.0",
|
|
118
|
+
"oxlint-tsgolint": "7.0.2006",
|
|
116
119
|
"sinon": "22.1.0",
|
|
117
120
|
"type-fest": "5.10.0",
|
|
118
121
|
"typebox": "1.4.3",
|
package/scripts/build.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { execFile as execFileCallback } from "node:child_process";
|
|
14
|
+
import assert from "node:assert/strict";
|
|
14
15
|
import { createHash } from "node:crypto";
|
|
15
16
|
import { existsSync } from "node:fs";
|
|
16
17
|
import { readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
@@ -21,7 +22,6 @@ import { promisify } from "node:util";
|
|
|
21
22
|
import { compilerInvocation } from "./typescript.mjs";
|
|
22
23
|
|
|
23
24
|
/** @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
25
|
const execFile = promisify(execFileCallback);
|
|
26
26
|
const RM_OPTIONS = { force: true, maxRetries: 5, recursive: true, retryDelay: 100 };
|
|
27
27
|
|
|
@@ -80,7 +80,6 @@ async function reapStrandedStaging(/** @type {string} */ cwd) {
|
|
|
80
80
|
continue;
|
|
81
81
|
}
|
|
82
82
|
// Reap abandoned trees sequentially to bound filesystem removal pressure.
|
|
83
|
-
// oxlint-disable-next-line no-await-in-loop
|
|
84
83
|
await discardStaging(join(cwd, entry.name));
|
|
85
84
|
}
|
|
86
85
|
}
|
|
@@ -114,13 +113,13 @@ async function compileToStaging(
|
|
|
114
113
|
await compileProject(cwd, stagingDir, compiler, "tsconfig.client.json");
|
|
115
114
|
await stampBuild(cwd, stagingDir);
|
|
116
115
|
} catch (error) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
if (typeof error === "object" && error !== null) {
|
|
117
|
+
if ("stdout" in error && typeof error.stdout === "string") {
|
|
118
|
+
process.stdout.write(error.stdout);
|
|
119
|
+
}
|
|
120
|
+
if ("stderr" in error && typeof error.stderr === "string") {
|
|
121
|
+
process.stderr.write(error.stderr);
|
|
122
|
+
}
|
|
124
123
|
}
|
|
125
124
|
await discardStaging(stagingDir);
|
|
126
125
|
throw error;
|
|
@@ -135,13 +134,13 @@ async function stampBuild(/** @type {string} */ cwd, /** @type {string} */ stagi
|
|
|
135
134
|
.sort();
|
|
136
135
|
for (const file of files) {
|
|
137
136
|
// Hash sorted path/content pairs in order without buffering the whole emitted tree.
|
|
138
|
-
// oxlint-disable-next-line no-await-in-loop
|
|
139
137
|
const content = await readFile(join(stagingDir, file));
|
|
140
138
|
hash.update(file).update("\0").update(content).update("\0");
|
|
141
139
|
}
|
|
142
|
-
/** @type {
|
|
143
|
-
// oxlint-disable-next-line typescript/no-unsafe-assignment
|
|
140
|
+
/** @type {unknown} */
|
|
144
141
|
const pkg = JSON.parse(await readFile(join(cwd, "package.json"), "utf8"));
|
|
142
|
+
assert.ok(typeof pkg === "object" && pkg !== null && "version" in pkg);
|
|
143
|
+
assert.ok(typeof pkg.version === "string");
|
|
145
144
|
const { version } = pkg;
|
|
146
145
|
await writeFile(
|
|
147
146
|
join(stagingDir, stamp),
|
|
@@ -184,7 +183,6 @@ async function publishStaging(/** @type {string} */ stagingDir, /** @type {strin
|
|
|
184
183
|
});
|
|
185
184
|
for (let retry = 0; ; retry++) {
|
|
186
185
|
// Each retry must observe publication before another rename can race it.
|
|
187
|
-
// oxlint-disable-next-line no-await-in-loop
|
|
188
186
|
if (await tryPublish(stagingDir, distDir, retry)) {
|
|
189
187
|
break;
|
|
190
188
|
}
|
package/scripts/check-compat.mjs
CHANGED
|
@@ -19,6 +19,8 @@ const run = (
|
|
|
19
19
|
/** @type {import("node:child_process").SpawnSyncOptions} */ options = {},
|
|
20
20
|
) => {
|
|
21
21
|
// Outlasts the 1_200_000 ms integration watchdog in scripts/run-tests.mjs.
|
|
22
|
+
// Inherited stdio produces null at runtime despite SpawnSyncReturns<T>'s declaration.
|
|
23
|
+
/** @type {import("node:child_process").SpawnSyncReturns<string | Buffer | null>} */
|
|
22
24
|
const result = spawnSync(command, args, { stdio: "inherit", timeout: 1_500_000, ...options });
|
|
23
25
|
if (result.error) {
|
|
24
26
|
throw result.error;
|
|
@@ -28,8 +30,7 @@ const run = (
|
|
|
28
30
|
0,
|
|
29
31
|
`${command} ${args.join(" ")} failed (${result.status ?? result.signal ?? "unknown"})`,
|
|
30
32
|
);
|
|
31
|
-
|
|
32
|
-
return /** @type {string | undefined} */ (/** @type {unknown} */ (result.stdout))?.trim();
|
|
33
|
+
return result.stdout === null ? undefined : result.stdout.toString().trim();
|
|
33
34
|
};
|
|
34
35
|
const git = (/** @type {string} */ cwd, /** @type {string[]} */ args) =>
|
|
35
36
|
run("git", args, { cwd, stdio: ["ignore", "pipe", "inherit"], encoding: "utf8" });
|
package/scripts/compat-host.mjs
CHANGED
|
@@ -12,9 +12,12 @@ if (hostPackageJson === undefined) {
|
|
|
12
12
|
}
|
|
13
13
|
export const hostRoot = realpathSync(dirname(hostPackageJson));
|
|
14
14
|
export const hostIndex = join(hostRoot, "dist/index.js");
|
|
15
|
-
/** @type {
|
|
16
|
-
// oxlint-disable-next-line typescript/no-unsafe-assignment
|
|
15
|
+
/** @type {unknown} */
|
|
17
16
|
const manifest = JSON.parse(readFileSync(join(hostRoot, "package.json"), "utf8"));
|
|
17
|
+
assert.ok(typeof manifest === "object" && manifest !== null);
|
|
18
|
+
assert.ok("version" in manifest && typeof manifest.version === "string");
|
|
19
|
+
assert.ok("bin" in manifest && typeof manifest.bin === "object" && manifest.bin !== null);
|
|
20
|
+
assert.ok("pi" in manifest.bin && typeof manifest.bin.pi === "string");
|
|
18
21
|
export const hostCli = realpathSync(join(hostRoot, manifest.bin.pi));
|
|
19
22
|
/** @type {[string, string][]} */
|
|
20
23
|
const expectedHostPaths = [
|
|
@@ -26,7 +29,6 @@ for (const [name, actual] of expectedHostPaths) {
|
|
|
26
29
|
const expected = process.env[name];
|
|
27
30
|
if (expected !== undefined) {
|
|
28
31
|
assert.equal(
|
|
29
|
-
// oxlint-disable-next-line typescript/no-unnecessary-condition -- assert generic inference already matches
|
|
30
32
|
realpathSync(expected),
|
|
31
33
|
realpathSync(actual),
|
|
32
34
|
`${name} must select the installed graph`,
|
|
@@ -35,11 +37,9 @@ for (const [name, actual] of expectedHostPaths) {
|
|
|
35
37
|
}
|
|
36
38
|
const expectedVersion = process.env.PI_COMPAT_EXPECTED_VERSION;
|
|
37
39
|
if (expectedVersion !== undefined) {
|
|
38
|
-
// oxlint-disable-next-line typescript/no-unnecessary-condition -- assert generic inference already matches
|
|
39
40
|
assert.equal(manifest.version, expectedVersion);
|
|
40
41
|
}
|
|
41
42
|
assert.equal(
|
|
42
|
-
// oxlint-disable-next-line typescript/no-unnecessary-condition -- assert generic inference already matches
|
|
43
43
|
realpathSync(fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"))),
|
|
44
44
|
realpathSync(hostIndex),
|
|
45
45
|
);
|
|
@@ -161,8 +161,7 @@ function issueCertificate(
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
function readConfig(/** @type {string} */ path) {
|
|
164
|
-
/** @type {
|
|
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
|
-
|
|
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 =
|
|
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
|
|
27
|
-
entry.message
|
|
28
|
-
entry.message
|
|
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
|
|
38
|
-
entry.message
|
|
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
|
-
|
|
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(
|