@tryinget/pi-agent-vent 0.1.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.
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ cd "$ROOT_DIR"
6
+
7
+ NAME="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).name")"
8
+ VERSION="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).version")"
9
+ REPOSITORY_URL="$(node -p "(() => { const pkg = JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')); const repo = pkg.repository; if (typeof repo === 'string') return repo.trim(); if (repo && typeof repo === 'object' && typeof repo.url === 'string') return repo.url.trim(); return ''; })()")"
10
+
11
+ echo "== release-check: ${NAME}@${VERSION}"
12
+
13
+ if [[ -z "$REPOSITORY_URL" ]]; then
14
+ echo "package.json repository.url is required for provenance release publishing." >&2
15
+ exit 1
16
+ fi
17
+
18
+ if [[ "$NAME" != "${NAME,,}" ]]; then
19
+ echo "Invalid npm package name: must be lowercase: $NAME" >&2
20
+ exit 1
21
+ fi
22
+
23
+ echo "== npm pack --dry-run --json"
24
+ PACK_JSON="$(npm pack --dry-run --json)"
25
+ echo "$PACK_JSON"
26
+
27
+ PACK_JSON="$PACK_JSON" node ./scripts/release-artifact-check.mjs
28
+
29
+ echo "== npm publish --dry-run"
30
+ set +e
31
+ PUBLISH_DRY_RUN_OUTPUT="$(npm publish --dry-run 2>&1)"
32
+ PUBLISH_DRY_RUN_EXIT=$?
33
+ set -e
34
+ echo "$PUBLISH_DRY_RUN_OUTPUT"
35
+ if [[ "$PUBLISH_DRY_RUN_EXIT" -ne 0 ]]; then
36
+ if grep -qiE "You cannot publish over the previously published versions|previously published version .* is higher than the new version" <<<"$PUBLISH_DRY_RUN_OUTPUT"; then
37
+ echo "npm publish --dry-run hit registry version guard (${VERSION}); continuing."
38
+ else
39
+ echo "npm publish --dry-run failed." >&2
40
+ exit "$PUBLISH_DRY_RUN_EXIT"
41
+ fi
42
+ fi
43
+
44
+ TEST_AGENT_DIR=""
45
+ TEST_NPM_PREFIX=""
46
+ TEST_NPM_CACHE=""
47
+ ARTIFACT_NPM_PREFIX=""
48
+ ARTIFACT_NPM_CACHE=""
49
+ ARTIFACT_TOOL_VENT_DIR=""
50
+ TARBALL_CHECK_DIR=""
51
+ TARBALL_PATH=""
52
+ cleanup() {
53
+ if [[ "${KEEP_RELEASE_ARTIFACTS:-0}" != "1" ]]; then
54
+ if [[ -n "$TEST_AGENT_DIR" && -d "$TEST_AGENT_DIR" ]]; then
55
+ rm -rf "$TEST_AGENT_DIR"
56
+ fi
57
+ if [[ -n "$TEST_NPM_PREFIX" && -d "$TEST_NPM_PREFIX" ]]; then
58
+ rm -rf "$TEST_NPM_PREFIX"
59
+ fi
60
+ if [[ -n "$TEST_NPM_CACHE" && -d "$TEST_NPM_CACHE" ]]; then
61
+ rm -rf "$TEST_NPM_CACHE"
62
+ fi
63
+ if [[ -n "$ARTIFACT_NPM_PREFIX" && -d "$ARTIFACT_NPM_PREFIX" ]]; then
64
+ rm -rf "$ARTIFACT_NPM_PREFIX"
65
+ fi
66
+ if [[ -n "$ARTIFACT_NPM_CACHE" && -d "$ARTIFACT_NPM_CACHE" ]]; then
67
+ rm -rf "$ARTIFACT_NPM_CACHE"
68
+ fi
69
+ if [[ -n "$ARTIFACT_TOOL_VENT_DIR" && -d "$ARTIFACT_TOOL_VENT_DIR" ]]; then
70
+ rm -rf "$ARTIFACT_TOOL_VENT_DIR"
71
+ fi
72
+ if [[ -n "$TARBALL_CHECK_DIR" && -d "$TARBALL_CHECK_DIR" ]]; then
73
+ rm -rf "$TARBALL_CHECK_DIR"
74
+ fi
75
+ if [[ -n "$TARBALL_PATH" && -f "$TARBALL_PATH" ]]; then
76
+ rm -f "$TARBALL_PATH"
77
+ fi
78
+ fi
79
+ }
80
+ trap cleanup EXIT
81
+
82
+ echo "== npm pack"
83
+ TARBALL="$(npm pack --silent | tail -n 1)"
84
+ TARBALL_PATH="$ROOT_DIR/$TARBALL"
85
+ echo "Tarball: $TARBALL_PATH"
86
+
87
+ TARBALL_CHECK_DIR="$(mktemp -d /tmp/pi-agent-vent-tarball-check-XXXXXX)"
88
+ echo "== unpacked tarball package contract"
89
+ tar -xzf "$TARBALL_PATH" -C "$TARBALL_CHECK_DIR"
90
+ (
91
+ cd "$TARBALL_CHECK_DIR/package"
92
+ npm install --ignore-scripts --no-audit --fund=false
93
+ npm run check
94
+ )
95
+
96
+ ARTIFACT_NPM_PREFIX="$(mktemp -d /tmp/pi-agent-vent-artifact-npm-prefix-XXXXXX)"
97
+ ARTIFACT_NPM_CACHE="$(mktemp -d /tmp/pi-agent-vent-artifact-npm-cache-XXXXXX)"
98
+ ARTIFACT_TOOL_VENT_DIR="$(mktemp -d /tmp/pi-agent-vent-artifact-tool-store-XXXXXX)"
99
+ ARTIFACT_PACKAGE_ROOT="$(npm --prefix "$ARTIFACT_NPM_PREFIX" root -g)/$NAME"
100
+
101
+ case "$ARTIFACT_PACKAGE_ROOT" in
102
+ "$ARTIFACT_NPM_PREFIX"/*) ;;
103
+ *)
104
+ echo "Artifact package root escaped isolated npm prefix: $ARTIFACT_PACKAGE_ROOT" >&2
105
+ exit 1
106
+ ;;
107
+ esac
108
+
109
+ echo "== npm installed artifact shadow registered-tool smoke (no Pi auth)"
110
+ npm --prefix "$ARTIFACT_NPM_PREFIX" --cache "$ARTIFACT_NPM_CACHE" \
111
+ install --global --ignore-scripts --no-audit --fund=false "$TARBALL_PATH"
112
+ node ./scripts/release-smoke-check.mjs assert-installed-artifact \
113
+ --package-root "$ARTIFACT_PACKAGE_ROOT" \
114
+ --package-name "$NAME" \
115
+ --package-version "$VERSION"
116
+ node ./scripts/release-smoke-check.mjs assert-installed-tool-path \
117
+ --package-root "$ARTIFACT_PACKAGE_ROOT" \
118
+ --vent-dir "$ARTIFACT_TOOL_VENT_DIR"
119
+
120
+ if [[ "${SKIP_PI_SMOKE:-0}" == "1" ]]; then
121
+ echo "Skipping pi smoke tests (SKIP_PI_SMOKE=1)."
122
+ else
123
+ if ! command -v pi >/dev/null 2>&1; then
124
+ echo "pi CLI not found in PATH." >&2
125
+ exit 1
126
+ fi
127
+ if [[ ! -f "$HOME/.pi/agent/auth.json" ]]; then
128
+ echo "Missing $HOME/.pi/agent/auth.json (needed for isolated pi smoke tests)." >&2
129
+ echo "Tip: set SKIP_PI_SMOKE=1 for artifact-only checks." >&2
130
+ exit 1
131
+ fi
132
+
133
+ TEST_AGENT_DIR="$(mktemp -d /tmp/pi-extension-release-check-XXXXXX)"
134
+ TEST_NPM_PREFIX="$(mktemp -d /tmp/pi-extension-release-npm-prefix-XXXXXX)"
135
+ TEST_NPM_CACHE="$(mktemp -d /tmp/pi-extension-release-npm-cache-XXXXXX)"
136
+
137
+ cp "$HOME/.pi/agent/auth.json" "$TEST_AGENT_DIR/auth.json"
138
+
139
+ # Allow override via environment variables for different provider configurations
140
+ PI_TEST_DEFAULT_PROVIDER="${PI_TEST_DEFAULT_PROVIDER:-openai}"
141
+ PI_TEST_DEFAULT_MODEL="${PI_TEST_DEFAULT_MODEL:-gpt-4o}"
142
+ PI_TEST_ENABLED_MODELS="${PI_TEST_ENABLED_MODELS:-[\"openai/gpt-4*\"]}"
143
+
144
+ cat > "$TEST_AGENT_DIR/settings.json" <<JSON
145
+ {
146
+ "defaultProvider": "${PI_TEST_DEFAULT_PROVIDER}",
147
+ "defaultModel": "${PI_TEST_DEFAULT_MODEL}",
148
+ "enabledModels": ${PI_TEST_ENABLED_MODELS},
149
+ "npmCommand": ["npm", "--prefix", "${TEST_NPM_PREFIX}", "--cache", "${TEST_NPM_CACHE}"],
150
+ "extensions": []
151
+ }
152
+ JSON
153
+
154
+ echo "== pi install tarball (isolated PI_CODING_AGENT_DIR and npm prefix)"
155
+ PACKAGE_SPEC="npm:$TARBALL_PATH"
156
+ NPM_CONFIG_PREFIX="$TEST_NPM_PREFIX" NPM_CONFIG_CACHE="$TEST_NPM_CACHE" \
157
+ PI_CODING_AGENT_DIR="$TEST_AGENT_DIR" pi install "$PACKAGE_SPEC"
158
+
159
+ echo "== verify tarball package recorded in settings"
160
+ TEST_AGENT_DIR="$TEST_AGENT_DIR" PACKAGE_SPEC="$PACKAGE_SPEC" node <<'NODE'
161
+ const fs = require("node:fs");
162
+ const path = require("node:path");
163
+ const settingsPath = path.join(process.env.TEST_AGENT_DIR, "settings.json");
164
+ const packageSpec = process.env.PACKAGE_SPEC;
165
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
166
+ const packages = Array.isArray(settings.packages) ? settings.packages : [];
167
+ const found = packages.some((entry) => {
168
+ if (typeof entry === "string") return entry === packageSpec;
169
+ if (entry && typeof entry === "object") return entry.source === packageSpec;
170
+ return false;
171
+ });
172
+ if (!found) {
173
+ console.error(`Could not find ${packageSpec} in settings.packages`);
174
+ process.exit(1);
175
+ }
176
+ console.log("Tarball package entry present in settings.packages.");
177
+ NODE
178
+
179
+ if [[ -x "./scripts/release-smoke.sh" ]]; then
180
+ echo "== extension-specific smoke checks (scripts/release-smoke.sh)"
181
+ NPM_CONFIG_PREFIX="$TEST_NPM_PREFIX" NPM_CONFIG_CACHE="$TEST_NPM_CACHE" \
182
+ PI_CODING_AGENT_DIR="$TEST_AGENT_DIR" PACKAGE_SPEC="$PACKAGE_SPEC" bash ./scripts/release-smoke.sh
183
+ fi
184
+ fi
185
+
186
+ echo "== npm view ${NAME} version (pre-publish may be 404)"
187
+ set +e
188
+ npm view "$NAME" version --json --registry https://registry.npmjs.org/
189
+ VIEW_EXIT=$?
190
+ set -e
191
+ echo "npm view exit: $VIEW_EXIT"
192
+ if [[ "$VIEW_EXIT" -ne 0 ]]; then
193
+ echo "Package likely not published yet (expected for first release)."
194
+ fi
195
+
196
+ echo "release-check done"
@@ -0,0 +1,367 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ export const readJsonFile = (filePath) => JSON.parse(fs.readFileSync(filePath, "utf8"));
7
+
8
+ export const packageSourcesFromSettings = (settings) => {
9
+ const packages = Array.isArray(settings?.packages) ? settings.packages : [];
10
+ return packages
11
+ .map((entry) => {
12
+ if (typeof entry === "string") return entry;
13
+ if (entry && typeof entry === "object" && typeof entry.source === "string") {
14
+ return entry.source;
15
+ }
16
+ return undefined;
17
+ })
18
+ .filter(Boolean);
19
+ };
20
+
21
+ export const findPackageEntryBySource = (settings, packageSpec) => {
22
+ const packages = Array.isArray(settings?.packages) ? settings.packages : [];
23
+ return packages.find((entry) => {
24
+ if (typeof entry === "string") return entry === packageSpec;
25
+ return entry && typeof entry === "object" && entry.source === packageSpec;
26
+ });
27
+ };
28
+
29
+ export const assertPackageSpecInstalled = ({ settings, packageSpec }) => {
30
+ if (!packageSpec || typeof packageSpec !== "string") {
31
+ throw new Error("PACKAGE_SPEC is required for release smoke validation.");
32
+ }
33
+
34
+ const entry = findPackageEntryBySource(settings, packageSpec);
35
+ if (!entry) {
36
+ const sources = packageSourcesFromSettings(settings);
37
+ throw new Error(
38
+ [
39
+ `Installed package spec not found in isolated pi settings: ${packageSpec}`,
40
+ `Found: ${sources.join(", ") || "(none)"}`,
41
+ ].join("\n"),
42
+ );
43
+ }
44
+
45
+ if (typeof entry !== "string") {
46
+ throw new Error(
47
+ "Release smoke requires an unfiltered package entry; object-form package filters could disable the extension under test.",
48
+ );
49
+ }
50
+ };
51
+
52
+ const normalizePathForOutput = (value) => path.resolve(value).replace(/\\/g, "/");
53
+ const normalizeOutput = (value) => String(value || "").replace(/\\/g, "/");
54
+
55
+ export const assertInstalledArtifactPackage = ({ packageRoot, packageName, packageVersion }) => {
56
+ if (!packageRoot || typeof packageRoot !== "string") {
57
+ throw new Error("packageRoot is required for release smoke validation.");
58
+ }
59
+
60
+ const packageJsonPath = path.join(packageRoot, "package.json");
61
+ const extensionPath = path.join(packageRoot, "extensions", "agent-vent.ts");
62
+ if (!fs.existsSync(packageJsonPath)) {
63
+ throw new Error(`Installed package.json not found: ${packageJsonPath}`);
64
+ }
65
+ if (!fs.existsSync(extensionPath)) {
66
+ throw new Error(`Installed agent_vent extension not found: ${extensionPath}`);
67
+ }
68
+
69
+ const pkg = readJsonFile(packageJsonPath);
70
+ if (pkg.name !== packageName) {
71
+ throw new Error(`Installed package name mismatch: expected ${packageName}, got ${pkg.name}`);
72
+ }
73
+ if (pkg.version !== packageVersion) {
74
+ throw new Error(
75
+ `Installed package version mismatch: expected ${packageVersion}, got ${pkg.version}`,
76
+ );
77
+ }
78
+ const extensions = Array.isArray(pkg.pi?.extensions) ? pkg.pi.extensions : [];
79
+ if (!extensions.includes("./extensions/agent-vent.ts")) {
80
+ throw new Error("Installed package pi.extensions does not include ./extensions/agent-vent.ts");
81
+ }
82
+
83
+ return { packageJsonPath, extensionPath };
84
+ };
85
+
86
+ export const assertLocalTarballInstallSource = ({ packageSpec }) => {
87
+ if (!packageSpec || typeof packageSpec !== "string") {
88
+ throw new Error("PACKAGE_SPEC is required for local tarball install-source validation.");
89
+ }
90
+ if (!packageSpec.startsWith("npm:")) {
91
+ throw new Error(`Release smoke expected an npm: tarball install source, got: ${packageSpec}`);
92
+ }
93
+
94
+ const tarballPath = packageSpec.slice("npm:".length);
95
+ if (!path.isAbsolute(tarballPath) || !tarballPath.endsWith(".tgz")) {
96
+ throw new Error(
97
+ `Release smoke expected npm:<absolute .tgz path> as the install source, got: ${packageSpec}`,
98
+ );
99
+ }
100
+
101
+ return { tarballPath };
102
+ };
103
+
104
+ export const buildLocalPathArtifactSettings = ({ settings, packageRoot }) => {
105
+ if (!packageRoot || typeof packageRoot !== "string") {
106
+ throw new Error("packageRoot is required for release smoke settings preparation.");
107
+ }
108
+
109
+ return {
110
+ ...settings,
111
+ packages: [packageRoot],
112
+ extensions: [],
113
+ };
114
+ };
115
+
116
+ export const buildInstalledArtifactSettings = buildLocalPathArtifactSettings;
117
+
118
+ export const assertAgentVentPathSmokeOutput = ({ output, ventDir }) => {
119
+ if (!ventDir || typeof ventDir !== "string") {
120
+ throw new Error("ventDir is required for release smoke validation.");
121
+ }
122
+
123
+ const normalizedOutput = normalizeOutput(output);
124
+ const normalizedVentDir = normalizePathForOutput(ventDir);
125
+ const expectedStore = `${normalizedVentDir}/vents.jsonl`;
126
+ const expectedReview = `${normalizedVentDir}/review-events.jsonl`;
127
+ const expectedCuration = `${normalizedVentDir}/curation-events.jsonl`;
128
+ const expectedRetention = `${normalizedVentDir}/retention-events.jsonl`;
129
+ const expectedBackups = `${normalizedVentDir}/backups`;
130
+
131
+ const requiredFragments = [
132
+ `Agent vent store: ${expectedStore}`,
133
+ `Agent vent review events: ${expectedReview}`,
134
+ `Agent vent curation events: ${expectedCuration}`,
135
+ `Agent vent retention events: ${expectedRetention}`,
136
+ `Agent vent retention backups: ${expectedBackups}`,
137
+ "Override: set PI_AGENT_VENT_DIR",
138
+ "not tasks, issues, incidents, evidence, telemetry, or ASC/self state",
139
+ ];
140
+
141
+ const missing = requiredFragments.filter((fragment) => !normalizedOutput.includes(fragment));
142
+ if (missing.length) {
143
+ throw new Error(
144
+ [
145
+ "Installed /agent_vent path smoke output did not match the expected local diagnostic contract.",
146
+ ...missing.map((fragment) => `Missing: ${fragment}`),
147
+ ].join("\n"),
148
+ );
149
+ }
150
+
151
+ const defaultStoreFragment = "/.pi/agent/agent-vent/vents.jsonl";
152
+ if (normalizedOutput.includes(defaultStoreFragment)) {
153
+ throw new Error(
154
+ "Installed /agent_vent path smoke used the default operator vent store instead of the isolated PI_AGENT_VENT_DIR.",
155
+ );
156
+ }
157
+ };
158
+
159
+ const activeStoreArtifacts = (ventDir) => [
160
+ path.join(ventDir, "vents.jsonl"),
161
+ path.join(ventDir, "review-events.jsonl"),
162
+ path.join(ventDir, "curation-events.jsonl"),
163
+ path.join(ventDir, "retention-events.jsonl"),
164
+ path.join(ventDir, "backups"),
165
+ ];
166
+
167
+ const findNodeModulesAncestor = (startPath) => {
168
+ let current = path.resolve(startPath);
169
+ while (current && current !== path.dirname(current)) {
170
+ if (path.basename(current) === "node_modules") return current;
171
+ current = path.dirname(current);
172
+ }
173
+ return undefined;
174
+ };
175
+
176
+ const createImportablePackageShadow = (packageRoot) => {
177
+ const resolvedPackageRoot = path.resolve(packageRoot);
178
+ const shadowParent = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-installed-shadow-"));
179
+ const shadowRoot = path.join(shadowParent, "package");
180
+ fs.cpSync(resolvedPackageRoot, shadowRoot, {
181
+ recursive: true,
182
+ filter: (sourcePath) => {
183
+ const relativePath = path.relative(resolvedPackageRoot, sourcePath);
184
+ return !relativePath.split(path.sep).includes("node_modules");
185
+ },
186
+ });
187
+
188
+ const packageNodeModules = path.join(packageRoot, "node_modules");
189
+ const dependencyRoot = fs.existsSync(packageNodeModules)
190
+ ? packageNodeModules
191
+ : findNodeModulesAncestor(packageRoot);
192
+ if (dependencyRoot) {
193
+ fs.symlinkSync(dependencyRoot, path.join(shadowRoot, "node_modules"), "dir");
194
+ }
195
+
196
+ return { shadowParent, shadowRoot };
197
+ };
198
+
199
+ export const executeInstalledArtifactToolPathSmoke = async ({ packageRoot, ventDir }) => {
200
+ if (!packageRoot || typeof packageRoot !== "string") {
201
+ throw new Error("packageRoot is required for installed tool smoke validation.");
202
+ }
203
+ if (!ventDir || typeof ventDir !== "string") {
204
+ throw new Error("ventDir is required for installed tool smoke validation.");
205
+ }
206
+
207
+ const installedExtensionPath = path.join(packageRoot, "extensions", "agent-vent.ts");
208
+ if (!fs.existsSync(installedExtensionPath)) {
209
+ throw new Error(`Installed agent_vent extension not found: ${installedExtensionPath}`);
210
+ }
211
+
212
+ const tools = new Map();
213
+ const commands = new Map();
214
+ const shadow = createImportablePackageShadow(packageRoot);
215
+ try {
216
+ const extensionPath = path.join(shadow.shadowRoot, "extensions", "agent-vent.ts");
217
+ const extensionModule = await import(pathToFileURL(extensionPath).href);
218
+ if (typeof extensionModule.default !== "function") {
219
+ throw new Error(
220
+ `Installed agent_vent extension has no default factory: ${installedExtensionPath}`,
221
+ );
222
+ }
223
+
224
+ extensionModule.default({
225
+ registerTool(tool) {
226
+ if (tool?.name) tools.set(tool.name, tool);
227
+ },
228
+ registerCommand(name, command) {
229
+ commands.set(name, command);
230
+ },
231
+ });
232
+ } finally {
233
+ fs.rmSync(shadow.shadowParent, { recursive: true, force: true });
234
+ }
235
+
236
+ const tool = tools.get("agent_vent");
237
+ if (!tool || typeof tool.execute !== "function") {
238
+ throw new Error("Installed agent_vent extension did not register executable agent_vent tool.");
239
+ }
240
+
241
+ const artifacts = activeStoreArtifacts(ventDir);
242
+ const existingArtifacts = new Set(
243
+ artifacts.filter((artifactPath) => fs.existsSync(artifactPath)),
244
+ );
245
+ const oldVentDir = process.env.PI_AGENT_VENT_DIR;
246
+ let result;
247
+ try {
248
+ process.env.PI_AGENT_VENT_DIR = ventDir;
249
+ result = await tool.execute(
250
+ "release-smoke-tool-path",
251
+ { action: "path" },
252
+ undefined,
253
+ undefined,
254
+ {
255
+ cwd: packageRoot,
256
+ sessionManager: { getSessionFile: () => undefined },
257
+ },
258
+ );
259
+ } finally {
260
+ if (oldVentDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
261
+ else process.env.PI_AGENT_VENT_DIR = oldVentDir;
262
+ }
263
+
264
+ const output = result?.content?.[0]?.text;
265
+ if (typeof output !== "string") {
266
+ throw new Error("Installed agent_vent tool path smoke did not return text content.");
267
+ }
268
+
269
+ assertAgentVentPathSmokeOutput({ output, ventDir });
270
+
271
+ const createdArtifacts = artifacts.filter(
272
+ (artifactPath) => !existingArtifacts.has(artifactPath) && fs.existsSync(artifactPath),
273
+ );
274
+ if (createdArtifacts.length) {
275
+ throw new Error(
276
+ [
277
+ "Installed agent_vent tool path smoke created local store artifacts; action=path must stay no-store-read/no-store-write.",
278
+ ...createdArtifacts.map((artifactPath) => `Created: ${artifactPath}`),
279
+ ].join("\n"),
280
+ );
281
+ }
282
+
283
+ return { output, executionMode: "shadow-copy", registeredCommandCount: commands.size };
284
+ };
285
+
286
+ const readArgValue = (args, name) => {
287
+ const index = args.indexOf(name);
288
+ if (index === -1) return undefined;
289
+ return args[index + 1];
290
+ };
291
+
292
+ const runCli = async () => {
293
+ const [command, ...args] = process.argv.slice(2);
294
+
295
+ if (command === "assert-settings") {
296
+ const settingsPath = readArgValue(args, "--settings");
297
+ const packageSpec = readArgValue(args, "--package-spec");
298
+ if (!settingsPath) throw new Error("--settings is required");
299
+ assertPackageSpecInstalled({ settings: readJsonFile(settingsPath), packageSpec });
300
+ console.log("Isolated pi settings include installed package spec.");
301
+ return;
302
+ }
303
+
304
+ if (command === "assert-installed-artifact") {
305
+ const packageRoot = readArgValue(args, "--package-root");
306
+ const packageName = readArgValue(args, "--package-name");
307
+ const packageVersion = readArgValue(args, "--package-version");
308
+ assertInstalledArtifactPackage({ packageRoot, packageName, packageVersion });
309
+ console.log("Installed package artifact exposes the agent_vent extension entrypoint.");
310
+ return;
311
+ }
312
+
313
+ if (command === "assert-local-tarball-install-source") {
314
+ const packageSpec = readArgValue(args, "--package-spec");
315
+ assertLocalTarballInstallSource({ packageSpec });
316
+ console.log(
317
+ "Local npm:<tarball> package spec is validated as an install source; runtime discovery smoke uses the installed local package path.",
318
+ );
319
+ return;
320
+ }
321
+
322
+ if (
323
+ command === "prepare-local-path-artifact-settings" ||
324
+ command === "prepare-installed-artifact-settings"
325
+ ) {
326
+ const settingsPath = readArgValue(args, "--settings");
327
+ const packageRoot = readArgValue(args, "--package-root");
328
+ if (!settingsPath) throw new Error("--settings is required");
329
+ const settings = readJsonFile(settingsPath);
330
+ const nextSettings = buildLocalPathArtifactSettings({ settings, packageRoot });
331
+ fs.writeFileSync(settingsPath, `${JSON.stringify(nextSettings, null, 2)}\n`, "utf8");
332
+ console.log(
333
+ "Isolated pi settings now load the installed package artifact through local-path package discovery.",
334
+ );
335
+ return;
336
+ }
337
+
338
+ if (command === "assert-command-output") {
339
+ const outputPath = readArgValue(args, "--output");
340
+ const ventDir = readArgValue(args, "--vent-dir");
341
+ if (!outputPath) throw new Error("--output is required");
342
+ assertAgentVentPathSmokeOutput({ output: fs.readFileSync(outputPath, "utf8"), ventDir });
343
+ console.log("Installed /agent_vent path command smoke output OK.");
344
+ return;
345
+ }
346
+
347
+ if (command === "assert-installed-tool-path") {
348
+ const packageRoot = readArgValue(args, "--package-root");
349
+ const ventDir = readArgValue(args, "--vent-dir");
350
+ await executeInstalledArtifactToolPathSmoke({ packageRoot, ventDir });
351
+ console.log("Installed artifact shadow registered-tool path smoke output OK.");
352
+ return;
353
+ }
354
+
355
+ throw new Error(
356
+ "Usage: node ./scripts/release-smoke-check.mjs <assert-settings|assert-local-tarball-install-source|assert-installed-artifact|prepare-local-path-artifact-settings|assert-command-output|assert-installed-tool-path> ...",
357
+ );
358
+ };
359
+
360
+ if (import.meta.url === `file://${process.argv[1]}`) {
361
+ try {
362
+ await runCli();
363
+ } catch (error) {
364
+ console.error(error.message);
365
+ process.exit(1);
366
+ }
367
+ }
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ cd "$ROOT_DIR"
6
+
7
+ if [[ -z "${PACKAGE_SPEC:-}" ]]; then
8
+ echo "PACKAGE_SPEC is required; release-check.sh sets it to the packed tarball spec." >&2
9
+ exit 1
10
+ fi
11
+
12
+ if [[ -z "${PI_CODING_AGENT_DIR:-}" ]]; then
13
+ echo "PI_CODING_AGENT_DIR is required so release smoke cannot touch operator pi settings." >&2
14
+ exit 1
15
+ fi
16
+
17
+ if ! command -v pi >/dev/null 2>&1; then
18
+ echo "pi CLI not found in PATH." >&2
19
+ exit 1
20
+ fi
21
+
22
+ if [[ ! -f "$PI_CODING_AGENT_DIR/settings.json" ]]; then
23
+ echo "Isolated pi settings missing: $PI_CODING_AGENT_DIR/settings.json" >&2
24
+ exit 1
25
+ fi
26
+
27
+ SMOKE_DIR=""
28
+ cleanup() {
29
+ if [[ "${KEEP_RELEASE_ARTIFACTS:-0}" != "1" && -n "$SMOKE_DIR" && -d "$SMOKE_DIR" ]]; then
30
+ rm -rf "$SMOKE_DIR"
31
+ fi
32
+ }
33
+ trap cleanup EXIT
34
+
35
+ SMOKE_DIR="$(mktemp -d /tmp/pi-agent-vent-release-smoke-XXXXXX)"
36
+ SMOKE_LOCAL_PATH_VENT_DIR="$SMOKE_DIR/agent-vent-local-path-store"
37
+ SMOKE_TOOL_VENT_DIR="$SMOKE_DIR/agent-vent-tool-store"
38
+ SMOKE_LOCAL_PATH_OUTPUT="$SMOKE_DIR/agent-vent-local-path.out"
39
+ PACKAGE_NAME="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).name")"
40
+ PACKAGE_VERSION="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).version")"
41
+ if [[ -n "${NPM_CONFIG_PREFIX:-}" ]]; then
42
+ GLOBAL_NODE_MODULES="$(npm --prefix "$NPM_CONFIG_PREFIX" root -g)"
43
+ else
44
+ GLOBAL_NODE_MODULES="$(npm root -g)"
45
+ fi
46
+ INSTALLED_PACKAGE_ROOT="$GLOBAL_NODE_MODULES/$PACKAGE_NAME"
47
+ INSTALLED_EXTENSION_PATH="$INSTALLED_PACKAGE_ROOT/extensions/agent-vent.ts"
48
+
49
+ if [[ -n "${NPM_CONFIG_PREFIX:-}" ]]; then
50
+ case "$INSTALLED_PACKAGE_ROOT" in
51
+ "$NPM_CONFIG_PREFIX"/*) ;;
52
+ *)
53
+ echo "Installed package root escaped isolated npm prefix: $INSTALLED_PACKAGE_ROOT" >&2
54
+ exit 1
55
+ ;;
56
+ esac
57
+ fi
58
+
59
+ node ./scripts/release-smoke-check.mjs assert-settings \
60
+ --settings "$PI_CODING_AGENT_DIR/settings.json" \
61
+ --package-spec "$PACKAGE_SPEC"
62
+
63
+ node ./scripts/release-smoke-check.mjs assert-installed-artifact \
64
+ --package-root "$INSTALLED_PACKAGE_ROOT" \
65
+ --package-name "$PACKAGE_NAME" \
66
+ --package-version "$PACKAGE_VERSION"
67
+
68
+ node ./scripts/release-smoke-check.mjs assert-local-tarball-install-source \
69
+ --package-spec "$PACKAGE_SPEC"
70
+
71
+ node ./scripts/release-smoke-check.mjs prepare-local-path-artifact-settings \
72
+ --settings "$PI_CODING_AGENT_DIR/settings.json" \
73
+ --package-root "$INSTALLED_PACKAGE_ROOT"
74
+
75
+ echo "== installed local-path package-discovery /agent_vent command smoke"
76
+ PI_AGENT_VENT_DIR="$SMOKE_LOCAL_PATH_VENT_DIR" \
77
+ pi --offline --no-session --no-builtin-tools --no-skills --no-prompt-templates --no-context-files --no-themes \
78
+ -p "/agent_vent path" >"$SMOKE_LOCAL_PATH_OUTPUT" 2>&1
79
+ cat "$SMOKE_LOCAL_PATH_OUTPUT"
80
+
81
+ node ./scripts/release-smoke-check.mjs assert-command-output \
82
+ --output "$SMOKE_LOCAL_PATH_OUTPUT" \
83
+ --vent-dir "$SMOKE_LOCAL_PATH_VENT_DIR"
84
+
85
+ echo "== installed artifact shadow agent_vent registered-tool path smoke"
86
+ node ./scripts/release-smoke-check.mjs assert-installed-tool-path \
87
+ --package-root "$INSTALLED_PACKAGE_ROOT" \
88
+ --vent-dir "$SMOKE_TOOL_VENT_DIR"
89
+
90
+ echo "release smoke done: local npm:<tarball> install source was validated; installed artifact /agent_vent path command loads through local-path package discovery and agent_vent shadow tool path executes from $INSTALLED_PACKAGE_ROOT."