gentle-pi 2.5.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 +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 +65 -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 +799 -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 +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 +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 +283 -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 +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 +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 +283 -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 +99 -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 +1460 -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 +12 -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 +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-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
package/lib/sdd-preflight.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
@@ -11,13 +11,61 @@ export type { SddArtifactStore };
|
|
|
11
11
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
12
12
|
const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
|
|
13
13
|
const MANAGED_ASSETS_MANIFEST = "managed-assets.json";
|
|
14
|
+
const MANAGED_ASSETS_LOCK = "managed-assets.lock";
|
|
14
15
|
const MANAGED_ASSETS_SCHEMA_VERSION = 1;
|
|
16
|
+
const MANAGED_ASSETS_LOCK_TIMEOUT_MS = 5_000;
|
|
17
|
+
const MANAGED_ASSETS_LOCK_RETRY_MS = 25;
|
|
15
18
|
const LEGACY_MANAGED_ASSET_MANIFESTS = Object.freeze([
|
|
16
19
|
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.10.7.json"), version: "0.10.7" },
|
|
17
20
|
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.13.json"), version: "0.13.0" },
|
|
18
21
|
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.14.json"), version: "0.14.0" },
|
|
22
|
+
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v2.5.0.json"), version: "2.5.0" },
|
|
19
23
|
]);
|
|
20
24
|
|
|
25
|
+
const ASSET_OWNER_BY_KEY = Object.freeze({
|
|
26
|
+
"agents/gentle-ai-explore.md": "delegation",
|
|
27
|
+
"agents/gentle-ai-verify.md": "delegation",
|
|
28
|
+
"agents/gentle-ai-worker.md": "delegation",
|
|
29
|
+
"agents/jd-fix-agent.md": "review",
|
|
30
|
+
"agents/jd-judge-a.md": "review",
|
|
31
|
+
"agents/jd-judge-b.md": "review",
|
|
32
|
+
"agents/review-readability.md": "review",
|
|
33
|
+
"agents/review-reliability.md": "review",
|
|
34
|
+
"agents/review-resilience.md": "review",
|
|
35
|
+
"agents/review-risk.md": "review",
|
|
36
|
+
"agents/review-refuter.md": "review",
|
|
37
|
+
"agents/review-validator.md": "review",
|
|
38
|
+
"chains/4r-review.chain.md": "review",
|
|
39
|
+
"agents/sdd-apply.md": "sdd",
|
|
40
|
+
"agents/sdd-archive.md": "sdd",
|
|
41
|
+
"agents/sdd-design.md": "sdd",
|
|
42
|
+
"agents/sdd-explore.md": "sdd",
|
|
43
|
+
"agents/sdd-init.md": "sdd",
|
|
44
|
+
"agents/sdd-onboard.md": "sdd",
|
|
45
|
+
"agents/sdd-proposal.md": "sdd",
|
|
46
|
+
"agents/sdd-research.md": "sdd",
|
|
47
|
+
"agents/sdd-remediate.md": "sdd",
|
|
48
|
+
"agents/sdd-spec.md": "sdd",
|
|
49
|
+
"agents/sdd-status.md": "sdd",
|
|
50
|
+
"agents/sdd-sync.md": "sdd",
|
|
51
|
+
"agents/sdd-tasks.md": "sdd",
|
|
52
|
+
"agents/sdd-verify.md": "sdd",
|
|
53
|
+
"chains/sdd-full.chain.md": "sdd",
|
|
54
|
+
"chains/sdd-plan.chain.md": "sdd",
|
|
55
|
+
"chains/sdd-verify.chain.md": "sdd",
|
|
56
|
+
"gentle-ai/support/sdd-status-contract.md": "sdd",
|
|
57
|
+
"gentle-ai/support/strict-tdd.md": "sdd",
|
|
58
|
+
"gentle-ai/support/strict-tdd-verify.md": "sdd",
|
|
59
|
+
} as const);
|
|
60
|
+
|
|
61
|
+
export type PackageAssetOwner = (typeof ASSET_OWNER_BY_KEY)[keyof typeof ASSET_OWNER_BY_KEY];
|
|
62
|
+
|
|
63
|
+
export function getPackageAssetOwner(ownershipKey: string): PackageAssetOwner | undefined {
|
|
64
|
+
return Object.hasOwn(ASSET_OWNER_BY_KEY, ownershipKey)
|
|
65
|
+
? ASSET_OWNER_BY_KEY[ownershipKey as keyof typeof ASSET_OWNER_BY_KEY]
|
|
66
|
+
: undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
21
69
|
function gentlePiAgentHome(): string {
|
|
22
70
|
return resolveGentlePiAgentHome();
|
|
23
71
|
}
|
|
@@ -37,6 +85,24 @@ export type SddChainedPrStrategy =
|
|
|
37
85
|
| "force-chained";
|
|
38
86
|
export type SddPreflightField = "executionMode" | "artifactStore" | "chainedPrStrategy" | "reviewBudgetLines";
|
|
39
87
|
export const SDD_PREFLIGHT_FIELDS = ["executionMode", "artifactStore", "chainedPrStrategy", "reviewBudgetLines"] as const;
|
|
88
|
+
// The parent dispatch and the process-spawn boundary share this exact shipped
|
|
89
|
+
// inventory so a newly packaged SDD actor cannot bypass preflight transport.
|
|
90
|
+
export const SHIPPED_SDD_AGENT_NAMES = Object.freeze([
|
|
91
|
+
"sdd-init",
|
|
92
|
+
"sdd-onboard",
|
|
93
|
+
"sdd-explore",
|
|
94
|
+
"sdd-research",
|
|
95
|
+
"sdd-proposal",
|
|
96
|
+
"sdd-spec",
|
|
97
|
+
"sdd-design",
|
|
98
|
+
"sdd-tasks",
|
|
99
|
+
"sdd-status",
|
|
100
|
+
"sdd-apply",
|
|
101
|
+
"sdd-verify",
|
|
102
|
+
"sdd-sync",
|
|
103
|
+
"sdd-archive",
|
|
104
|
+
"sdd-remediate",
|
|
105
|
+
]);
|
|
40
106
|
|
|
41
107
|
export interface SddPreflightPreferences {
|
|
42
108
|
executionMode: SddExecutionMode;
|
|
@@ -86,6 +152,20 @@ interface LegacyManagedAssetsManifest extends ManagedAssetsManifest {
|
|
|
86
152
|
packageVersion: string;
|
|
87
153
|
}
|
|
88
154
|
|
|
155
|
+
interface ManagedAssetsLockOwner {
|
|
156
|
+
schemaVersion: 1;
|
|
157
|
+
token: string;
|
|
158
|
+
pid: number;
|
|
159
|
+
createdAtMs: number;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** @internal The hold option exists only to make process-lock regression tests deterministic. */
|
|
163
|
+
interface PackageAssetInstallLockOptions {
|
|
164
|
+
timeoutMs?: number;
|
|
165
|
+
retryMs?: number;
|
|
166
|
+
holdLockMs?: number;
|
|
167
|
+
}
|
|
168
|
+
|
|
89
169
|
export const DEFAULT_SDD_PREFLIGHT: SddPreflightPreferences = Object.freeze({
|
|
90
170
|
executionMode: "auto",
|
|
91
171
|
artifactStore: "openspec",
|
|
@@ -198,6 +278,89 @@ function managedAssetHash(content: string): string {
|
|
|
198
278
|
return createHash("sha256").update(content).digest("hex");
|
|
199
279
|
}
|
|
200
280
|
|
|
281
|
+
function readManagedAssetsLockOwner(lockPath: string): ManagedAssetsLockOwner | undefined {
|
|
282
|
+
try {
|
|
283
|
+
if (!lstatSync(lockPath).isFile()) return undefined;
|
|
284
|
+
const parsed: unknown = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
285
|
+
if (!isRecord(parsed) || parsed.schemaVersion !== 1 || typeof parsed.token !== "string" || parsed.token.length === 0 || !Number.isInteger(parsed.pid) || parsed.pid <= 0 || typeof parsed.createdAtMs !== "number" || !Number.isFinite(parsed.createdAtMs)) return undefined;
|
|
286
|
+
return parsed as ManagedAssetsLockOwner;
|
|
287
|
+
} catch {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function waitForManagedAssetsLock(milliseconds: number): void {
|
|
293
|
+
if (milliseconds <= 0) return;
|
|
294
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function normalizedLockDuration(value: number | undefined, fallback: number): number {
|
|
298
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
299
|
+
? Math.floor(value)
|
|
300
|
+
: fallback;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function acquireManagedAssetsLock(
|
|
304
|
+
agentHome: string,
|
|
305
|
+
options: PackageAssetInstallLockOptions = {},
|
|
306
|
+
): { path: string; owner: ManagedAssetsLockOwner } {
|
|
307
|
+
const lockParent = join(agentHome, "gentle-ai");
|
|
308
|
+
const lockPath = join(lockParent, MANAGED_ASSETS_LOCK);
|
|
309
|
+
const timeoutMs = normalizedLockDuration(options.timeoutMs, MANAGED_ASSETS_LOCK_TIMEOUT_MS);
|
|
310
|
+
const retryMs = Math.max(1, normalizedLockDuration(options.retryMs, MANAGED_ASSETS_LOCK_RETRY_MS));
|
|
311
|
+
const deadline = Date.now() + timeoutMs;
|
|
312
|
+
mkdirSync(lockParent, { recursive: true });
|
|
313
|
+
for (;;) {
|
|
314
|
+
const owner: ManagedAssetsLockOwner = {
|
|
315
|
+
schemaVersion: 1,
|
|
316
|
+
token: randomUUID(),
|
|
317
|
+
pid: process.pid,
|
|
318
|
+
createdAtMs: Date.now(),
|
|
319
|
+
};
|
|
320
|
+
try {
|
|
321
|
+
writeFileSync(lockPath, JSON.stringify(owner), { encoding: "utf8", flag: "wx" });
|
|
322
|
+
return { path: lockPath, owner };
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (!isRecord(error) || error.code !== "EEXIST") throw error;
|
|
325
|
+
try {
|
|
326
|
+
if (!lstatSync(lockPath).isFile()) {
|
|
327
|
+
throw new Error(`Managed-assets lock path is unsafe and must be a regular file: ${lockPath}`);
|
|
328
|
+
}
|
|
329
|
+
} catch (inspectionError) {
|
|
330
|
+
if (isRecord(inspectionError) && inspectionError.code === "ENOENT") continue;
|
|
331
|
+
throw inspectionError;
|
|
332
|
+
}
|
|
333
|
+
if (Date.now() >= deadline) {
|
|
334
|
+
throw new Error(`Timed out acquiring managed-assets lock file ${lockPath}. Verify no installer is active before removing this exact lock file.`);
|
|
335
|
+
}
|
|
336
|
+
waitForManagedAssetsLock(retryMs);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function releaseManagedAssetsLock(lock: { path: string; owner: ManagedAssetsLockOwner }): void {
|
|
342
|
+
if (readManagedAssetsLockOwner(lock.path)?.token !== lock.owner.token) return;
|
|
343
|
+
try {
|
|
344
|
+
unlinkSync(lock.path);
|
|
345
|
+
} catch {
|
|
346
|
+
// An unreadable or replaced lock remains for an operator to inspect.
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function withManagedAssetsLock<T>(
|
|
351
|
+
agentHome: string,
|
|
352
|
+
action: () => T,
|
|
353
|
+
options: PackageAssetInstallLockOptions | undefined,
|
|
354
|
+
): T {
|
|
355
|
+
const lock = acquireManagedAssetsLock(agentHome, options);
|
|
356
|
+
try {
|
|
357
|
+
waitForManagedAssetsLock(normalizedLockDuration(options?.holdLockMs, 0));
|
|
358
|
+
return action();
|
|
359
|
+
} finally {
|
|
360
|
+
releaseManagedAssetsLock(lock);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
201
364
|
function readLegacyManagedAssets(
|
|
202
365
|
path: string,
|
|
203
366
|
version: string,
|
|
@@ -289,10 +452,36 @@ function migrateLegacyAssetContent(
|
|
|
289
452
|
return updateAgentFrontmatterRouting(packagedContent, routingLines);
|
|
290
453
|
}
|
|
291
454
|
|
|
455
|
+
function replaceManagedAssetFileAtomically(path: string, content: string): void {
|
|
456
|
+
const temporaryPath = join(dirname(path), `.${randomUUID()}.tmp`);
|
|
457
|
+
let mode: number | undefined;
|
|
458
|
+
try {
|
|
459
|
+
const stat = lstatSync(path);
|
|
460
|
+
if (stat.isFile()) mode = stat.mode & 0o777;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
writeFileSync(temporaryPath, content, {
|
|
466
|
+
encoding: "utf8",
|
|
467
|
+
flag: "wx",
|
|
468
|
+
...(mode === undefined ? {} : { mode }),
|
|
469
|
+
});
|
|
470
|
+
renameSync(temporaryPath, path);
|
|
471
|
+
} finally {
|
|
472
|
+
try {
|
|
473
|
+
unlinkSync(temporaryPath);
|
|
474
|
+
} catch {
|
|
475
|
+
// A renamed or otherwise inaccessible temporary file needs no further action.
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
292
480
|
export function updatePackageManagedSddAgentOwnership(
|
|
293
481
|
installedPath: string,
|
|
294
482
|
previousContent: string,
|
|
295
483
|
nextContent: string,
|
|
484
|
+
lockOptions?: PackageAssetInstallLockOptions,
|
|
296
485
|
): boolean {
|
|
297
486
|
const agentHome = gentlePiAgentHome();
|
|
298
487
|
const relativePath = relative(join(agentHome, "agents"), installedPath);
|
|
@@ -305,19 +494,51 @@ export function updatePackageManagedSddAgentOwnership(
|
|
|
305
494
|
return false;
|
|
306
495
|
}
|
|
307
496
|
const ownershipKey = `agents/${relativePath.split(sep).join("/")}`;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
497
|
+
return withManagedAssetsLock(agentHome, () => {
|
|
498
|
+
const manifestPath = join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST);
|
|
499
|
+
const manifest = readManagedAssetsManifest(manifestPath);
|
|
500
|
+
if (manifest.assets[ownershipKey] !== managedAssetHash(previousContent)) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
const installedContent = readFileSync(installedPath, "utf8");
|
|
504
|
+
// The next-content branch preserves the prior internal caller contract:
|
|
505
|
+
// callers that wrote the file before this function still receive a managed
|
|
506
|
+
// manifest update. New callers take the previous-content branch below so
|
|
507
|
+
// the file and manifest update share this lock.
|
|
508
|
+
if (installedContent !== nextContent && installedContent !== previousContent) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
if (installedContent === previousContent) {
|
|
513
|
+
replaceManagedAssetFileAtomically(installedPath, nextContent);
|
|
514
|
+
}
|
|
515
|
+
manifest.assets[ownershipKey] = managedAssetHash(nextContent);
|
|
516
|
+
replaceManagedAssetFileAtomically(
|
|
517
|
+
manifestPath,
|
|
518
|
+
JSON.stringify(manifest, null, 2),
|
|
519
|
+
);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
try {
|
|
522
|
+
replaceManagedAssetFileAtomically(installedPath, previousContent);
|
|
523
|
+
} catch (rollbackError) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`Managed routing update failed and could not restore ${installedPath}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
|
|
526
|
+
{ cause: error },
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
317
531
|
return true;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
532
|
+
}, lockOptions);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export function hasPackageAssetOwnerInstallation(owner: PackageAssetOwner): boolean {
|
|
536
|
+
const agentHome = gentlePiAgentHome();
|
|
537
|
+
const manifest = readManagedAssetsManifest(join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST));
|
|
538
|
+
return Object.keys(manifest.assets).some((key) => getPackageAssetOwner(key) === owner) ||
|
|
539
|
+
Object.entries(ASSET_OWNER_BY_KEY).some(([key, candidate]) =>
|
|
540
|
+
candidate === owner && existsSync(join(agentHome, key)),
|
|
541
|
+
);
|
|
321
542
|
}
|
|
322
543
|
|
|
323
544
|
export function isPackageManagedSddAsset(
|
|
@@ -411,8 +632,12 @@ function copyDirectoryFiles(
|
|
|
411
632
|
force: boolean,
|
|
412
633
|
manifest: ManagedAssetsManifest,
|
|
413
634
|
legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined,
|
|
635
|
+
selected?: ReadonlySet<string>,
|
|
414
636
|
): { copied: number; skipped: number } {
|
|
415
637
|
if (!existsSync(sourceDir)) return { copied: 0, skipped: 0 };
|
|
638
|
+
if (selected && ![...selected].some(key => key.startsWith(`${ownershipPrefix}/`))) {
|
|
639
|
+
return { copied: 0, skipped: 0 };
|
|
640
|
+
}
|
|
416
641
|
mkdirSync(targetDir, { recursive: true });
|
|
417
642
|
let copied = 0;
|
|
418
643
|
let skipped = 0;
|
|
@@ -428,12 +653,13 @@ function copyDirectoryFiles(
|
|
|
428
653
|
force,
|
|
429
654
|
manifest,
|
|
430
655
|
legacyAssetHashes,
|
|
656
|
+
selected,
|
|
431
657
|
);
|
|
432
658
|
copied += child.copied;
|
|
433
659
|
skipped += child.skipped;
|
|
434
660
|
continue;
|
|
435
661
|
}
|
|
436
|
-
if (!entry.isFile()) continue;
|
|
662
|
+
if (!entry.isFile() || (selected && !selected.has(ownershipKey))) continue;
|
|
437
663
|
const source = readFileSync(sourcePath, "utf8");
|
|
438
664
|
let nextSource = source;
|
|
439
665
|
if (existsSync(targetPath)) {
|
|
@@ -476,6 +702,9 @@ function copyDirectoryFiles(
|
|
|
476
702
|
delete manifest.assets[ownershipKey];
|
|
477
703
|
skipped += 1;
|
|
478
704
|
continue;
|
|
705
|
+
} else if (ownershipKey === "agents/sdd-research.md" && installedContent !== undefined) {
|
|
706
|
+
// Keep routing adopted by the legacy migration on subsequent refreshes.
|
|
707
|
+
nextSource = migrateLegacyAssetContent(ownershipKey, installedContent, source);
|
|
479
708
|
}
|
|
480
709
|
}
|
|
481
710
|
writeFileSync(targetPath, nextSource);
|
|
@@ -503,9 +732,11 @@ const RETIRED_MANAGED_ASSETS = Object.freeze([
|
|
|
503
732
|
function removeRetiredManagedAssets(
|
|
504
733
|
agentHome: string,
|
|
505
734
|
manifest: ManagedAssetsManifest,
|
|
735
|
+
selected?: ReadonlySet<string>,
|
|
506
736
|
): void {
|
|
507
737
|
let legacyHashes: Record<string, readonly string[]> | undefined;
|
|
508
738
|
for (const ownershipKey of RETIRED_MANAGED_ASSETS) {
|
|
739
|
+
if (selected && !selected.has(ownershipKey)) continue;
|
|
509
740
|
const installedPath = join(agentHome, ...ownershipKey.split("/"));
|
|
510
741
|
if (!existsSync(installedPath)) {
|
|
511
742
|
delete manifest.assets[ownershipKey];
|
|
@@ -536,52 +767,80 @@ function removeRetiredManagedAssets(
|
|
|
536
767
|
}
|
|
537
768
|
}
|
|
538
769
|
|
|
770
|
+
// Legacy all-owner entry point retained for compatibility.
|
|
771
|
+
// Owner-specific commands and SDD preflight use installPackageAssets directly.
|
|
539
772
|
export function installSddAssets(
|
|
773
|
+
cwd: string,
|
|
774
|
+
force: boolean,
|
|
775
|
+
): { agents: number; chains: number; support: number; skipped: number } {
|
|
776
|
+
return installPackageAssets(cwd, force);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export function installPackageAssets(
|
|
540
780
|
_cwd: string,
|
|
541
781
|
force: boolean,
|
|
782
|
+
owners?: readonly PackageAssetOwner[],
|
|
783
|
+
lockOptions?: PackageAssetInstallLockOptions,
|
|
542
784
|
): { agents: number; chains: number; support: number; skipped: number } {
|
|
543
785
|
const agentHome = gentlePiAgentHome();
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
786
|
+
return withManagedAssetsLock(agentHome, () => {
|
|
787
|
+
const selected = owners === undefined ? undefined : new Set(
|
|
788
|
+
Object.entries(ASSET_OWNER_BY_KEY).filter(([, owner]) => owners.includes(owner)).map(([key]) => key),
|
|
789
|
+
);
|
|
790
|
+
const manifestPath = join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST);
|
|
791
|
+
let legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined;
|
|
792
|
+
if (force) {
|
|
793
|
+
let cachedLegacyAssetHashes: Record<string, readonly string[]> | undefined;
|
|
794
|
+
legacyAssetHashes = () =>
|
|
795
|
+
(cachedLegacyAssetHashes ??= readLegacyManagedAssetHashes());
|
|
796
|
+
}
|
|
797
|
+
const manifest = readManagedAssetsManifest(manifestPath);
|
|
798
|
+
removeRetiredManagedAssets(agentHome, manifest, selected);
|
|
799
|
+
const agents = copyDirectoryFiles(
|
|
800
|
+
join(ASSETS_DIR, "agents"),
|
|
801
|
+
join(agentHome, "agents"),
|
|
802
|
+
"agents",
|
|
803
|
+
force,
|
|
804
|
+
manifest,
|
|
805
|
+
legacyAssetHashes,
|
|
806
|
+
selected,
|
|
807
|
+
);
|
|
808
|
+
const chains = copyDirectoryFiles(
|
|
809
|
+
join(ASSETS_DIR, "chains"),
|
|
810
|
+
join(agentHome, "chains"),
|
|
811
|
+
"chains",
|
|
812
|
+
force,
|
|
813
|
+
manifest,
|
|
814
|
+
legacyAssetHashes,
|
|
815
|
+
selected,
|
|
816
|
+
);
|
|
817
|
+
const support = copyDirectoryFiles(
|
|
818
|
+
join(ASSETS_DIR, "support"),
|
|
819
|
+
join(agentHome, "gentle-ai", "support"),
|
|
820
|
+
"gentle-ai/support",
|
|
821
|
+
force,
|
|
822
|
+
manifest,
|
|
823
|
+
legacyAssetHashes,
|
|
824
|
+
selected,
|
|
825
|
+
);
|
|
826
|
+
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
827
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
828
|
+
return {
|
|
829
|
+
agents: agents.copied,
|
|
830
|
+
chains: chains.copied,
|
|
831
|
+
support: support.copied,
|
|
832
|
+
skipped: agents.skipped + chains.skipped + support.skipped,
|
|
833
|
+
};
|
|
834
|
+
}, lockOptions);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function hasAffirmativeSddIntent(text: string): boolean {
|
|
838
|
+
// Natural-language routing must not depend on a closed list of complete
|
|
839
|
+
// phrases. An SDD mention becomes an invocation only with an imperative,
|
|
840
|
+
// request, or first-person intent marker; a neutral statement such as
|
|
841
|
+
// "I use SDD sometimes" remains ordinary conversation.
|
|
842
|
+
if (!/\bsdd\b/i.test(text)) return false;
|
|
843
|
+
return /(?:\b(?:please|por\s+favor)\b|\b(?:want|need|would\s+like|let'?s|quiero|queremos|necesito|quisiera|me\s+gustar[ií]a|vamos|vayamos|hagamos|usemos)\b|^(?:use|run|start|build|create|implement|handle|make|usa|usá|corre|corré|arranca|arrancá|inicia|iniciá|empeza|empezá|hacelo|hazlo|hacerlo)\b)/i.test(text);
|
|
585
844
|
}
|
|
586
845
|
|
|
587
846
|
export function isSddPreflightTrigger(text: string): boolean {
|
|
@@ -589,19 +848,12 @@ export function isSddPreflightTrigger(text: string): boolean {
|
|
|
589
848
|
if (/^\/(?:gentle-)?sdd(?:[-:][^\s]*)?(?:\s|$)/i.test(trimmed)) return true;
|
|
590
849
|
if (/[??]\s*$/.test(trimmed)) return false;
|
|
591
850
|
if (
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
)
|
|
851
|
+
/(?:\b(?:don't|do\s+not|never)\b|\bnot\s+(?:want|need|plan(?:ning)?|intend|use|using)\b)[^.!?\n]{0,80}\bsdd\b/i.test(trimmed) ||
|
|
852
|
+
/\b(?:sin\s+usar|no\s+(?:quiero|queremos|necesito|necesitamos|quisiera|quisiéramos|vamos\s+a|pienso|planeo|usar))\b[^.!?\n]{0,80}\bsdd\b/i.test(trimmed)
|
|
595
853
|
) {
|
|
596
854
|
return false;
|
|
597
855
|
}
|
|
598
|
-
return
|
|
599
|
-
/^(?:please\s+)?(?:use|run|start)\s+(?:the\s+|an?\s+)?sdd(?:\s+(?:flow|process|workflow|plan))?\b/i,
|
|
600
|
-
/^(?:please\s+)?(?:do|handle|implement)\b.+\b(?:with|using)\s+(?:the\s+|an?\s+)?sdd\b/i,
|
|
601
|
-
/^(?:por\s+favor[\s,]+)?(?:vamos|vayamos)\s+con\s+(?:el\s+)?sdd\b/i,
|
|
602
|
-
/^(?:por\s+favor[\s,]+)?(?:usa|usá|usemos|corre|corré|arranca|arrancá|inicia|iniciá|empeza|empezá)\s+(?:el\s+)?sdd\b/i,
|
|
603
|
-
/^(?:por\s+favor[\s,]+)?(?:hacelo|hazlo|hacerlo)\s+(?:con|usando)\s+(?:el\s+)?sdd\b/i,
|
|
604
|
-
].some((pattern) => pattern.test(trimmed));
|
|
856
|
+
return hasAffirmativeSddIntent(trimmed);
|
|
605
857
|
}
|
|
606
858
|
|
|
607
859
|
export function sddPreflightSessionKey(ctx: ExtensionContext): string {
|
|
@@ -646,13 +898,23 @@ export async function collectSddPreflightPreferences(
|
|
|
646
898
|
engramAvailable: boolean,
|
|
647
899
|
options: SddPreflightResolutionOptions = {},
|
|
648
900
|
): Promise<SddPreflightPreferences> {
|
|
649
|
-
|
|
901
|
+
// Disk preferences suggest values; they never carry current-session consent.
|
|
650
902
|
const allowExceptionOk = options.acceptSizeException === true;
|
|
651
903
|
const persisted = normalizedSelections(options.persisted, engramAvailable, allowExceptionOk);
|
|
652
904
|
const resolved: Partial<Record<SddPreflightField, unknown>> = { ...persisted };
|
|
653
|
-
let prompted =
|
|
905
|
+
let prompted = false;
|
|
654
906
|
let sizeExceptionAccepted = allowExceptionOk && persisted.chainedPrStrategy === "exception-ok";
|
|
655
907
|
const promptFields = new Set(options.promptFields ?? []);
|
|
908
|
+
// RPC is headless even though Pi exposes functional dialog methods there.
|
|
909
|
+
if (ctx.hasUI && ctx.mode !== "rpc" && promptFields.size === 0) {
|
|
910
|
+
const suggestions = { ...DEFAULT_SDD_PREFLIGHT, ...persisted };
|
|
911
|
+
ctx.ui.notify(`SDD session suggestions: mode=${suggestions.executionMode}; artifacts=${suggestions.artifactStore}; delivery=${suggestions.chainedPrStrategy}; budget=${suggestions.reviewBudgetLines}. Saved preferences are not session consent.`, "info");
|
|
912
|
+
if (typeof ctx.ui.select !== "function") throw new Error("SDD preflight confirmation UI unavailable; no session consent recorded.");
|
|
913
|
+
const answer = await ctx.ui.select("Confirm SDD session preflight", ["Confirm", "Change choices"]);
|
|
914
|
+
if (answer === "Confirm") prompted = true;
|
|
915
|
+
else if (answer === "Change choices") for (const field of SDD_PREFLIGHT_FIELDS) promptFields.add(field);
|
|
916
|
+
else throw new Error("SDD preflight cancelled; no session consent recorded.");
|
|
917
|
+
}
|
|
656
918
|
|
|
657
919
|
const usePromptedValue = (
|
|
658
920
|
field: SddPreflightField,
|
|
@@ -671,15 +933,23 @@ export async function collectSddPreflightPreferences(
|
|
|
671
933
|
read: () => Promise<unknown>,
|
|
672
934
|
enabled = true,
|
|
673
935
|
): Promise<void> => {
|
|
674
|
-
if (ctx.hasUI && enabled && promptFields.has(field)) {
|
|
675
|
-
|
|
936
|
+
if (ctx.hasUI && ctx.mode !== "rpc" && enabled && promptFields.has(field)) {
|
|
937
|
+
const value = await read();
|
|
938
|
+
if (normalizedSelections({ [field]: value }, engramAvailable, allowExceptionOk)[field] === undefined) {
|
|
939
|
+
throw new Error("SDD preflight cancelled or invalid; no session consent recorded.");
|
|
940
|
+
}
|
|
941
|
+
usePromptedValue(field, value);
|
|
676
942
|
}
|
|
677
943
|
};
|
|
678
944
|
const artifactOptions = engramAvailable ? ["openspec", "engram", "hybrid"] : ["openspec"];
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
945
|
+
const suggestedFirst = (field: SddPreflightField, values: string[]): string[] => {
|
|
946
|
+
const suggested = String(resolved[field] ?? DEFAULT_SDD_PREFLIGHT[field]);
|
|
947
|
+
return values.includes(suggested) ? [suggested, ...values.filter(value => value !== suggested)] : values;
|
|
948
|
+
};
|
|
949
|
+
await promptField("executionMode", () => ctx.ui.select("SDD execution mode", suggestedFirst("executionMode", ["interactive", "auto"])));
|
|
950
|
+
await promptField("artifactStore", () => ctx.ui.select("SDD artifact store", suggestedFirst("artifactStore", artifactOptions)), artifactOptions.length > 1);
|
|
951
|
+
await promptField("chainedPrStrategy", () => ctx.ui.select("SDD delivery strategy", suggestedFirst("chainedPrStrategy", ["ask-on-risk", "auto-chain", "single-pr"])));
|
|
952
|
+
await promptField("reviewBudgetLines", () => ctx.ui.input("SDD review budget lines", String(resolved.reviewBudgetLines ?? DEFAULT_SDD_PREFLIGHT.reviewBudgetLines)));
|
|
683
953
|
|
|
684
954
|
const resolvedValue = <T>(field: SddPreflightField, fallback: T): T =>
|
|
685
955
|
(resolved[field] as T | undefined) ?? fallback;
|
|
@@ -694,6 +964,16 @@ export async function collectSddPreflightPreferences(
|
|
|
694
964
|
};
|
|
695
965
|
}
|
|
696
966
|
|
|
967
|
+
export function isParentConfirmedSddPreflightContext(context: unknown): context is string {
|
|
968
|
+
if (typeof context !== "string") return false;
|
|
969
|
+
return /^## SDD Session Preflight\n(?:These SDD preferences are explicit current-session choices\. Reuse them unless the user explicitly changes them\.|These SDD preferences are canonical defaults or persisted choices\. Treat them as authoritative; do not revisit dependent decisions unless a genuine human-control gate is reached\.)\n- Execution mode: (?:interactive|auto)\n- Artifact store: (?:openspec|engram|hybrid|none)(?: \(Engram unavailable in this session\))?\n- Delivery strategy: (?:ask-on-risk|auto-chain|single-pr|exception-ok)\n- Delivery strategy domain: `ask-on-risk` \| `auto-chain` \| `single-pr` \| `exception-ok`\n- Review budget: [1-9]\d* changed lines \(400 is the canonical threshold unless explicitly changed\)\n- Chain strategy: deferred until chaining is selected\./.test(context);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
export function extractParentConfirmedSddPreflightContext(context: unknown): string | undefined {
|
|
973
|
+
if (!isParentConfirmedSddPreflightContext(context)) return undefined;
|
|
974
|
+
return context.split("\n\n", 1)[0];
|
|
975
|
+
}
|
|
976
|
+
|
|
697
977
|
export function renderSddPreflightPrompt(prefs: SddPreflightPreferences): string {
|
|
698
978
|
const deliveryStrategy = normalizeSddChainedPrStrategy(
|
|
699
979
|
prefs.chainedPrStrategy,
|
|
@@ -733,6 +1013,12 @@ export async function ensureSddPreflight(
|
|
|
733
1013
|
callbacks: SddPreflightCallbacks,
|
|
734
1014
|
resolutionOptions: SddPreflightResolutionOptions = {},
|
|
735
1015
|
): Promise<SddPreflightPreferences> {
|
|
1016
|
+
// `collectSddPreflightPreferences` remains a pure suggestion resolver for
|
|
1017
|
+
// callers that need to render options. Persisting or promoting those options
|
|
1018
|
+
// is parent-only: an RPC child must consume the transported rendered block.
|
|
1019
|
+
if (ctx.mode === "rpc") {
|
|
1020
|
+
throw new Error("SDD preflight must be resolved by the parent; an RPC child cannot originate or persist defaults.");
|
|
1021
|
+
}
|
|
736
1022
|
const sessionKey = sddPreflightSessionKey(ctx);
|
|
737
1023
|
const existing = sddPreflightBySession.get(sessionKey);
|
|
738
1024
|
if (existing && !(resolutionOptions.promptFields?.length ?? 0)) return existing;
|
|
@@ -747,7 +1033,7 @@ export async function ensureSddPreflight(
|
|
|
747
1033
|
});
|
|
748
1034
|
const result =
|
|
749
1035
|
(await callbacks.installAssets?.(ctx.cwd)) ??
|
|
750
|
-
|
|
1036
|
+
installPackageAssets(ctx.cwd, false, ["sdd"]);
|
|
751
1037
|
const modelResult = (await callbacks.applyModelConfig?.(ctx.cwd)) ?? {
|
|
752
1038
|
updated: 0,
|
|
753
1039
|
skipped: 0,
|
|
@@ -788,11 +1074,6 @@ export function getSddPreflightPreferences(
|
|
|
788
1074
|
const sessionKey = sddPreflightSessionKey(ctx);
|
|
789
1075
|
const cached = sddPreflightBySession.get(sessionKey);
|
|
790
1076
|
if (cached) return cached;
|
|
791
|
-
//
|
|
792
|
-
const persisted = readSddPreflightFromDisk(ctx.cwd);
|
|
793
|
-
if (persisted) {
|
|
794
|
-
sddPreflightBySession.set(sessionKey, persisted);
|
|
795
|
-
return persisted;
|
|
796
|
-
}
|
|
1077
|
+
// Only ensureSddPreflight may promote disk suggestions to resolved session choices.
|
|
797
1078
|
return undefined;
|
|
798
1079
|
}
|