gentle-pi 2.4.0 → 2.6.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 +292 -25
- package/assets/agents/gentle-ai-worker.md +13 -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 +39 -11
- package/assets/orchestrator.md +5 -5
- 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 +65 -0
- package/docs/delegated-verification.md +25 -0
- package/docs/telemetry.md +94 -0
- package/docs/windows-startup-console-visibility.md +18 -0
- package/extensions/ask-user-choice.ts +159 -25
- package/extensions/codegraph-tools.ts +95 -5
- package/extensions/gentle-agents.ts +1337 -0
- package/extensions/gentle-ai.ts +2916 -386
- package/extensions/gentle-shell.ts +650 -0
- package/extensions/gentle-todo.ts +234 -0
- package/extensions/quiet-tools.ts +2 -1
- package/extensions/runtime-metrics.ts +130 -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 +315 -0
- package/lib/agents-history.ts +88 -0
- package/lib/agents-messaging.ts +187 -0
- package/lib/agents-protocol.ts +501 -0
- package/lib/agents-runner.ts +1012 -0
- package/lib/agents-thread-view.ts +57 -0
- package/lib/agents-transcript.ts +87 -0
- package/lib/agents-view-layout.ts +40 -0
- package/lib/agents-view.ts +914 -0
- package/lib/agents-widget.ts +241 -0
- package/lib/gentle-ai-binary.ts +3 -1
- package/lib/gentle-ai-renderer.ts +143 -25
- 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 +371 -13
- package/lib/orchestrator-presence.ts +337 -0
- package/lib/profiles-orchestrator.ts +203 -0
- package/lib/review-candidate-view-owner.ts +427 -0
- package/lib/review-candidate-view.ts +150 -48
- package/lib/review-consent-component.ts +247 -0
- package/lib/review-consent-ui.ts +110 -0
- package/lib/review-host-relay.ts +28 -0
- package/lib/review-integration-v2.ts +243 -11
- package/lib/review-last-event-controller.ts +8 -4
- package/lib/review-relay-contract.ts +11 -0
- package/lib/review-reminder-receipt.ts +74 -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 +240 -0
- package/lib/runtime-metrics-children.ts +199 -0
- package/lib/runtime-metrics-delivery.ts +68 -0
- package/lib/runtime-metrics-native.ts +166 -0
- package/lib/runtime-metrics-pi-identity.ts +113 -0
- package/lib/runtime-metrics-policy.ts +51 -0
- package/lib/runtime-metrics.ts +255 -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 +184 -0
- package/lib/shell-card.ts +133 -0
- package/lib/shell-changes-view.ts +530 -0
- package/lib/shell-changes.ts +290 -0
- package/lib/shell-gauge.ts +40 -0
- package/lib/shell-prompt.ts +115 -0
- 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 +297 -0
- package/lib/shell-usage-view.ts +76 -0
- package/lib/shell-usage.ts +246 -0
- package/lib/telemetry-trigger.ts +153 -0
- package/package.json +8 -5
- package/runtime/gentle-ai-binary.mjs +3 -1
- package/runtime/native-review-cli.mjs +370 -12
- package/runtime/review-integration-v2.mjs +243 -11
- package/runtime/review-relay-contract.mjs +11 -0
- package/runtime/review-risk-assessment.mjs +340 -0
- package/runtime/telemetry-trigger.mjs +154 -0
- package/scripts/build-runtime-modules.mjs +11 -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 +38 -2
- package/scripts/types-baseline.json +99 -0
- package/scripts/verify-package-files.mjs +8 -2
- package/skills/_shared/review-ledger-contract.md +20 -2
- 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 +205 -0
- package/tests/agents-fake-child.ts +66 -0
- package/tests/agents-grouping.test.ts +179 -0
- package/tests/agents-history.test.ts +54 -0
- package/tests/agents-integration.test.ts +100 -0
- package/tests/agents-messaging.test.ts +94 -0
- package/tests/agents-protocol.test.ts +198 -0
- package/tests/agents-queries.test.ts +190 -0
- package/tests/agents-responsive.test.ts +43 -0
- package/tests/agents-runner-process.test.ts +111 -0
- package/tests/agents-runner.test.ts +959 -0
- package/tests/agents-thread-view.test.ts +45 -0
- package/tests/agents-transcript.test.ts +30 -0
- package/tests/agents-view.test.ts +685 -0
- package/tests/agents-widget.test.ts +141 -0
- package/tests/artifact-language.test.ts +25 -2
- package/tests/ask-user-choice.test.ts +325 -5
- package/tests/asset-installation-runtime.test.ts +108 -0
- package/tests/autonomous-guard.test.ts +116 -1
- package/tests/codegraph-tools.test.ts +112 -2
- package/tests/delegated-key-learnings-contract.test.ts +1 -1
- package/tests/devbinary/native-review-parity.devtest.ts +110 -0
- package/tests/feature-request-form.test.ts +67 -0
- package/tests/fixtures/agents-messaging-child.mjs +5 -0
- package/tests/fixtures/agents-process-child.mjs +23 -0
- package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
- package/tests/gentle-agents.test.ts +2168 -0
- 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 +103 -0
- package/tests/gentle-ai.test.ts +971 -15
- package/tests/gentle-card-text.ts +35 -0
- package/tests/gentle-shell.test.ts +818 -0
- package/tests/gentle-todo.test.ts +226 -0
- 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-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 +27 -1
- package/tests/native-review-cli.test.ts +317 -3
- 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 +43 -29
- package/tests/native-sdd-attempt-authority.test.ts +7 -2
- package/tests/orchestrator-budget.test.ts +69 -0
- package/tests/orchestrator-presence.test.ts +389 -0
- package/tests/orchestrator-rdd-ownership.test.ts +9 -0
- package/tests/package-manifest.test.ts +243 -7
- package/tests/profiles-orchestrator.test.ts +208 -0
- package/tests/quiet-tool-rendering.test.ts +97 -37
- package/tests/rdd-aware-verification-contract.test.ts +226 -0
- package/tests/rdd-status-line.test.ts +286 -0
- package/tests/review-agent-end-preflight.test.ts +332 -24
- package/tests/review-candidate-view.test.ts +751 -7
- package/tests/review-consent-ui.test.ts +352 -0
- package/tests/review-contract-prompt.test.ts +17 -0
- package/tests/review-controller-native-recovery.test.ts +29 -4
- package/tests/review-controller-native-routing.test.ts +884 -7
- package/tests/review-controller-workspace-root.test.ts +45 -2
- package/tests/review-controller.test.ts +26 -1
- package/tests/review-host-relay-restart-parity.test.ts +142 -1
- package/tests/review-host-relay-routing.test.ts +384 -8
- 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 +276 -0
- package/tests/review-last-event-closure.test.ts +112 -3
- package/tests/review-ledger-contract.test.ts +61 -6
- package/tests/review-relay-contract.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +62 -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 +656 -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 +156 -0
- package/tests/runtime-harness.mjs +447 -39
- package/tests/runtime-metrics-children.test.ts +206 -0
- package/tests/runtime-metrics-delivery.test.ts +85 -0
- package/tests/runtime-metrics-extension.test.ts +187 -0
- package/tests/runtime-metrics-native.test.ts +209 -0
- package/tests/runtime-metrics-pi-identity.test.ts +113 -0
- package/tests/runtime-metrics-policy.test.ts +62 -0
- package/tests/runtime-metrics.test.ts +184 -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-bar.test.ts +176 -0
- package/tests/shell-card.test.ts +139 -0
- package/tests/shell-changes-view.test.ts +609 -0
- package/tests/shell-changes.test.ts +350 -0
- package/tests/shell-prompt.test.ts +140 -0
- 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 +259 -0
- package/tests/shell-usage-view.test.ts +62 -0
- package/tests/shell-usage.test.ts +197 -0
- package/tests/startup-banner.test.ts +126 -0
- package/tests/telemetry-trigger.test.ts +351 -0
|
@@ -60,6 +60,15 @@ test("rendered parent prompt keeps the RDD boundary while omitting lifecycle mir
|
|
|
60
60
|
for (const marker of ["Authority-First Terminal Procedure", "reconcile-terminal-mirrors", "next_transition"]) {
|
|
61
61
|
assert.ok(!rendered.includes(marker), `rendered parent prompt leaked: ${marker}`);
|
|
62
62
|
}
|
|
63
|
+
// gentle-pi#661: getOrchestratorPrompt()'s no-argument default renders the
|
|
64
|
+
// "unknown (native status unavailable)" RDD status line -- the longest of
|
|
65
|
+
// the three renderable forms -- so this IS the worst-case render the 8 KiB
|
|
66
|
+
// budget below must cover, not a smaller placeholder production later
|
|
67
|
+
// exceeds.
|
|
68
|
+
assert.ok(
|
|
69
|
+
rendered.includes("Receipt-driven development: unknown (native status unavailable)"),
|
|
70
|
+
"the default render must include the worst-case RDD status line",
|
|
71
|
+
);
|
|
63
72
|
assert.ok(Buffer.byteLength(rendered, "utf8") <= 8192, "the rendered parent prompt must stay below the reduced 8 KiB budget");
|
|
64
73
|
});
|
|
65
74
|
|
|
@@ -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";
|
|
@@ -80,6 +80,8 @@ interface PackageJson {
|
|
|
80
80
|
files?: string[];
|
|
81
81
|
scripts?: Record<string, string>;
|
|
82
82
|
dependencies?: Record<string, string>;
|
|
83
|
+
peerDependencies?: Record<string, string>;
|
|
84
|
+
devDependencies?: Record<string, string>;
|
|
83
85
|
bundledDependencies?: string[];
|
|
84
86
|
bundleDependencies?: string[];
|
|
85
87
|
repository?: {
|
|
@@ -99,6 +101,15 @@ function readPackageJson(): PackageJson {
|
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
test("package declares the tested Pi minimum required for agent_settled", () => {
|
|
105
|
+
const manifest = readPackageJson();
|
|
106
|
+
assert.equal(manifest.peerDependencies?.["@earendil-works/pi-coding-agent"], ">=0.85.1");
|
|
107
|
+
assert.equal(manifest.devDependencies?.["@earendil-works/pi-coding-agent"], "0.85.1");
|
|
108
|
+
const readme = readFileSync(join(PACKAGE_ROOT, "README.md"), "utf8");
|
|
109
|
+
assert.match(readme, /Pi 0\.85\.1 or newer/);
|
|
110
|
+
assert.match(readme, /agent_settled/);
|
|
111
|
+
});
|
|
112
|
+
|
|
102
113
|
test("package manifest has no obsolete native activation build surface", () => {
|
|
103
114
|
const packageJson = readPackageJson();
|
|
104
115
|
const manifest = JSON.stringify(packageJson);
|
|
@@ -218,8 +229,12 @@ test("generated runtime modules and packed-package checks are deterministic", ()
|
|
|
218
229
|
test("package manifest ships and runs the checked-in package-local Gentle AI installer", () => {
|
|
219
230
|
const packageJson = readPackageJson();
|
|
220
231
|
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
232
|
+
const readme = readFileSync(join(PACKAGE_ROOT, "README.md"), "utf8");
|
|
221
233
|
|
|
222
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");
|
|
223
238
|
assert.ok(packageJson.files?.includes("scripts/"));
|
|
224
239
|
assert.match(verifier, /"scripts\/install-gentle-ai\.mjs"/);
|
|
225
240
|
assert.match(verifier, /"scripts\/gentle-ai-installer\.mjs"/);
|
|
@@ -262,20 +277,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
262
277
|
);
|
|
263
278
|
});
|
|
264
279
|
|
|
265
|
-
test("package verification binds the published Gentle AI v2.
|
|
280
|
+
test("package verification binds the published Gentle AI v2.8.0 runtime pin", () => {
|
|
266
281
|
const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
|
|
267
282
|
const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
|
|
268
283
|
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
269
284
|
|
|
270
|
-
assert.match(installer, /INSTALLER_VERSION = "2\.
|
|
285
|
+
assert.match(installer, /INSTALLER_VERSION = "2\.8\.0"/);
|
|
271
286
|
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
|
|
272
|
-
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:
|
|
287
|
+
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:QNdrYDxq\/Bi08o9PCk0jc4r5YObLdFmRCLXjKMqsze8="/);
|
|
273
288
|
assert.match(installer, /GOTOOLCHAIN: "local"/);
|
|
274
289
|
assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
|
|
275
290
|
assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
|
|
276
291
|
assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
|
|
277
292
|
assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
|
|
278
|
-
assert.match(verifier, /v2\.
|
|
293
|
+
assert.match(verifier, /v2\.8\.0/);
|
|
279
294
|
});
|
|
280
295
|
|
|
281
296
|
|
|
@@ -458,6 +473,222 @@ test("packaged agents declare only tool names a Pi child session can resolve", (
|
|
|
458
473
|
}
|
|
459
474
|
});
|
|
460
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
|
+
|
|
461
692
|
test("the retired Pi adversarial role agents are not packaged", () => {
|
|
462
693
|
// gentle-pi#311 P5: the refuter and targeted validator verdicts execute
|
|
463
694
|
// through Go-owned pi processes via provider-rendered self-contained
|
|
@@ -1277,9 +1508,9 @@ test("pi-pretty wrapper uses real package path resolution for pnpm symlink insta
|
|
|
1277
1508
|
assert.match(wrapper, /quietToolsEnabled/);
|
|
1278
1509
|
});
|
|
1279
1510
|
|
|
1280
|
-
test("v2.
|
|
1511
|
+
test("v2.6.0 release package and runtime stop before publication", () => {
|
|
1281
1512
|
const packageJson = readPackageJson();
|
|
1282
|
-
assert.equal(packageJson.version, "2.
|
|
1513
|
+
assert.equal(packageJson.version, "2.6.0", "the release manifest must remain explicitly pinned to v2.6.0");
|
|
1283
1514
|
assert.equal(
|
|
1284
1515
|
packageJson.scripts?.test,
|
|
1285
1516
|
"node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",
|
|
@@ -1322,3 +1553,8 @@ test("README documents dynamic Gentle AI RDD ownership and the installed permiss
|
|
|
1322
1553
|
}
|
|
1323
1554
|
assert.doesNotMatch(readme, /New ordinary review uses compact `gentle_review` `start -> finalize -> validate`\./);
|
|
1324
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
|
+
});
|