gentle-pi 2.5.0 → 2.6.1
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 +139 -30
- package/assets/agents/gentle-ai-worker.md +4 -0
- package/assets/agents/jd-fix-agent.md +18 -0
- package/assets/agents/jd-judge-a.md +1 -1
- package/assets/agents/jd-judge-b.md +1 -1
- package/assets/agents/sdd-apply.md +7 -5
- package/assets/agents/sdd-archive.md +5 -3
- package/assets/agents/sdd-design.md +4 -0
- package/assets/agents/sdd-explore.md +4 -0
- package/assets/agents/sdd-init.md +4 -0
- package/assets/agents/sdd-onboard.md +4 -0
- package/assets/agents/sdd-proposal.md +4 -0
- package/assets/agents/sdd-remediate.md +37 -0
- package/assets/agents/sdd-research.md +26 -3
- package/assets/agents/sdd-spec.md +4 -0
- package/assets/agents/sdd-status.md +9 -75
- package/assets/agents/sdd-sync.md +4 -0
- package/assets/agents/sdd-tasks.md +4 -0
- package/assets/agents/sdd-verify.md +5 -3
- package/assets/chains/sdd-full.chain.md +4 -0
- package/assets/chains/sdd-plan.chain.md +4 -0
- package/assets/chains/sdd-verify.chain.md +4 -0
- package/assets/migrations/managed-assets-v2.5.0.json +7 -0
- package/assets/orchestrator-delegation.md +21 -3
- package/assets/sdd-orchestrator-workflow.md +54 -21
- package/assets/support/sdd-status-contract.md +34 -90
- package/contracts/telemetry/runtime-aggregate-v1.schema.json +67 -0
- package/docs/telemetry.md +57 -1
- package/docs/windows-startup-console-visibility.md +18 -0
- package/extensions/ask-user-choice.ts +143 -15
- package/extensions/codegraph-tools.ts +1 -0
- package/extensions/gentle-agents.ts +795 -50
- package/extensions/gentle-ai.ts +2033 -322
- package/extensions/gentle-shell.ts +145 -42
- package/extensions/gentle-todo.ts +47 -12
- package/extensions/quiet-tools.ts +1 -0
- package/extensions/runtime-metrics.ts +121 -0
- package/extensions/sdd-init.ts +2 -2
- package/extensions/startup-banner.ts +52 -75
- package/lib/agent-profiles.ts +550 -0
- package/lib/agents-completion-delivery.ts +72 -0
- package/lib/agents-config.ts +7 -10
- package/lib/agents-history.ts +9 -1
- package/lib/agents-messaging.ts +187 -0
- package/lib/agents-protocol.ts +77 -5
- package/lib/agents-runner.ts +548 -26
- package/lib/agents-thread-view.ts +57 -0
- package/lib/agents-view-layout.ts +40 -0
- package/lib/agents-view.ts +548 -191
- package/lib/agents-widget.ts +33 -14
- package/lib/gentle-ai-binary.ts +3 -1
- package/lib/gentle-ai-renderer.ts +8 -6
- package/lib/native-review-cli.ts +288 -1
- package/lib/orchestrator-presence.ts +337 -0
- package/lib/profiles-orchestrator.ts +203 -0
- package/lib/review-candidate-view-owner.ts +296 -46
- package/lib/review-candidate-view.ts +30 -20
- package/lib/review-consent-component.ts +247 -0
- package/lib/review-consent-ui.ts +53 -8
- package/lib/review-host-relay.ts +28 -0
- package/lib/review-integration-v2.ts +187 -5
- package/lib/review-last-event-controller.ts +7 -4
- package/lib/review-reminder-receipt.ts +74 -0
- package/lib/review-session-standing-permission.ts +27 -6
- package/lib/runtime-metrics-children.ts +197 -0
- package/lib/runtime-metrics-delivery.ts +68 -0
- package/lib/runtime-metrics-native.ts +174 -0
- package/lib/runtime-metrics-policy.ts +51 -0
- package/lib/runtime-metrics.ts +308 -0
- package/lib/sdd-preflight.ts +362 -81
- package/lib/sdd-research-capabilities.ts +228 -0
- package/lib/sdd-status.ts +29 -7
- package/lib/session-worktree-registry.ts +118 -0
- package/lib/shell-bar.ts +47 -1
- package/lib/shell-card.ts +1 -4
- package/lib/shell-changes-view.ts +362 -37
- package/lib/shell-changes.ts +81 -1
- package/lib/shell-prompt.ts +11 -15
- package/lib/shell-sidebar-banner.ts +11 -0
- package/lib/shell-sidebar-layout.ts +213 -0
- package/lib/shell-sidebar.ts +41 -0
- package/lib/shell-todo.ts +28 -11
- package/lib/telemetry-trigger.ts +2 -0
- package/package.json +6 -3
- package/runtime/gentle-ai-binary.mjs +3 -1
- package/runtime/native-review-cli.mjs +288 -1
- package/runtime/review-integration-v2.mjs +187 -5
- package/runtime/telemetry-trigger.mjs +2 -0
- package/scripts/build-runtime-modules.mjs +9 -1
- package/scripts/check-types.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-gentle-ai.mjs +12 -0
- package/scripts/install-tui-mode-setting.mjs +114 -0
- package/scripts/test-packed-runner.mjs +16 -2
- package/scripts/types-baseline.json +95 -0
- package/scripts/verify-package-files.mjs +4 -2
- package/skills/_shared/review-ledger-contract.md +17 -1
- package/skills/issue-creation/SKILL.md +3 -3
- package/skills/judgment-day/SKILL.md +17 -3
- package/skills/judgment-day/references/prompts-and-formats.md +14 -3
- package/tests/agent-profiles.test.ts +722 -0
- package/tests/agents-completion-delivery.test.ts +94 -0
- package/tests/agents-config.test.ts +62 -0
- package/tests/agents-fake-child.ts +15 -1
- package/tests/agents-grouping.test.ts +179 -0
- package/tests/agents-integration.test.ts +100 -0
- package/tests/agents-messaging.test.ts +94 -0
- package/tests/agents-protocol.test.ts +45 -0
- package/tests/agents-queries.test.ts +190 -0
- package/tests/agents-responsive.test.ts +43 -0
- package/tests/agents-runner.test.ts +571 -14
- package/tests/agents-thread-view.test.ts +45 -0
- package/tests/agents-view.test.ts +476 -65
- package/tests/agents-widget.test.ts +31 -1
- package/tests/artifact-language.test.ts +25 -2
- package/tests/ask-user-choice.test.ts +169 -3
- package/tests/asset-installation-runtime.test.ts +108 -0
- package/tests/autonomous-guard.test.ts +116 -1
- package/tests/codegraph-tools.test.ts +2 -1
- package/tests/delegated-key-learnings-contract.test.ts +1 -1
- package/tests/devbinary/native-review-parity.devtest.ts +2 -0
- package/tests/feature-request-form.test.ts +67 -0
- package/tests/fixtures/agents-messaging-child.mjs +5 -0
- package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
- package/tests/gentle-agents.test.ts +1454 -33
- package/tests/gentle-ai-binary.test.ts +7 -2
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +38 -0
- package/tests/gentle-ai.test.ts +944 -4
- package/tests/gentle-shell.test.ts +303 -12
- package/tests/gentle-todo.test.ts +54 -10
- package/tests/install-tui-mode-setting.test.ts +324 -0
- package/tests/issue-creation-skill.test.ts +22 -0
- package/tests/model-routing-authority.test.ts +12 -0
- package/tests/native-review-capability-contract.test.ts +23 -1
- package/tests/native-review-cli.test.ts +277 -3
- package/tests/native-review-parity.test.ts +14 -7
- package/tests/native-sdd-attempt-authority.test.ts +7 -2
- package/tests/orchestrator-presence.test.ts +389 -0
- package/tests/package-manifest.test.ts +232 -7
- package/tests/profiles-orchestrator.test.ts +208 -0
- package/tests/quiet-tool-rendering.test.ts +1 -0
- package/tests/rdd-aware-verification-contract.test.ts +10 -0
- package/tests/review-agent-end-preflight.test.ts +332 -24
- package/tests/review-candidate-view.test.ts +304 -6
- package/tests/review-consent-ui.test.ts +352 -0
- package/tests/review-contract-prompt.test.ts +14 -0
- package/tests/review-controller-native-routing.test.ts +563 -3
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-restart-parity.test.ts +142 -1
- package/tests/review-host-relay-routing.test.ts +364 -4
- package/tests/review-host-relay.test.ts +29 -0
- package/tests/review-integration-v2-forward.test.ts +44 -0
- package/tests/review-integration-v2.test.ts +164 -0
- package/tests/review-last-event-closure.test.ts +105 -1
- package/tests/review-ledger-contract.test.ts +61 -6
- package/tests/review-reminder-receipt.test.ts +62 -0
- package/tests/review-session-standing-permission-controller.test.ts +52 -4
- package/tests/review-session-standing-permission.test.ts +30 -0
- package/tests/runtime-harness.mjs +447 -39
- package/tests/runtime-metrics-children.test.ts +207 -0
- package/tests/runtime-metrics-delivery.test.ts +85 -0
- package/tests/runtime-metrics-extension.test.ts +196 -0
- package/tests/runtime-metrics-model.test.ts +76 -0
- package/tests/runtime-metrics-native.test.ts +275 -0
- package/tests/runtime-metrics-policy.test.ts +62 -0
- package/tests/runtime-metrics.test.ts +187 -0
- package/tests/sdd-agent-tools.test.ts +10 -1
- package/tests/sdd-execution-routing-contract.test.ts +28 -0
- package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
- package/tests/sdd-native-managed-uptake.test.ts +253 -0
- package/tests/sdd-planning-routing-contract.test.ts +45 -0
- package/tests/sdd-preflight.test.ts +252 -8
- package/tests/sdd-research-capabilities.test.ts +256 -0
- package/tests/sdd-research-live.test.ts +241 -0
- package/tests/sdd-selection-transport.test.ts +504 -0
- package/tests/sdd-status.test.ts +51 -0
- package/tests/session-worktree-registry.test.ts +135 -0
- package/tests/shell-card.test.ts +24 -3
- package/tests/shell-changes-view.test.ts +471 -8
- package/tests/shell-changes.test.ts +168 -0
- package/tests/shell-prompt.test.ts +28 -6
- package/tests/shell-sidebar-banner.test.ts +23 -0
- package/tests/shell-sidebar-layout.test.ts +387 -0
- package/tests/shell-sidebar.test.ts +50 -0
- package/tests/shell-todo.test.ts +100 -11
- package/tests/startup-banner.test.ts +126 -0
- package/tests/telemetry-trigger.test.ts +3 -1
|
@@ -15,7 +15,7 @@ import test from "node:test";
|
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { applyModelConfig } from "../extensions/gentle-ai.ts";
|
|
17
17
|
import { resolveGentlePiAgentHome } from "../lib/agent-home.ts";
|
|
18
|
-
import { installSddAssets } from "../lib/sdd-preflight.ts";
|
|
18
|
+
import { getPackageAssetOwner, installPackageAssets, installSddAssets, type PackageAssetOwner } from "../lib/sdd-preflight.ts";
|
|
19
19
|
|
|
20
20
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
21
21
|
const MANAGED_EXEMPLAR_FILE = "gentle-ai-explore.md";
|
|
@@ -229,8 +229,12 @@ test("generated runtime modules and packed-package checks are deterministic", ()
|
|
|
229
229
|
test("package manifest ships and runs the checked-in package-local Gentle AI installer", () => {
|
|
230
230
|
const packageJson = readPackageJson();
|
|
231
231
|
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
232
|
+
const readme = readFileSync(join(PACKAGE_ROOT, "README.md"), "utf8");
|
|
232
233
|
|
|
233
234
|
assert.equal(packageJson.scripts?.postinstall, "node scripts/install-gentle-ai.mjs");
|
|
235
|
+
assert.match(readme, /run `node scripts\/install-gentle-ai\.mjs`/, "missing-binary recovery documentation must use the package postinstall entrypoint");
|
|
236
|
+
assert.match(readme, /installed `gentle-pi` package directory/, "recovery documentation must name the package working directory");
|
|
237
|
+
assert.match(readme, /if `GENTLE_PI_SKIP_GENTLE_AI_INSTALL` is set, remove or unset it before/i, "recovery documentation must prevent the installer skip from repeating");
|
|
234
238
|
assert.ok(packageJson.files?.includes("scripts/"));
|
|
235
239
|
assert.match(verifier, /"scripts\/install-gentle-ai\.mjs"/);
|
|
236
240
|
assert.match(verifier, /"scripts\/gentle-ai-installer\.mjs"/);
|
|
@@ -273,20 +277,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
273
277
|
);
|
|
274
278
|
});
|
|
275
279
|
|
|
276
|
-
test("package verification binds the published Gentle AI v2.
|
|
280
|
+
test("package verification binds the published Gentle AI v2.8.1 runtime pin", () => {
|
|
277
281
|
const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
|
|
278
282
|
const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
|
|
279
283
|
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
280
284
|
|
|
281
|
-
assert.match(installer, /INSTALLER_VERSION = "2\.
|
|
285
|
+
assert.match(installer, /INSTALLER_VERSION = "2\.8\.1"/);
|
|
282
286
|
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
|
|
283
|
-
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:
|
|
287
|
+
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:EixFRy7P3JLVi1ovr18pMxQIrtjDib9i7VEH9g4XRVE="/);
|
|
284
288
|
assert.match(installer, /GOTOOLCHAIN: "local"/);
|
|
285
289
|
assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
|
|
286
290
|
assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
|
|
287
291
|
assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
|
|
288
292
|
assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
|
|
289
|
-
assert.match(verifier, /v2\.
|
|
293
|
+
assert.match(verifier, /v2\.8\.1/);
|
|
290
294
|
});
|
|
291
295
|
|
|
292
296
|
|
|
@@ -469,6 +473,222 @@ test("packaged agents declare only tool names a Pi child session can resolve", (
|
|
|
469
473
|
}
|
|
470
474
|
});
|
|
471
475
|
|
|
476
|
+
function withIsolatedAssetHome(run: (agentHome: string) => void): void {
|
|
477
|
+
const temporary = mkdtempSync(join(tmpdir(), "gentle-asset-owners-"));
|
|
478
|
+
const previous = process.env.GENTLE_PI_AGENT_HOME;
|
|
479
|
+
try {
|
|
480
|
+
process.env.GENTLE_PI_AGENT_HOME = temporary;
|
|
481
|
+
run(temporary);
|
|
482
|
+
} finally {
|
|
483
|
+
if (previous === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
|
|
484
|
+
else process.env.GENTLE_PI_AGENT_HOME = previous;
|
|
485
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function installedAssetManifest(agentHome: string): ManagedAssetsManifest {
|
|
490
|
+
return JSON.parse(readFileSync(join(agentHome, "gentle-ai", "managed-assets.json"), "utf8"));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
test("selective delegation installation owns only generic agents", () => {
|
|
494
|
+
withIsolatedAssetHome((agentHome) => {
|
|
495
|
+
const result = installPackageAssets(agentHome, false, ["delegation"]);
|
|
496
|
+
assert.deepEqual(Object.keys(installedAssetManifest(agentHome).assets).sort(), [
|
|
497
|
+
"agents/gentle-ai-explore.md",
|
|
498
|
+
"agents/gentle-ai-verify.md",
|
|
499
|
+
"agents/gentle-ai-worker.md",
|
|
500
|
+
]);
|
|
501
|
+
assert.deepEqual(result, { agents: 3, chains: 0, support: 0, skipped: 0 });
|
|
502
|
+
assert.deepEqual(readdirSync(join(agentHome, "agents")).sort(), [
|
|
503
|
+
"gentle-ai-explore.md", "gentle-ai-verify.md", "gentle-ai-worker.md",
|
|
504
|
+
]);
|
|
505
|
+
assert.equal(existsSync(join(agentHome, "chains")), false);
|
|
506
|
+
assert.equal(existsSync(join(agentHome, "gentle-ai", "support")), false);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("selective installation retires only assets belonging to the selected owner", () => {
|
|
511
|
+
withIsolatedAssetHome((agentHome) => {
|
|
512
|
+
installSddAssets(agentHome, false);
|
|
513
|
+
const manifestPath = join(agentHome, "gentle-ai", "managed-assets.json");
|
|
514
|
+
const manifest = installedAssetManifest(agentHome);
|
|
515
|
+
for (const name of RETIRED_ADVERSARIAL_AGENTS) {
|
|
516
|
+
writeFileSync(join(agentHome, "agents", name), "Previously managed review agent\n");
|
|
517
|
+
manifest.assets[`agents/${name}`] = sha256("Previously managed review agent\n");
|
|
518
|
+
}
|
|
519
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
520
|
+
for (const owner of ["delegation", "sdd"] as const) {
|
|
521
|
+
installPackageAssets(agentHome, true, [owner]);
|
|
522
|
+
assert.deepEqual(installedAssetManifest(agentHome), manifest);
|
|
523
|
+
for (const name of RETIRED_ADVERSARIAL_AGENTS) {
|
|
524
|
+
assert.equal(readFileSync(join(agentHome, "agents", name), "utf8"), "Previously managed review agent\n");
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
writeFileSync(join(agentHome, "agents", "review-validator.md"), "User-modified retired agent\n");
|
|
528
|
+
installPackageAssets(agentHome, false, ["review"]);
|
|
529
|
+
assert.equal(existsSync(join(agentHome, "agents", "review-refuter.md")), false);
|
|
530
|
+
assert.equal(readFileSync(join(agentHome, "agents", "review-validator.md"), "utf8"), "User-modified retired agent\n");
|
|
531
|
+
for (const name of RETIRED_ADVERSARIAL_AGENTS) {
|
|
532
|
+
assert.equal(installedAssetManifest(agentHome).assets[`agents/${name}`], undefined);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const EXPECTED_OWNER_ASSETS: Record<PackageAssetOwner, readonly string[]> = {
|
|
538
|
+
delegation: [
|
|
539
|
+
"agents/gentle-ai-explore.md", "agents/gentle-ai-verify.md", "agents/gentle-ai-worker.md",
|
|
540
|
+
],
|
|
541
|
+
review: [
|
|
542
|
+
"agents/jd-fix-agent.md", "agents/jd-judge-a.md", "agents/jd-judge-b.md",
|
|
543
|
+
"agents/review-readability.md", "agents/review-reliability.md",
|
|
544
|
+
"agents/review-resilience.md", "agents/review-risk.md", "chains/4r-review.chain.md",
|
|
545
|
+
],
|
|
546
|
+
sdd: [
|
|
547
|
+
"agents/sdd-apply.md", "agents/sdd-archive.md", "agents/sdd-design.md",
|
|
548
|
+
"agents/sdd-explore.md", "agents/sdd-init.md", "agents/sdd-onboard.md",
|
|
549
|
+
"agents/sdd-proposal.md", "agents/sdd-remediate.md", "agents/sdd-research.md", "agents/sdd-spec.md",
|
|
550
|
+
"agents/sdd-status.md", "agents/sdd-sync.md", "agents/sdd-tasks.md", "agents/sdd-verify.md",
|
|
551
|
+
"chains/sdd-full.chain.md", "chains/sdd-plan.chain.md", "chains/sdd-verify.chain.md",
|
|
552
|
+
"gentle-ai/support/sdd-status-contract.md", "gentle-ai/support/strict-tdd-verify.md",
|
|
553
|
+
"gentle-ai/support/strict-tdd.md",
|
|
554
|
+
],
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
function assetFileKeys(root: string, prefix = ""): string[] {
|
|
558
|
+
if (!existsSync(root)) return [];
|
|
559
|
+
return readdirSync(root, { withFileTypes: true }).flatMap(entry => {
|
|
560
|
+
const key = `${prefix}${entry.name}`;
|
|
561
|
+
return entry.isDirectory() ? assetFileKeys(join(root, entry.name), `${key}/`) : [key];
|
|
562
|
+
}).sort();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
for (const owner of Object.keys(EXPECTED_OWNER_ASSETS) as PackageAssetOwner[]) {
|
|
566
|
+
test(`selective ${owner} installation covers its catalog without cross-owner files`, () => {
|
|
567
|
+
withIsolatedAssetHome((agentHome) => {
|
|
568
|
+
const expected = [...EXPECTED_OWNER_ASSETS[owner]].sort();
|
|
569
|
+
installPackageAssets(agentHome, false, [owner]);
|
|
570
|
+
assert.deepEqual(Object.keys(installedAssetManifest(agentHome).assets).sort(), expected);
|
|
571
|
+
assert.deepEqual(assetFileKeys(agentHome), [...expected, "gentle-ai/managed-assets.json"].sort());
|
|
572
|
+
for (const key of expected) {
|
|
573
|
+
assert.equal(getPackageAssetOwner(key), owner);
|
|
574
|
+
const source = join(PACKAGE_ROOT, "assets", key.replace(/^gentle-ai\//, ""));
|
|
575
|
+
assert.equal(readFileSync(join(agentHome, key), "utf8"), readFileSync(source, "utf8"));
|
|
576
|
+
}
|
|
577
|
+
const counts = installPackageAssets(agentHome, false, [owner, owner]);
|
|
578
|
+
assert.deepEqual(counts, { agents: 0, chains: 0, support: 0, skipped: expected.length });
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
test("legacy all-assets installation covers every packaged file with explicit ownership", () => {
|
|
584
|
+
const packaged = ["agents", "chains", "support"].flatMap(group =>
|
|
585
|
+
assetFileKeys(join(PACKAGE_ROOT, "assets", group), group === "support" ? "gentle-ai/support/" : `${group}/`),
|
|
586
|
+
).sort();
|
|
587
|
+
assert.deepEqual(packaged, Object.values(EXPECTED_OWNER_ASSETS).flat().sort());
|
|
588
|
+
for (const key of packaged) assert.notEqual(getPackageAssetOwner(key), undefined, key);
|
|
589
|
+
for (const key of ["agents/sdd-new.md", "gentle-ai/support/new.md", "toString", "__proto__"]) {
|
|
590
|
+
assert.equal(getPackageAssetOwner(key), undefined, "unknown assets must not default to SDD");
|
|
591
|
+
}
|
|
592
|
+
withIsolatedAssetHome((agentHome) => {
|
|
593
|
+
assert.deepEqual(installSddAssets(agentHome, false), { agents: 24, chains: 4, support: 3, skipped: 0 });
|
|
594
|
+
assert.deepEqual(Object.keys(installedAssetManifest(agentHome).assets).sort(), packaged);
|
|
595
|
+
assert.deepEqual(installSddAssets(agentHome, false), { agents: 0, chains: 0, support: 0, skipped: 31 });
|
|
596
|
+
assert.deepEqual(installSddAssets(agentHome, true), { agents: 24, chains: 4, support: 3, skipped: 0 });
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test("selective refresh preserves unselected ownership and selected user changes after an all-assets install", () => {
|
|
601
|
+
withIsolatedAssetHome((agentHome) => {
|
|
602
|
+
installSddAssets(agentHome, false);
|
|
603
|
+
const manifest = installedAssetManifest(agentHome);
|
|
604
|
+
const selectedUserKey = "agents/gentle-ai-explore.md";
|
|
605
|
+
const unselectedUserKey = "agents/sdd-apply.md";
|
|
606
|
+
for (const key of [selectedUserKey, unselectedUserKey, "agents/custom.md"]) {
|
|
607
|
+
writeFileSync(join(agentHome, key), "User-authored instructions\n");
|
|
608
|
+
}
|
|
609
|
+
const before = new Map(assetFileKeys(agentHome).map(key => [key, readFileSync(join(agentHome, key), "utf8")]));
|
|
610
|
+
assert.deepEqual(installPackageAssets(agentHome, true, ["delegation"]), { agents: 2, chains: 0, support: 0, skipped: 1 });
|
|
611
|
+
delete manifest.assets[selectedUserKey];
|
|
612
|
+
assert.deepEqual(installedAssetManifest(agentHome), manifest);
|
|
613
|
+
for (const [key, content] of before) {
|
|
614
|
+
if (key !== "gentle-ai/managed-assets.json") assert.equal(readFileSync(join(agentHome, key), "utf8"), content, key);
|
|
615
|
+
}
|
|
616
|
+
const manifestBytes = readFileSync(join(agentHome, "gentle-ai", "managed-assets.json"), "utf8");
|
|
617
|
+
assert.deepEqual(installPackageAssets(agentHome, true, []), { agents: 0, chains: 0, support: 0, skipped: 0 });
|
|
618
|
+
assert.equal(readFileSync(join(agentHome, "gentle-ai", "managed-assets.json"), "utf8"), manifestBytes);
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
test("selective review migration adopts only untouched legacy copies and preserves routing", () => {
|
|
623
|
+
for (const edited of [false, true]) {
|
|
624
|
+
withIsolatedAssetHome((agentHome) => {
|
|
625
|
+
mkdirSync(join(agentHome, "agents"));
|
|
626
|
+
const legacy = readFileSync(V014_REVIEW_RISK_FIXTURE, "utf8")
|
|
627
|
+
.replace("name: review-risk\n", "name: review-risk\nmodel: custom/model\nthinking: high\n")
|
|
628
|
+
+ (edited ? "\nUser review restrictions.\n" : "");
|
|
629
|
+
const target = join(agentHome, "agents", REVIEW_RISK_FILE);
|
|
630
|
+
writeFileSync(target, legacy);
|
|
631
|
+
installPackageAssets(agentHome, true, ["delegation"]);
|
|
632
|
+
assert.equal(readFileSync(target, "utf8"), legacy);
|
|
633
|
+
assert.equal(installedAssetManifest(agentHome).assets["agents/review-risk.md"], undefined);
|
|
634
|
+
const result = installPackageAssets(agentHome, true, ["review"]);
|
|
635
|
+
const actual = readFileSync(target, "utf8");
|
|
636
|
+
const manifest = installedAssetManifest(agentHome);
|
|
637
|
+
assert.equal(result.skipped, edited ? 1 : 0);
|
|
638
|
+
if (edited) {
|
|
639
|
+
assert.equal(actual, legacy);
|
|
640
|
+
assert.equal(manifest.assets["agents/review-risk.md"], undefined);
|
|
641
|
+
} else {
|
|
642
|
+
assert.notEqual(actual, legacy);
|
|
643
|
+
assert.match(actual, /model: custom\/model\nthinking: high/);
|
|
644
|
+
assert.equal(manifest.assets["agents/review-risk.md"], sha256(actual));
|
|
645
|
+
}
|
|
646
|
+
assert.equal(existsSync(join(agentHome, "agents", "sdd-apply.md")), false);
|
|
647
|
+
assert.equal(existsSync(join(agentHome, "gentle-ai", "support")), false);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test("unowned legacy research migrates by exact normalized hash, preserving routing and user edits", () => {
|
|
653
|
+
const packaged = readFileSync(join(PACKAGE_ROOT, "assets", "agents", "sdd-research.md"), "utf8");
|
|
654
|
+
const oldAdmission = "- Evidence grants for this runtime are `documentation=[]; open-web=[]`. Never infer evidence capability from bash, persistence tools, or any inherited tool; persistence tools are not evidence grants. Unsupported or undeclared classes deny admission and emit no claims.\n- Because this runtime declares no evidence grants, retain the selected request, persist a `blocked` outcome with no claims, and stop.\n";
|
|
655
|
+
const legacy = packaged
|
|
656
|
+
.replace(/## Parent Preflight Transport\n[\s\S]*?(?=## Skill Resolution Contract)/, "")
|
|
657
|
+
.replace(/## Bounded artifact handoff\n[\s\S]*?(?=## Memory Contract)/, "")
|
|
658
|
+
.replace(/ - fetch_content\n - web_search\n - source_check\n - get_search_content\n/, "")
|
|
659
|
+
.replace(/- Use the injected `## SDD Research Capabilities`[\s\S]*?(?=- Admission denial)/, oldAdmission)
|
|
660
|
+
.replace(/Use `done` only when all selected questions[\s\S]*?product decisions remain separately confirmed by the parent\./i, "For this runtime the outcome is `blocked` with an admission denial and no claims.");
|
|
661
|
+
const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, "assets", "migrations", "managed-assets-v2.5.0.json"), "utf8"));
|
|
662
|
+
assert.equal(sha256(legacy), manifest.assets["agents/sdd-research.md"], "fixture reconstruction must match observed old package bytes");
|
|
663
|
+
const temporary = mkdtempSync(join(tmpdir(), "gentle-research-migration-"));
|
|
664
|
+
const previous = process.env.GENTLE_PI_AGENT_HOME;
|
|
665
|
+
try {
|
|
666
|
+
for (const edited of [false, true]) {
|
|
667
|
+
const agentHome = join(temporary, edited ? "edited" : "legacy");
|
|
668
|
+
process.env.GENTLE_PI_AGENT_HOME = agentHome;
|
|
669
|
+
mkdirSync(join(agentHome, "agents"), { recursive: true });
|
|
670
|
+
const target = join(agentHome, "agents", "sdd-research.md");
|
|
671
|
+
const routed = legacy.replace("name: sdd-research\n", "name: sdd-research\nmodel: custom/model\nthinking: high\n") + (edited ? "\nUser research restrictions.\n" : "");
|
|
672
|
+
writeFileSync(target, routed);
|
|
673
|
+
installSddAssets(temporary, true);
|
|
674
|
+
const actual = readFileSync(target, "utf8");
|
|
675
|
+
if (edited) assert.equal(actual, routed);
|
|
676
|
+
else {
|
|
677
|
+
assert.match(actual, / - fetch_content/);
|
|
678
|
+
assert.match(actual, /model: custom\/model\nthinking: high/);
|
|
679
|
+
const ownership = JSON.parse(readFileSync(join(agentHome, "gentle-ai", "managed-assets.json"), "utf8"));
|
|
680
|
+
assert.equal(ownership.assets["agents/sdd-research.md"], sha256(actual));
|
|
681
|
+
installSddAssets(temporary, true);
|
|
682
|
+
assert.equal(readFileSync(target, "utf8"), actual, "subsequent refresh keeps adopted model routing");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
} finally {
|
|
686
|
+
if (previous === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
|
|
687
|
+
else process.env.GENTLE_PI_AGENT_HOME = previous;
|
|
688
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
|
|
472
692
|
test("the retired Pi adversarial role agents are not packaged", () => {
|
|
473
693
|
// gentle-pi#311 P5: the refuter and targeted validator verdicts execute
|
|
474
694
|
// through Go-owned pi processes via provider-rendered self-contained
|
|
@@ -1288,9 +1508,9 @@ test("pi-pretty wrapper uses real package path resolution for pnpm symlink insta
|
|
|
1288
1508
|
assert.match(wrapper, /quietToolsEnabled/);
|
|
1289
1509
|
});
|
|
1290
1510
|
|
|
1291
|
-
test("v2.
|
|
1511
|
+
test("v2.6.1 release package and runtime stop before publication", () => {
|
|
1292
1512
|
const packageJson = readPackageJson();
|
|
1293
|
-
assert.equal(packageJson.version, "2.
|
|
1513
|
+
assert.equal(packageJson.version, "2.6.1", "the release manifest must remain explicitly pinned to v2.6.1");
|
|
1294
1514
|
assert.equal(
|
|
1295
1515
|
packageJson.scripts?.test,
|
|
1296
1516
|
"node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",
|
|
@@ -1333,3 +1553,8 @@ test("README documents dynamic Gentle AI RDD ownership and the installed permiss
|
|
|
1333
1553
|
}
|
|
1334
1554
|
assert.doesNotMatch(readme, /New ordinary review uses compact `gentle_review` `start -> finalize -> validate`\./);
|
|
1335
1555
|
});
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
test("package verification explicitly requires the managed remediation actor", () => {
|
|
1559
|
+
assert.match(readFileSync(join(PACKAGE_ROOT, "scripts/verify-package-files.mjs"), "utf8"), /assets\/agents\/sdd-remediate\.md/);
|
|
1560
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import {
|
|
7
|
+
applyOrchestratorSettings,
|
|
8
|
+
parseOrchestratorModelRef,
|
|
9
|
+
readOrchestratorSettings,
|
|
10
|
+
restoreOrchestratorSettings,
|
|
11
|
+
} from "../lib/profiles-orchestrator.ts";
|
|
12
|
+
|
|
13
|
+
function fixture(t: test.TestContext) {
|
|
14
|
+
const root = mkdtempSync(join(tmpdir(), "gentle-pi-orchestrator-"));
|
|
15
|
+
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
16
|
+
return { root, settingsPath: join(root, "settings.json") };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test("orchestrator model refs split only on a real provider boundary", () => {
|
|
20
|
+
assert.deepEqual(parseOrchestratorModelRef("nan/glm5.3"), {
|
|
21
|
+
provider: "nan",
|
|
22
|
+
model: "glm5.3",
|
|
23
|
+
});
|
|
24
|
+
assert.deepEqual(parseOrchestratorModelRef(" openai-codex/gpt-6-astra "), {
|
|
25
|
+
provider: "openai-codex",
|
|
26
|
+
model: "gpt-6-astra",
|
|
27
|
+
});
|
|
28
|
+
// A nested slash stays with the model half.
|
|
29
|
+
assert.deepEqual(parseOrchestratorModelRef("nan/qwen3.8/flash"), {
|
|
30
|
+
provider: "nan",
|
|
31
|
+
model: "qwen3.8/flash",
|
|
32
|
+
});
|
|
33
|
+
for (const value of ["glm5.3", "/glm5.3", "nan/", "nan", "", " ", 42, undefined, null]) {
|
|
34
|
+
assert.equal(parseOrchestratorModelRef(value), undefined, `rejects ${JSON.stringify(value)}`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("reading the orchestrator distinguishes missing, unreadable, unset and set", (t) => {
|
|
39
|
+
const { settingsPath } = fixture(t);
|
|
40
|
+
assert.deepEqual(readOrchestratorSettings(settingsPath), { status: "missing" });
|
|
41
|
+
|
|
42
|
+
writeFileSync(settingsPath, "{ not json");
|
|
43
|
+
assert.equal(readOrchestratorSettings(settingsPath).status, "invalid");
|
|
44
|
+
|
|
45
|
+
writeFileSync(settingsPath, JSON.stringify(["not", "an", "object"]));
|
|
46
|
+
assert.equal(readOrchestratorSettings(settingsPath).status, "invalid");
|
|
47
|
+
|
|
48
|
+
writeFileSync(settingsPath, JSON.stringify({ theme: "Gentleman-Cute" }));
|
|
49
|
+
const unset = readOrchestratorSettings(settingsPath);
|
|
50
|
+
assert.equal(unset.status, "valid");
|
|
51
|
+
assert.equal(unset.status === "valid" ? unset.entry : undefined, undefined);
|
|
52
|
+
|
|
53
|
+
writeFileSync(
|
|
54
|
+
settingsPath,
|
|
55
|
+
JSON.stringify({
|
|
56
|
+
defaultProvider: "nan",
|
|
57
|
+
defaultModel: "glm5.3",
|
|
58
|
+
defaultThinkingLevel: "high",
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
const set = readOrchestratorSettings(settingsPath);
|
|
62
|
+
assert.deepEqual(set.status === "valid" ? set.entry : undefined, {
|
|
63
|
+
model: "nan/glm5.3",
|
|
64
|
+
thinking: "high",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// A half-written pair is "unset", never a fabricated provider or model.
|
|
68
|
+
writeFileSync(settingsPath, JSON.stringify({ defaultProvider: "nan" }));
|
|
69
|
+
const half = readOrchestratorSettings(settingsPath);
|
|
70
|
+
assert.equal(half.status, "valid");
|
|
71
|
+
assert.equal(half.status === "valid" ? half.entry : undefined, undefined);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("an orchestrator entry without a model never touches the settings file", (t) => {
|
|
75
|
+
const { root, settingsPath } = fixture(t);
|
|
76
|
+
const before = '{\n "packages": [\n "npm:pi-mcp-adapter"\n ],\n "theme": "Gentleman-Cute"\n}\n';
|
|
77
|
+
writeFileSync(settingsPath, before);
|
|
78
|
+
assert.deepEqual(applyOrchestratorSettings(settingsPath, { thinking: "high" }), {
|
|
79
|
+
status: "unchanged",
|
|
80
|
+
});
|
|
81
|
+
assert.equal(readFileSync(settingsPath, "utf8"), before);
|
|
82
|
+
|
|
83
|
+
// Also true when there is no file at all: nothing to write, nothing created.
|
|
84
|
+
const absent = join(root, "absent-settings.json");
|
|
85
|
+
assert.deepEqual(applyOrchestratorSettings(absent, {}), { status: "unchanged" });
|
|
86
|
+
assert.equal(existsSync(absent), false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("applying the orchestrator preserves every unrelated settings key", (t) => {
|
|
90
|
+
const { settingsPath } = fixture(t);
|
|
91
|
+
const before =
|
|
92
|
+
JSON.stringify(
|
|
93
|
+
{
|
|
94
|
+
packages: ["npm:pi-mcp-adapter", "some-package"],
|
|
95
|
+
theme: "Gentleman-Cute",
|
|
96
|
+
tuiMode: "fullscreen",
|
|
97
|
+
telemetry: { enabled: true },
|
|
98
|
+
defaultProvider: "nan",
|
|
99
|
+
defaultModel: "deepseek-v4-flash",
|
|
100
|
+
defaultThinkingLevel: "high",
|
|
101
|
+
images: { autoResize: true },
|
|
102
|
+
},
|
|
103
|
+
null,
|
|
104
|
+
2,
|
|
105
|
+
) + "\n";
|
|
106
|
+
writeFileSync(settingsPath, before);
|
|
107
|
+
|
|
108
|
+
const result = applyOrchestratorSettings(settingsPath, {
|
|
109
|
+
model: "nan/glm5.3",
|
|
110
|
+
thinking: "max",
|
|
111
|
+
});
|
|
112
|
+
assert.equal(result.status, "written");
|
|
113
|
+
assert.equal(result.status === "written" ? result.previous : undefined, before);
|
|
114
|
+
|
|
115
|
+
const after = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
116
|
+
assert.equal(after.defaultProvider, "nan");
|
|
117
|
+
assert.equal(after.defaultModel, "glm5.3");
|
|
118
|
+
assert.equal(after.defaultThinkingLevel, "max");
|
|
119
|
+
assert.deepEqual(after.packages, ["npm:pi-mcp-adapter", "some-package"]);
|
|
120
|
+
assert.equal(after.theme, "Gentleman-Cute");
|
|
121
|
+
assert.equal(after.tuiMode, "fullscreen");
|
|
122
|
+
assert.deepEqual(after.telemetry, { enabled: true });
|
|
123
|
+
assert.deepEqual(after.images, { autoResize: true });
|
|
124
|
+
assert.equal(readFileSync(settingsPath, "utf8").endsWith("\n"), true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("a missing settings file is created with only the orchestrator keys", (t) => {
|
|
128
|
+
const { settingsPath } = fixture(t);
|
|
129
|
+
const result = applyOrchestratorSettings(settingsPath, { model: "nan/glm5.3" });
|
|
130
|
+
assert.equal(result.status, "written");
|
|
131
|
+
assert.equal(result.status === "written" ? result.previous : "sentinel", undefined);
|
|
132
|
+
assert.deepEqual(JSON.parse(readFileSync(settingsPath, "utf8")), {
|
|
133
|
+
defaultProvider: "nan",
|
|
134
|
+
defaultModel: "glm5.3",
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("an orchestrator entry without thinking clears a stale thinking level", (t) => {
|
|
139
|
+
const { settingsPath } = fixture(t);
|
|
140
|
+
writeFileSync(
|
|
141
|
+
settingsPath,
|
|
142
|
+
JSON.stringify({
|
|
143
|
+
defaultProvider: "nan",
|
|
144
|
+
defaultModel: "glm5.3",
|
|
145
|
+
defaultThinkingLevel: "high",
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
const result = applyOrchestratorSettings(settingsPath, { model: "nan/glm5.3-flash" });
|
|
149
|
+
assert.equal(result.status, "written");
|
|
150
|
+
const after = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
151
|
+
assert.equal(after.defaultModel, "glm5.3-flash");
|
|
152
|
+
assert.equal("defaultThinkingLevel" in after, false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("re-applying the same selection is a no-op", (t) => {
|
|
156
|
+
const { settingsPath } = fixture(t);
|
|
157
|
+
assert.equal(applyOrchestratorSettings(settingsPath, { model: "nan/glm5.3" }).status, "written");
|
|
158
|
+
const bytes = readFileSync(settingsPath, "utf8");
|
|
159
|
+
assert.deepEqual(applyOrchestratorSettings(settingsPath, { model: "nan/glm5.3" }), {
|
|
160
|
+
status: "unchanged",
|
|
161
|
+
});
|
|
162
|
+
assert.equal(readFileSync(settingsPath, "utf8"), bytes);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("an unusable target is refused instead of being overwritten", (t) => {
|
|
166
|
+
const { settingsPath } = fixture(t);
|
|
167
|
+
const before = "{ this is not json\n";
|
|
168
|
+
writeFileSync(settingsPath, before);
|
|
169
|
+
const broken = applyOrchestratorSettings(settingsPath, { model: "nan/glm5.3" });
|
|
170
|
+
assert.equal(broken.status, "invalid");
|
|
171
|
+
assert.equal(readFileSync(settingsPath, "utf8"), before);
|
|
172
|
+
|
|
173
|
+
writeFileSync(settingsPath, "[]");
|
|
174
|
+
const notObject = applyOrchestratorSettings(settingsPath, { model: "nan/glm5.3" });
|
|
175
|
+
assert.equal(notObject.status, "invalid");
|
|
176
|
+
assert.equal(readFileSync(settingsPath, "utf8"), "[]");
|
|
177
|
+
|
|
178
|
+
// A model id that is not a provider/model pair is refused for the same reason:
|
|
179
|
+
// there is no provider to write, and guessing one would corrupt the selection.
|
|
180
|
+
writeFileSync(settingsPath, JSON.stringify({ theme: "Gentleman-Cute" }));
|
|
181
|
+
const noProvider = applyOrchestratorSettings(settingsPath, { model: "glm5.3" });
|
|
182
|
+
assert.equal(noProvider.status, "invalid");
|
|
183
|
+
assert.deepEqual(JSON.parse(readFileSync(settingsPath, "utf8")), { theme: "Gentleman-Cute" });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("restoring returns the previous bytes, or removes a file the write created", (t) => {
|
|
187
|
+
const { root, settingsPath } = fixture(t);
|
|
188
|
+
const before = '{\n "theme": "Gentleman-Cute"\n}\n';
|
|
189
|
+
writeFileSync(settingsPath, before);
|
|
190
|
+
const written = applyOrchestratorSettings(settingsPath, { model: "nan/glm5.3" });
|
|
191
|
+
assert.equal(written.status, "written");
|
|
192
|
+
restoreOrchestratorSettings(
|
|
193
|
+
settingsPath,
|
|
194
|
+
written.status === "written" ? written.previous : undefined,
|
|
195
|
+
);
|
|
196
|
+
assert.equal(readFileSync(settingsPath, "utf8"), before);
|
|
197
|
+
|
|
198
|
+
// A file the write created restores by disappearing, not by holding an empty object.
|
|
199
|
+
const createdPath = join(root, "created-settings.json");
|
|
200
|
+
const created = applyOrchestratorSettings(createdPath, { model: "nan/glm5.3" });
|
|
201
|
+
assert.equal(created.status, "written");
|
|
202
|
+
assert.equal(existsSync(createdPath), true);
|
|
203
|
+
restoreOrchestratorSettings(
|
|
204
|
+
createdPath,
|
|
205
|
+
created.status === "written" ? created.previous : undefined,
|
|
206
|
+
);
|
|
207
|
+
assert.equal(existsSync(createdPath), false);
|
|
208
|
+
});
|
|
@@ -173,6 +173,7 @@ test("quiet tool rendering registers noisy built-in tools", () => {
|
|
|
173
173
|
for (const toolName of ["read", "bash", "grep", "find", "ls", "edit", "write"]) {
|
|
174
174
|
const tool = tools.get(toolName);
|
|
175
175
|
assert.ok(tool, `missing quiet renderer for ${toolName}`);
|
|
176
|
+
assert.equal(tool.renderShell, "self", `${toolName} must opt out of Pi's painted Box`);
|
|
176
177
|
assert.equal(typeof tool.execute, "function", `${toolName} must delegate execution`);
|
|
177
178
|
assert.ok(tool.parameters, `${toolName} must preserve built-in parameters`);
|
|
178
179
|
}
|
|
@@ -199,6 +199,16 @@ test("worker asset never claims completion while a required verification command
|
|
|
199
199
|
);
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
+
test("worker keeps candidate review disposition and lifecycle parent-owned", () => {
|
|
203
|
+
for (const clause of [
|
|
204
|
+
"The primary parent owns candidate review disposition and lifecycle, including preflight and any explicit candidate-level opt-out.",
|
|
205
|
+
"Never search for, request, or invoke review tools, including `gentle_review`.",
|
|
206
|
+
"Missing review tools never block this worker's implementation or verification handoff.",
|
|
207
|
+
]) {
|
|
208
|
+
assert.ok(worker.includes(clause), `worker asset is missing: ${clause}`);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
202
212
|
test("worker asset keeps the existing Return contract fields", () => {
|
|
203
213
|
for (const field of [
|
|
204
214
|
"status: completed | partial | blocked | interaction_required",
|