gentle-pi 2.4.0 → 2.5.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.
- package/README.md +170 -12
- package/assets/agents/gentle-ai-worker.md +9 -0
- package/assets/orchestrator-delegation.md +19 -9
- package/assets/orchestrator.md +5 -5
- package/docs/delegated-verification.md +25 -0
- package/docs/telemetry.md +38 -0
- package/extensions/ask-user-choice.ts +26 -20
- package/extensions/codegraph-tools.ts +94 -5
- package/extensions/gentle-agents.ts +588 -0
- package/extensions/gentle-ai.ts +898 -79
- package/extensions/gentle-shell.ts +547 -0
- package/extensions/gentle-todo.ts +199 -0
- package/extensions/quiet-tools.ts +1 -1
- package/lib/agents-config.ts +318 -0
- package/lib/agents-history.ts +80 -0
- package/lib/agents-protocol.ts +429 -0
- package/lib/agents-runner.ts +490 -0
- package/lib/agents-transcript.ts +87 -0
- package/lib/agents-view.ts +557 -0
- package/lib/agents-widget.ts +222 -0
- package/lib/gentle-ai-renderer.ts +142 -26
- package/lib/native-choice-list.ts +194 -0
- package/lib/native-fullscreen-interaction.ts +47 -0
- package/lib/native-pointer-region.ts +164 -0
- package/lib/native-review-cli.ts +88 -12
- package/lib/review-candidate-view-owner.ts +177 -0
- package/lib/review-candidate-view.ts +127 -35
- package/lib/review-consent-ui.ts +65 -0
- package/lib/review-integration-v2.ts +58 -8
- package/lib/review-last-event-controller.ts +1 -0
- package/lib/review-relay-contract.ts +11 -0
- package/lib/review-repository.ts +2 -2
- package/lib/review-risk-assessment.ts +339 -0
- package/lib/review-session-standing-permission-ipc.ts +309 -0
- package/lib/review-session-standing-permission.ts +219 -0
- package/lib/shell-bar.ts +138 -0
- package/lib/shell-card.ts +136 -0
- package/lib/shell-changes-view.ts +205 -0
- package/lib/shell-changes.ts +210 -0
- package/lib/shell-gauge.ts +40 -0
- package/lib/shell-prompt.ts +119 -0
- package/lib/shell-todo.ts +280 -0
- package/lib/shell-usage-view.ts +76 -0
- package/lib/shell-usage.ts +246 -0
- package/lib/telemetry-trigger.ts +151 -0
- package/package.json +4 -4
- package/runtime/native-review-cli.mjs +87 -11
- package/runtime/review-integration-v2.mjs +58 -8
- package/runtime/review-relay-contract.mjs +11 -0
- package/runtime/review-risk-assessment.mjs +340 -0
- package/runtime/telemetry-trigger.mjs +152 -0
- package/scripts/build-runtime-modules.mjs +2 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/test-packed-runner.mjs +22 -0
- package/scripts/verify-package-files.mjs +6 -2
- package/skills/_shared/review-ledger-contract.md +3 -1
- package/tests/agents-config.test.ts +143 -0
- package/tests/agents-fake-child.ts +52 -0
- package/tests/agents-history.test.ts +54 -0
- package/tests/agents-protocol.test.ts +153 -0
- package/tests/agents-runner-process.test.ts +111 -0
- package/tests/agents-runner.test.ts +402 -0
- package/tests/agents-transcript.test.ts +30 -0
- package/tests/agents-view.test.ts +274 -0
- package/tests/agents-widget.test.ts +111 -0
- package/tests/ask-user-choice.test.ts +157 -3
- package/tests/codegraph-tools.test.ts +110 -1
- package/tests/devbinary/native-review-parity.devtest.ts +108 -0
- package/tests/fixtures/agents-process-child.mjs +23 -0
- package/tests/gentle-agents.test.ts +741 -0
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +65 -0
- package/tests/gentle-ai.test.ts +28 -12
- package/tests/gentle-card-text.ts +35 -0
- package/tests/gentle-shell.test.ts +527 -0
- package/tests/gentle-todo.test.ts +182 -0
- package/tests/native-choice-list.test.ts +202 -0
- package/tests/native-fullscreen-interaction.test.ts +125 -0
- package/tests/native-pointer-region.test.ts +245 -0
- package/tests/native-review-capability-contract.test.ts +16 -1
- package/tests/native-review-cli.test.ts +40 -0
- package/tests/native-review-consent.test.ts +91 -0
- package/tests/native-review-parity-runtime.test.ts +8 -2
- package/tests/native-review-parity.test.ts +29 -22
- package/tests/orchestrator-budget.test.ts +69 -0
- package/tests/orchestrator-rdd-ownership.test.ts +9 -0
- package/tests/package-manifest.test.ts +17 -6
- package/tests/quiet-tool-rendering.test.ts +96 -37
- package/tests/rdd-aware-verification-contract.test.ts +216 -0
- package/tests/rdd-status-line.test.ts +286 -0
- package/tests/review-candidate-view.test.ts +452 -6
- package/tests/review-contract-prompt.test.ts +3 -0
- package/tests/review-controller-native-recovery.test.ts +29 -4
- package/tests/review-controller-native-routing.test.ts +321 -4
- package/tests/review-controller-workspace-root.test.ts +45 -2
- package/tests/review-controller.test.ts +25 -0
- package/tests/review-host-relay-routing.test.ts +20 -4
- package/tests/review-integration-v2.test.ts +112 -0
- package/tests/review-last-event-closure.test.ts +7 -2
- package/tests/review-relay-contract.test.ts +26 -0
- package/tests/review-repository.test.ts +28 -1
- package/tests/review-risk-assessment.test.ts +626 -0
- package/tests/review-session-standing-permission-controller.test.ts +608 -0
- package/tests/review-session-standing-permission-ipc.test.ts +233 -0
- package/tests/review-session-standing-permission-runtime.test.ts +212 -0
- package/tests/review-session-standing-permission.test.ts +126 -0
- package/tests/shell-bar.test.ts +176 -0
- package/tests/shell-card.test.ts +118 -0
- package/tests/shell-changes-view.test.ts +146 -0
- package/tests/shell-changes.test.ts +182 -0
- package/tests/shell-prompt.test.ts +118 -0
- package/tests/shell-todo.test.ts +170 -0
- package/tests/shell-usage-view.test.ts +62 -0
- package/tests/shell-usage.test.ts +197 -0
- package/tests/telemetry-trigger.test.ts +349 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
-
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import test from "node:test";
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import codeGraphTools, {
|
|
9
9
|
createCodeGraphTool,
|
|
10
|
+
codeGraphNodeScript,
|
|
11
|
+
findCodeGraphNodeScriptOnPath,
|
|
10
12
|
type CodeGraphRunner,
|
|
11
13
|
} from "../extensions/codegraph-tools.ts";
|
|
12
14
|
|
|
@@ -211,3 +213,110 @@ test("CodeGraph tool registration exposes a single constrained custom tool", ()
|
|
|
211
213
|
},
|
|
212
214
|
});
|
|
213
215
|
});
|
|
216
|
+
|
|
217
|
+
function writeCodeGraphPackage(binDir: string, scriptSource = "#!/usr/bin/env node\n"): string {
|
|
218
|
+
const pkgRoot = join(binDir, "node_modules", "@colbymchenry", "codegraph");
|
|
219
|
+
mkdirSync(pkgRoot, { recursive: true });
|
|
220
|
+
const scriptTarget = join(pkgRoot, "npm-shim.js");
|
|
221
|
+
writeFileSync(scriptTarget, scriptSource);
|
|
222
|
+
writeFileSync(
|
|
223
|
+
join(pkgRoot, "package.json"),
|
|
224
|
+
JSON.stringify({
|
|
225
|
+
name: "@colbymchenry/codegraph",
|
|
226
|
+
version: "1.0.0",
|
|
227
|
+
bin: { codegraph: "npm-shim.js" },
|
|
228
|
+
}),
|
|
229
|
+
);
|
|
230
|
+
writeFileSync(
|
|
231
|
+
join(binDir, "codegraph.cmd"),
|
|
232
|
+
"@ECHO off\nnode \"%~dp0\\node_modules\\@colbymchenry\\codegraph\\npm-shim.js\" %*\n",
|
|
233
|
+
);
|
|
234
|
+
return scriptTarget;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function withWindowsPath(t: test.TestContext, path: string): void {
|
|
238
|
+
const previousPath = process.env.PATH;
|
|
239
|
+
const previousWindowsPath = process.env.Path;
|
|
240
|
+
const windowsPath = process.platform === "win32"
|
|
241
|
+
? `${path};${previousWindowsPath ?? previousPath ?? ""}`
|
|
242
|
+
: path;
|
|
243
|
+
process.env.Path = windowsPath;
|
|
244
|
+
process.env.PATH = windowsPath;
|
|
245
|
+
t.after(() => {
|
|
246
|
+
if (previousPath === undefined) delete process.env.PATH;
|
|
247
|
+
else process.env.PATH = previousPath;
|
|
248
|
+
if (previousWindowsPath === undefined) delete process.env.Path;
|
|
249
|
+
else process.env.Path = previousWindowsPath;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
test("Windows shim helpers skip stale PATH shims before resolving the npm script", (t) => {
|
|
254
|
+
const staleBinDir = realpathSync(mkdtempSync(join(tmpdir(), "gentle-pi-codegraph-stale-bin-")));
|
|
255
|
+
const validBinDir = realpathSync(mkdtempSync(join(tmpdir(), "gentle-pi-codegraph-valid-bin-")));
|
|
256
|
+
t.after(() => rmSync(staleBinDir, { recursive: true, force: true }));
|
|
257
|
+
t.after(() => rmSync(validBinDir, { recursive: true, force: true }));
|
|
258
|
+
writeFileSync(join(staleBinDir, "codegraph.cmd"), "@ECHO off\n");
|
|
259
|
+
const stalePkgRoot = join(staleBinDir, "node_modules", "@colbymchenry", "codegraph");
|
|
260
|
+
mkdirSync(stalePkgRoot, { recursive: true });
|
|
261
|
+
writeFileSync(join(stalePkgRoot, "package.json"), JSON.stringify({ bin: { codegraph: "." } }));
|
|
262
|
+
const scriptTarget = writeCodeGraphPackage(validBinDir);
|
|
263
|
+
|
|
264
|
+
withWindowsPath(t, `${staleBinDir};${validBinDir}`);
|
|
265
|
+
|
|
266
|
+
assert.equal(codeGraphNodeScript(join(staleBinDir, "codegraph.cmd")), undefined);
|
|
267
|
+
assert.equal(findCodeGraphNodeScriptOnPath(), scriptTarget);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("Windows shim helpers resolve quoted PATH entries in order without stripping unmatched quotes", (t) => {
|
|
271
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gentle-pi-codegraph-quoted-bin-")));
|
|
272
|
+
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
273
|
+
const firstBin = join(root, "Program Files", "nodejs");
|
|
274
|
+
const secondBin = join(root, "other-bin");
|
|
275
|
+
const firstScript = writeCodeGraphPackage(firstBin);
|
|
276
|
+
const secondScript = writeCodeGraphPackage(secondBin);
|
|
277
|
+
withWindowsPath(t, `"${firstBin}";${secondBin}`);
|
|
278
|
+
|
|
279
|
+
assert.equal(findCodeGraphNodeScriptOnPath(), firstScript);
|
|
280
|
+
for (const [path, expected] of [
|
|
281
|
+
[`${firstBin};"${secondBin}"`, firstScript],
|
|
282
|
+
[`"${firstBin};${secondBin}`, secondScript],
|
|
283
|
+
[`${firstBin}";${secondBin}`, secondScript],
|
|
284
|
+
[`${secondBin};"${firstBin}"`, secondScript],
|
|
285
|
+
]) {
|
|
286
|
+
process.env.Path = path;
|
|
287
|
+
process.env.PATH = path;
|
|
288
|
+
assert.equal(findCodeGraphNodeScriptOnPath(), expected);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("Windows launcher passes command metacharacters as one literal argv value", async (t) => {
|
|
293
|
+
const binDir = realpathSync(mkdtempSync(join(tmpdir(), "gentle-pi-codegraph-bin-")));
|
|
294
|
+
t.after(() => rmSync(binDir, { recursive: true, force: true }));
|
|
295
|
+
writeCodeGraphPackage(binDir, "process.stdout.write(JSON.stringify(process.argv.slice(2)));\n");
|
|
296
|
+
const cwd = workspace(t);
|
|
297
|
+
if (process.platform !== "win32") {
|
|
298
|
+
const gitShim = join(binDir, "git");
|
|
299
|
+
writeFileSync(gitShim, "#!/bin/sh\nprintf '%s\\n' \"$PWD\"\n");
|
|
300
|
+
chmodSync(gitShim, 0o755);
|
|
301
|
+
}
|
|
302
|
+
withWindowsPath(t, binDir);
|
|
303
|
+
|
|
304
|
+
const previousPlatform = process.platform;
|
|
305
|
+
Object.defineProperty(process, "platform", { configurable: true, value: "win32" });
|
|
306
|
+
t.after(() => Object.defineProperty(process, "platform", { configurable: true, value: previousPlatform }));
|
|
307
|
+
|
|
308
|
+
const query = "literal & echo injected > shell-injection.txt";
|
|
309
|
+
const result = await createCodeGraphTool().execute(
|
|
310
|
+
"test",
|
|
311
|
+
{ operation: "query", query },
|
|
312
|
+
undefined,
|
|
313
|
+
undefined,
|
|
314
|
+
{ cwd } as ExtensionContext,
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
assert.deepEqual(result.content, [{
|
|
318
|
+
type: "text",
|
|
319
|
+
text: JSON.stringify(["query", "--path", cwd, "--limit", "10", "--", query]),
|
|
320
|
+
}]);
|
|
321
|
+
assert.equal(existsSync(join(cwd, "shell-injection.txt")), false);
|
|
322
|
+
});
|
|
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import test from "node:test";
|
|
7
7
|
import { __testing } from "../../extensions/gentle-ai.ts";
|
|
8
|
+
import { CandidateViewRegistry } from "../../lib/review-candidate-view.ts";
|
|
8
9
|
import {
|
|
9
10
|
NATIVE_REVIEW_ERROR_CODE,
|
|
10
11
|
NativeReviewCliError,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
createNodeExecFileAdapter,
|
|
14
15
|
type ExecFileAdapter,
|
|
15
16
|
} from "../../lib/native-review-cli.ts";
|
|
17
|
+
import type { ReviewStatusV3 } from "../../lib/review-integration-v2.ts";
|
|
16
18
|
import { requireDevBinary } from "../support/native-binary-gate.ts";
|
|
17
19
|
|
|
18
20
|
// Organic RDD Parity: candidate-bound dev-binary journeys.
|
|
@@ -43,6 +45,35 @@ function bridgeAdapter(binary: string, calls: NativeCall[]): ExecFileAdapter {
|
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
function selectedStatusFieldNames(stdout: string): string {
|
|
49
|
+
try {
|
|
50
|
+
const status = JSON.parse(stdout) as Record<string, unknown>;
|
|
51
|
+
const projection = status.projection as Record<string, unknown> | undefined;
|
|
52
|
+
const transition = status.next_transition as Record<string, unknown> | undefined;
|
|
53
|
+
const execute = transition?.execute as Record<string, unknown> | undefined;
|
|
54
|
+
const argument = Array.isArray(execute?.arguments) ? execute.arguments[0] as Record<string, unknown> | undefined : undefined;
|
|
55
|
+
const names = (prefix: string, value?: Record<string, unknown>) => value === undefined ? [] : Object.keys(value).sort().map((key) => `${prefix}.${key}`);
|
|
56
|
+
return [...names("status", status), ...names("status.projection", projection), ...names("status.next_transition", transition), ...names("status.next_transition.execute", execute), ...names("status.next_transition.execute.arguments[]", argument)].join(", ");
|
|
57
|
+
} catch {
|
|
58
|
+
return "status JSON";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function controllerResultStructure(result: Record<string, unknown>): string {
|
|
63
|
+
const diagnostics = typeof result.diagnostics === "object" && result.diagnostics !== null && !Array.isArray(result.diagnostics)
|
|
64
|
+
? result.diagnostics as Record<string, unknown>
|
|
65
|
+
: undefined;
|
|
66
|
+
const safeCode = (name: string, value: unknown) => typeof value === "string" && /^[a-z0-9][a-z0-9._-]*$/.test(value) ? `${name}=${value}` : undefined;
|
|
67
|
+
const safeBooleanNames = ["candidate_view_created", "candidate_view_pre_native", "native_invocation_attempted", "lineage_created", "mutation_performed", "reset_eligible"];
|
|
68
|
+
return [
|
|
69
|
+
`keys=[${Object.keys(result).sort().join(",")}]`,
|
|
70
|
+
...(diagnostics === undefined ? [] : [`diagnostics.keys=[${Object.keys(diagnostics).sort().join(",")}]`]),
|
|
71
|
+
safeCode("operation", result.operation), safeCode("status", result.status), safeCode("outcome", result.outcome), safeCode("reason", result.reason),
|
|
72
|
+
safeCode("diagnostics.error_code", diagnostics?.error_code), safeCode("diagnostics.code", diagnostics?.code), safeCode("diagnostics.reason", diagnostics?.reason),
|
|
73
|
+
...safeBooleanNames.flatMap((name) => typeof result[name] === "boolean" ? [`${name}=${String(result[name])}`] : []),
|
|
74
|
+
].filter((value): value is string => value !== undefined).join("; ");
|
|
75
|
+
}
|
|
76
|
+
|
|
46
77
|
function journeyNative(binary: string): { native: NativeReviewCliV216; calls: NativeCall[] } {
|
|
47
78
|
const calls: NativeCall[] = [];
|
|
48
79
|
return { native: new NativeReviewCliV216(bridgeAdapter(binary, calls), binary), calls };
|
|
@@ -202,6 +233,83 @@ test("dev-binary: an empty candidate exposes the current STATUS refusal and neve
|
|
|
202
233
|
assert.equal(calls.filter((arguments_) => arguments_.at(0) === "review" && arguments_.at(1) === "start").length, 0);
|
|
203
234
|
});
|
|
204
235
|
|
|
236
|
+
test("dev-binary: v7 intended-untracked selection decodes both selected statuses before START", { skip: !RUNNABLE }, async (t) => {
|
|
237
|
+
const cwd = repository(t);
|
|
238
|
+
writeFileSync(join(cwd, "app.ts"), "export const value = 1;\n");
|
|
239
|
+
git(cwd, "add", "app.ts");
|
|
240
|
+
git(cwd, "commit", "-qm", "test: base candidate");
|
|
241
|
+
writeFileSync(join(cwd, "app.ts"), "export const value = 2;\n");
|
|
242
|
+
writeFileSync(join(cwd, "chosen.md"), "# Chosen\n");
|
|
243
|
+
|
|
244
|
+
const calls: NativeCall[] = [], selectedWireFields: string[] = [];
|
|
245
|
+
let interceptedStarts = 0;
|
|
246
|
+
let delegatedStarts = 0;
|
|
247
|
+
const startIntercept = new Error("test-only native START intercept");
|
|
248
|
+
const real = createNodeExecFileAdapter();
|
|
249
|
+
const adapter: ExecFileAdapter = async (request) => {
|
|
250
|
+
calls.push([...request.arguments]);
|
|
251
|
+
if (request.arguments.at(0) === "review" && request.arguments.at(1) === "start") {
|
|
252
|
+
interceptedStarts += 1;
|
|
253
|
+
throw startIntercept;
|
|
254
|
+
}
|
|
255
|
+
if (request.arguments.at(0) === "review" && request.arguments.at(1) === "start") delegatedStarts += 1;
|
|
256
|
+
const result = await real({ ...request, file: DEV_BINARY! });
|
|
257
|
+
if (request.arguments.some((argument) => argument.startsWith("--intended-untracked-selection="))) selectedWireFields.push(selectedStatusFieldNames(result.stdout));
|
|
258
|
+
return result;
|
|
259
|
+
};
|
|
260
|
+
const native = new NativeReviewCliV216(adapter, DEV_BINARY!);
|
|
261
|
+
const targetStatus = native.targetStatus.bind(native);
|
|
262
|
+
const selectedStatuses: ReviewStatusV3[] = [];
|
|
263
|
+
const selectedFailures: Array<{ call: number; code: string }> = [];
|
|
264
|
+
let selectedStatusCalls = 0;
|
|
265
|
+
native.targetStatus = async (request) => {
|
|
266
|
+
if (request.intendedUntrackedSelection === undefined) return targetStatus(request);
|
|
267
|
+
selectedStatusCalls += 1;
|
|
268
|
+
try {
|
|
269
|
+
const status = await targetStatus(request);
|
|
270
|
+
selectedStatuses.push(status);
|
|
271
|
+
return status;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
selectedFailures.push({ call: selectedStatusCalls, code: error instanceof NativeReviewCliError ? error.code : "unexpected-error" });
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const candidateViews = new CandidateViewRegistry(), retainedSelections = new Map();
|
|
279
|
+
await enableReview(native, cwd);
|
|
280
|
+
const initial = await __testing.executeReviewControllerOperation({ operation: "status" }, cwd, native, undefined, candidateViews, undefined, retainedSelections);
|
|
281
|
+
assert.equal((initial.result as Record<string, unknown> | undefined)?.schema, "gentle-ai.review-integration.status/v7", "initial status.schema");
|
|
282
|
+
assert.equal(typeof initial.selectionBinding, "string", "initial selectionBinding");
|
|
283
|
+
const result = await __testing.executeReviewControllerOperation({ operation: "select-intended-untracked", selectionBinding: initial.selectionBinding as string, intendedUntracked: ["chosen.md"] } as never, cwd, native, undefined, candidateViews, undefined, retainedSelections);
|
|
284
|
+
|
|
285
|
+
if (selectedStatusCalls !== 2) assert.fail(`intended-selection STATUS call count=${selectedStatusCalls}; ${controllerResultStructure(result)}`);
|
|
286
|
+
if (selectedFailures.length > 0) {
|
|
287
|
+
const failure = selectedFailures[0]!;
|
|
288
|
+
assert.fail(`selected STATUS call ${failure.call} decode failed at fields: ${selectedWireFields[failure.call - 1] ?? "status"}; error=${failure.code}`);
|
|
289
|
+
}
|
|
290
|
+
assert.equal(selectedStatusCalls, 2, "intended-selection STATUS call count");
|
|
291
|
+
assert.equal(selectedStatuses.length, 2, "decoded intended-selection STATUS count");
|
|
292
|
+
for (const [index, status] of selectedStatuses.entries()) {
|
|
293
|
+
assert.equal(status.raw.schema, "gentle-ai.review-integration.status/v7", `selected STATUS call ${index + 1} status.schema`);
|
|
294
|
+
assert.deepEqual(status.projection.intendedUntracked, ["chosen.md"], `selected STATUS call ${index + 1} status.projection.intended_untracked`);
|
|
295
|
+
assert.equal(status.nextTransition?.kind, "execute", `selected STATUS call ${index + 1} status.next_transition.kind`);
|
|
296
|
+
assert.equal(status.nextTransition?.execute?.operation, "review.start", `selected STATUS call ${index + 1} status.next_transition.execute.operation`);
|
|
297
|
+
assert.ok((status.nextTransition?.execute?.arguments.length ?? 0) > 0, `selected STATUS call ${index + 1} status.next_transition.execute.arguments`);
|
|
298
|
+
assert.equal(status.nextTransition?.execute?.arguments.every((argument) => typeof argument.token === "string" && argument.token.length > 0), true, `selected STATUS call ${index + 1} status.next_transition.execute.arguments[].token`);
|
|
299
|
+
}
|
|
300
|
+
assert.equal(interceptedStarts, 1, "adapter must intercept the one native review.start attempt");
|
|
301
|
+
assert.equal(delegatedStarts, 0, "intercepted native START must not reach the real adapter");
|
|
302
|
+
assert.equal(calls.filter((arguments_) => arguments_.at(0) === "review" && arguments_.at(1) === "start").length, 1, "native review.start adapter attempt count");
|
|
303
|
+
assert.equal(result.outcome, "native-mutation-status-reconciled", "controller result after intentional START intercept");
|
|
304
|
+
assert.equal("lineage_id" in result, false, "intercepted START must report no lineage binding");
|
|
305
|
+
assert.equal("authority" in result, false, "intercepted START must report no authority binding");
|
|
306
|
+
assert.equal("lineage_created" in result, false, "reconciled unknown mutation must not claim lineage creation");
|
|
307
|
+
assert.equal("mutation_performed" in result, false, "reconciled unknown mutation must not claim a mutation result");
|
|
308
|
+
assert.equal(result.mutation_outcome, "unknown", "the controller cannot prove mutation status after an intercepted launch");
|
|
309
|
+
const after = await native.targetStatus({ cwd, agent: "pi" });
|
|
310
|
+
assert.equal(after.authority, undefined, "post-intercept STATUS must not expose authority");
|
|
311
|
+
});
|
|
312
|
+
|
|
205
313
|
test("dev-binary: a low-risk START closes the review with no receipt or follow-up capture", { skip: !RUNNABLE }, async (t) => {
|
|
206
314
|
const cwd = repository(t);
|
|
207
315
|
writeFileSync(join(cwd, "guide.md"), "# Guide\n\nBase\n");
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const descendant = spawn(process.execPath, ["-e", "process.on('SIGTERM', () => {}); process.stdout.write('ready'); setInterval(() => {}, 1000);"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
4
|
+
descendant.stdout.once("data", () => {
|
|
5
|
+
console.log(`DESCENDANT:${descendant.pid}`);
|
|
6
|
+
if (process.env.AGENTS_PROCESS_CHILD_EXIT_AFTER_READY === "1") process.exit(0);
|
|
7
|
+
});
|
|
8
|
+
process.on("SIGTERM", () => {
|
|
9
|
+
if (process.env.AGENTS_PROCESS_CHILD_EXIT_ON_TERM === "1") process.exit(0);
|
|
10
|
+
});
|
|
11
|
+
process.stdin.setEncoding("utf8");
|
|
12
|
+
process.stdin.on("data", (chunk) => {
|
|
13
|
+
for (const line of chunk.split("\n")) {
|
|
14
|
+
if (!line) continue;
|
|
15
|
+
try {
|
|
16
|
+
const command = JSON.parse(line);
|
|
17
|
+
process.stdout.write(`${JSON.stringify({ type: "response", id: command.id, success: true })}\n`);
|
|
18
|
+
} catch {
|
|
19
|
+
// Ignore malformed input; the runner owns the RPC protocol.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
setInterval(() => {}, 1000);
|