gentle-pi 4.2.0 → 4.4.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 +340 -138
- package/assets/agents/gentle-ai-explore.md +1 -0
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -1
- package/assets/design-canvas/canvas.css +648 -0
- package/assets/design-canvas/canvas.js +1497 -0
- package/assets/design-canvas/index.html +88 -0
- package/assets/design-canvas/inspector.js +290 -0
- package/assets/orchestrator-delegation-reference.md +163 -0
- package/assets/orchestrator-delegation.md +25 -176
- package/assets/orchestrator-tracking.md +1 -1
- package/assets/orchestrator-verification.md +14 -8
- package/assets/orchestrator-writer.md +5 -1
- package/assets/orchestrator.md +11 -11
- package/bin/engram-guard +1 -1
- package/bin/gentle-shell.mjs +237 -139
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
- package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/assets/diagrams/agent-orchestration.svg +54 -15
- package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
- package/docs/assets/diagrams/measured-routing.svg +49 -0
- package/docs/assets/diagrams/native-review.svg +52 -12
- package/docs/assets/diagrams/odd-workflow.svg +128 -66
- package/docs/assets/diagrams/permission-modes.svg +51 -0
- package/docs/assets/features/audio-notifications.png +0 -0
- package/docs/assets/features/command-palette.png +0 -0
- package/docs/bundled-install.md +65 -30
- package/docs/delegated-verification.md +16 -8
- package/docs/gentle-agents-activity.md +16 -6
- package/docs/gentle-music.md +61 -0
- package/docs/gentle-shell-uninstall.md +8 -1
- package/docs/gentle-shell.md +80 -9
- package/docs/install-wizard.md +37 -6
- package/docs/permission-modes.md +8 -3
- package/docs/readme-reference.md +120 -28
- package/docs/session-profile-format.md +26 -0
- package/docs/voice.md +259 -0
- package/docs/yolo-mode.md +38 -10
- package/extensions/ask-user-choice.ts +4 -1
- package/extensions/codegraph-tools.ts +68 -6
- package/extensions/gentle-agents.ts +270 -48
- package/extensions/gentle-ai.ts +490 -113
- package/extensions/gentle-design.ts +158 -0
- package/extensions/gentle-herdr-activity.ts +2 -3
- package/extensions/gentle-music.ts +289 -0
- package/extensions/gentle-shell.ts +359 -85
- package/extensions/gentle-todo.ts +43 -9
- package/extensions/gentle-voice.ts +11 -0
- package/extensions/quiet-tools.ts +1 -1
- package/extensions/session-scratch.ts +384 -0
- package/extensions/skill-registry.ts +63 -8
- package/extensions/startup-banner.ts +2 -3
- package/lib/agent-home.ts +8 -3
- package/lib/agent-profiles.ts +26 -0
- package/lib/agents-config.ts +35 -0
- package/lib/agents-protocol.ts +60 -1
- package/lib/agents-runner.ts +107 -9
- package/lib/agents-session-transport.ts +101 -6
- package/lib/agents-view.ts +3 -1
- package/lib/agents-widget.ts +14 -1
- package/lib/auto-permission-memory.ts +48 -18
- package/lib/auto-permission-runtime.ts +21 -6
- package/lib/bounded-writer-admission.ts +51 -0
- package/lib/clipboard-image.ts +147 -0
- package/lib/command-palette-catalog.ts +2 -4
- package/lib/commit-slice-signal.ts +100 -0
- package/lib/context-backstop.ts +120 -0
- package/lib/delivered-test-lint.ts +80 -0
- package/lib/design-canvas-server.ts +947 -0
- package/lib/design-comments.ts +437 -0
- package/lib/design-handoffs.ts +313 -0
- package/lib/design-open-browser.ts +58 -0
- package/lib/design-sources.ts +120 -0
- package/lib/destructive-command-guard.ts +120 -9
- package/lib/external-editor.ts +31 -0
- package/lib/gauge-theme.ts +146 -0
- package/lib/gentle-ai-binary.ts +28 -7
- package/lib/gentle-design.ts +513 -0
- package/lib/gentle-shell-bundled.ts +146 -36
- package/lib/gentle-shell-import-pi.ts +233 -0
- package/lib/gentle-shell-launcher.ts +154 -15
- package/lib/gentle-shell-startup-update.ts +61 -9
- package/lib/gentle-shell-uninstall.ts +51 -2
- package/lib/herdr-activity.ts +24 -0
- package/lib/image-attachments.ts +150 -0
- package/lib/inprocess-reviewer.ts +33 -2
- package/lib/nan-provider.ts +3 -0
- package/lib/native-review-cli.ts +20 -5
- package/lib/notification-audio-native.ts +149 -13
- package/lib/notification-audio.ts +4 -0
- package/lib/notification-pulse-worker.ts +82 -18
- package/lib/notification-service.ts +5 -1
- package/lib/orchestrator-discovery.ts +5 -1
- package/lib/orchestrator-presence.ts +87 -8
- package/lib/orchestrator-scope.ts +31 -3
- package/lib/orchestrator-state.ts +11 -3
- package/lib/permission-mode-policy.ts +2 -2
- package/lib/provider-contract-bundle.ts +99 -53
- package/lib/review-candidate-view-owner.ts +29 -19
- package/lib/review-candidate-view.ts +36 -2
- package/lib/review-host-relay.ts +11 -2
- package/lib/review-reminder-receipt.ts +24 -1
- package/lib/review-risk-assessment.ts +43 -38
- package/lib/review-session-standing-permission.ts +3 -1
- package/lib/session-profile-append-controller.ts +713 -0
- package/lib/session-profile-disk-reader.ts +182 -0
- package/lib/session-profile-freeze.ts +30 -1
- package/lib/session-profile-persistence.ts +15 -7
- package/lib/session-scratch.ts +523 -0
- package/lib/session-worktree-registry.ts +7 -0
- package/lib/shell-bar.ts +29 -28
- package/lib/shell-changes-view.ts +80 -13
- package/lib/shell-changes.ts +81 -5
- package/lib/shell-gauge.ts +49 -6
- package/lib/shell-prompt.ts +4 -4
- package/lib/shell-sidebar-layout.ts +26 -6
- package/lib/shell-todo.ts +24 -5
- package/lib/shell-usage-view.ts +5 -1
- package/lib/shell-usage.ts +7 -7
- package/lib/verifier-admission.ts +30 -0
- package/lib/vim-editor-adapter.ts +11 -8
- package/lib/voice-artifacts.ts +188 -0
- package/lib/voice-command.ts +576 -0
- package/lib/voice-install.ts +455 -0
- package/lib/voice-platform.ts +99 -0
- package/lib/voice-push-to-talk.ts +262 -0
- package/lib/voice-session.ts +460 -0
- package/lib/voice-settings.ts +50 -0
- package/lib/voice-ui.ts +158 -0
- package/lib/yolo-session-policy.ts +58 -22
- package/lib/yolo-temp-scratch.ts +69 -0
- package/package.json +1 -2
- package/runtime/gentle-ai-binary.mjs +28 -7
- package/runtime/gentle-shell-bundled.mjs +145 -35
- package/runtime/gentle-shell-import-pi.mjs +234 -0
- package/runtime/gentle-shell-launcher.mjs +152 -13
- package/runtime/gentle-shell-startup-update.mjs +61 -9
- package/runtime/gentle-shell-uninstall.mjs +51 -2
- package/runtime/native-review-cli.mjs +19 -4
- package/runtime/review-risk-assessment.mjs +43 -38
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/bundled-engram-migration.mjs +119 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/installer-downloads.mjs +7 -7
- package/scripts/installer-preflight.mjs +5 -3
- package/scripts/installer-server.mjs +14 -4
- package/scripts/installer-windows.mjs +37 -7
- package/scripts/main-channel.mjs +94 -19
- package/scripts/pi-update.mjs +34 -6
- package/scripts/run-test-suite.mjs +5 -0
- package/scripts/verify-package-files.mjs +43 -13
- package/scripts/voice-worker.cjs +476 -0
- package/skills/design/SKILL.md +110 -0
- package/skills/gentle-ai/SKILL.md +3 -3
- package/skills/work-unit-commits/SKILL.md +5 -3
- package/tests/agent-profiles.test.ts +24 -0
- package/tests/agent-risk-escalation.test.ts +37 -10
- package/tests/agents-config.test.ts +61 -0
- package/tests/agents-fake-child.ts +8 -1
- package/tests/agents-protocol.test.ts +32 -0
- package/tests/agents-runner.test.ts +485 -7
- package/tests/agents-session-transport.test.ts +53 -3
- package/tests/ask-user-choice.test.ts +30 -0
- package/tests/auto-mode.test.ts +17 -0
- package/tests/auto-permission-memory-diagnostics.test.ts +56 -0
- package/tests/auto-permission-runtime.test.ts +85 -1
- package/tests/autonomous-guard.test.ts +142 -0
- package/tests/bounded-writer-admission.test.ts +11 -3
- package/tests/bundled-install.test.ts +3 -3
- package/tests/card-content-policy.test.ts +19 -9
- package/tests/child-safety.test.ts +39 -0
- package/tests/clipboard-image.test.ts +114 -0
- package/tests/codegraph-tools.test.ts +123 -2
- package/tests/command-palette.test.ts +15 -5
- package/tests/commit-slice-signal.test.ts +162 -0
- package/tests/context-backstop.test.ts +225 -0
- package/tests/delegate-for-reason-contract.test.ts +92 -24
- package/tests/delivered-test-lint.test.ts +105 -0
- package/tests/design-canvas-assets.test.ts +710 -0
- package/tests/design-canvas-server.test.ts +1096 -0
- package/tests/design-comments.test.ts +201 -0
- package/tests/design-handoffs.test.ts +282 -0
- package/tests/design-open-browser.test.ts +70 -0
- package/tests/design-sources.test.ts +132 -0
- package/tests/destructive-command-guard.test.ts +85 -1
- package/tests/engram-safety.test.ts +132 -57
- package/tests/external-editor.test.ts +84 -0
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
- package/tests/gauge-theme.test.ts +120 -0
- package/tests/gentle-agents.test.ts +645 -21
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-child-guards.test.ts +61 -0
- package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
- package/tests/gentle-ai-installer.test.ts +51 -51
- package/tests/gentle-ai.test.ts +292 -13
- package/tests/gentle-design.test.ts +1313 -0
- package/tests/gentle-music.test.ts +335 -0
- package/tests/gentle-shell-bin.test.ts +163 -0
- package/tests/gentle-shell-bundled.test.ts +392 -176
- package/tests/gentle-shell-import-pi.test.ts +262 -0
- package/tests/gentle-shell-launcher.test.ts +107 -2
- package/tests/gentle-shell-startup-update.test.ts +77 -1
- package/tests/gentle-shell-uninstall.test.ts +89 -0
- package/tests/gentle-shell.test.ts +225 -17
- package/tests/gentle-todo.test.ts +190 -0
- package/tests/headless-delivery-question.test.ts +58 -0
- package/tests/herdr-activity.test.ts +34 -1
- package/tests/image-attachments.test.ts +119 -0
- package/tests/image-paste-chips.test.ts +236 -0
- package/tests/inprocess-reviewer.test.ts +55 -2
- package/tests/installer-bundles.test.ts +12 -6
- package/tests/installer-posix-bootstrap.test.ts +8 -8
- package/tests/installer-preflight.test.ts +16 -0
- package/tests/installer-server.test.ts +10 -2
- package/tests/installer-windows-bootstrap.test.ts +94 -7
- package/tests/installer-windows-diagnostics.test.ts +111 -0
- package/tests/main-channel-upgrade.test.ts +232 -2
- package/tests/main-channel.test.ts +140 -21
- package/tests/nan-provider.test.ts +36 -0
- package/tests/native-review-capability-contract.test.ts +17 -4
- package/tests/native-review-parity.test.ts +23 -2
- package/tests/no-self-pause-contract.test.ts +5 -2
- package/tests/notification-audio-native.test.ts +128 -9
- package/tests/notification-customize.test.ts +17 -5
- package/tests/odd-integration.test.ts +5 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +128 -162
- package/tests/odd-routing-contract.test.ts +115 -30
- package/tests/orchestrator-budget.test.ts +22 -8
- package/tests/orchestrator-discovery.test.ts +29 -0
- package/tests/orchestrator-presence.test.ts +100 -2
- package/tests/orchestrator-rdd-ownership.test.ts +3 -1
- package/tests/orchestrator-scope.test.ts +80 -0
- package/tests/orchestrator-state.test.ts +29 -0
- package/tests/orchestrator-status-publication-contract.test.ts +12 -8
- package/tests/package-files.test.ts +34 -0
- package/tests/package-manifest.test.ts +24 -20
- package/tests/permission-mode-policy.test.ts +10 -1
- package/tests/pi-update-launcher.test.ts +21 -2
- package/tests/pi-update.test.ts +99 -4
- package/tests/primary-session-prompt.test.ts +101 -0
- package/tests/provider-contract-bundle.test.ts +118 -0
- package/tests/provider-contract-mirror.test.ts +24 -0
- package/tests/quiet-tool-rendering.test.ts +23 -0
- package/tests/rdd-aware-verification-contract.test.ts +29 -11
- package/tests/rdd-status-line.test.ts +79 -0
- package/tests/review-agent-end-preflight.test.ts +6 -0
- package/tests/review-candidate-view.test.ts +49 -0
- package/tests/review-contract-prompt.test.ts +5 -2
- package/tests/review-controller-native-routing.test.ts +14 -1
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +147 -3
- package/tests/review-host-relay.test.ts +9 -2
- package/tests/review-json-arguments.test.ts +20 -3
- package/tests/review-loadout-rdd-off-sdk.test.ts +258 -0
- package/tests/review-owner-identity.test.ts +132 -0
- package/tests/review-relay-transport-agent.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +31 -1
- package/tests/review-risk-assessment.test.ts +163 -76
- package/tests/review-session-standing-permission-controller.test.ts +14 -1
- package/tests/review-session-standing-permission.test.ts +27 -1
- package/tests/run-test-suite.test.ts +15 -0
- package/tests/runtime-harness.mjs +16 -6
- package/tests/session-changes-shell.test.ts +1 -1
- package/tests/session-changes.test.ts +2 -1
- package/tests/session-profile-append-controller-fork.test.ts +458 -0
- package/tests/session-profile-append-controller-pi.test.ts +290 -0
- package/tests/session-profile-append-controller.test.ts +731 -0
- package/tests/session-profile-disk-reader-pi.test.ts +183 -0
- package/tests/session-profile-disk-reader.test.ts +311 -0
- package/tests/session-profile-freeze.test.ts +23 -0
- package/tests/session-scratch.test.ts +1011 -0
- package/tests/session-subject-routing-contract.test.ts +2 -1
- package/tests/session-worktree-registry.test.ts +13 -1
- package/tests/shell-bar.test.ts +21 -2
- package/tests/shell-changes-view.test.ts +137 -1
- package/tests/shell-changes.test.ts +148 -1
- package/tests/shell-gauge.test.ts +42 -0
- package/tests/shell-prompt.test.ts +3 -3
- package/tests/shell-render-cache.test.ts +134 -1
- package/tests/shell-sidebar-layout.test.ts +50 -0
- package/tests/short-check-routing-contract.test.ts +1 -1
- package/tests/skill-collision-prefixes.test.ts +3 -1
- package/tests/skill-registry-watcher.test.ts +17 -5
- package/tests/skill-registry.test.ts +47 -3
- package/tests/small-task-inline-contract.test.ts +37 -0
- package/tests/standalone-config-home.test.ts +324 -0
- package/tests/startup-banner.test.ts +5 -4
- package/tests/startup-home-repo.test.ts +34 -0
- package/tests/support/orchestrator-modules.ts +1 -0
- package/tests/task-size-routing-contract.test.ts +70 -10
- package/tests/test-scratch-contract.test.ts +102 -0
- package/tests/test-strength-contract.test.ts +36 -1
- package/tests/tool-batching-contract.test.ts +64 -0
- package/tests/verification-proportionality.test.ts +46 -0
- package/tests/verifier-admission.test.ts +154 -0
- package/tests/verify-agent-contract.test.ts +8 -8
- package/tests/voice-command.test.ts +773 -0
- package/tests/voice-install.test.ts +323 -0
- package/tests/voice-platform.test.ts +176 -0
- package/tests/voice-push-to-talk.test.ts +444 -0
- package/tests/voice-session.test.ts +434 -0
- package/tests/voice-settings.test.ts +49 -0
- package/tests/voice-ui.test.ts +97 -0
- package/tests/voice-worker.test.ts +445 -0
- package/tests/weak-model-economy-prompt.test.ts +32 -0
- package/tests/windows-hidden-processes.test.ts +30 -2
- package/tests/writer-edit-surface-scope.test.ts +3 -0
- package/tests/writer-feature-document-admission.test.ts +156 -0
- package/tests/yolo-customize.test.ts +5 -1
- package/tests/yolo-mode.test.ts +19 -1
- package/tests/yolo-session-policy-capture.test.ts +104 -0
- package/tests/yolo-temp-scratch.test.ts +71 -0
- package/scripts/mirror-odd-routing.mjs +0 -242
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Observable selected-record authority only, not conversation durability.
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
|
+
import {
|
|
5
|
+
isSessionProfileFamilyEntry,
|
|
6
|
+
readSessionProfileEntry,
|
|
7
|
+
type SessionProfileReadResult,
|
|
8
|
+
} from "./session-profile-persistence.ts";
|
|
9
|
+
|
|
10
|
+
export interface SessionProfileSource {
|
|
11
|
+
getSessionId(): string;
|
|
12
|
+
getSessionFile(): string | undefined;
|
|
13
|
+
getBranch(): readonly unknown[];
|
|
14
|
+
}
|
|
15
|
+
export type DiskProfileIndeterminateReason =
|
|
16
|
+
| "missing-source"
|
|
17
|
+
| "invalid-candidate-metadata"
|
|
18
|
+
| "unreadable-file"
|
|
19
|
+
| "source-changed"
|
|
20
|
+
| "invalid-record"
|
|
21
|
+
| "session-header-mismatch"
|
|
22
|
+
| "duplicate-id"
|
|
23
|
+
| "missing-header"
|
|
24
|
+
| "selected-record-missing"
|
|
25
|
+
| "selected-record-mismatch"
|
|
26
|
+
| "unserializable-source";
|
|
27
|
+
export type DiskProfileResult =
|
|
28
|
+
| {
|
|
29
|
+
status: "indeterminate";
|
|
30
|
+
reason: DiskProfileIndeterminateReason;
|
|
31
|
+
entryIndex?: never;
|
|
32
|
+
lineNumber?: number;
|
|
33
|
+
}
|
|
34
|
+
| (SessionProfileReadResult & { entryIndex?: number; lineNumber?: number });
|
|
35
|
+
|
|
36
|
+
export interface DiskProfileOptions {
|
|
37
|
+
readFile?: (path: string) => string;
|
|
38
|
+
/** Trusted caller evidence ONLY: exclude known failed appends, never arbitrary
|
|
39
|
+
* invalid/future entries. This reader owns no quarantine or append controller.
|
|
40
|
+
* Failed IDs remain excluded even if they subsequently appear on disk. */
|
|
41
|
+
knownFailedEntryIds?: ReadonlySet<string>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
45
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
46
|
+
}
|
|
47
|
+
/** Own required metadata of a profile-family candidate, before serialization. */
|
|
48
|
+
export function hasSessionProfileCandidateMetadata(value: unknown): boolean {
|
|
49
|
+
return (
|
|
50
|
+
record(value) && metadata(value) && Object.hasOwn(value, "customType")
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function metadata(value: Record<string, unknown>): boolean {
|
|
54
|
+
return (
|
|
55
|
+
["type", "id", "parentId", "timestamp"].every((key) =>
|
|
56
|
+
Object.hasOwn(value, key),
|
|
57
|
+
) &&
|
|
58
|
+
typeString(value.type) &&
|
|
59
|
+
typeString(value.id) &&
|
|
60
|
+
typeString(value.timestamp) &&
|
|
61
|
+
(value.parentId === null || typeString(value.parentId))
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
function typeString(value: unknown): value is string {
|
|
65
|
+
return typeof value === "string" && value.length > 0;
|
|
66
|
+
}
|
|
67
|
+
function snapshot(value: unknown): unknown {
|
|
68
|
+
// Same undefined omission/toJSON behavior as Pi's JSONL serialization. Throws
|
|
69
|
+
// on cycles/BigInt; decoded copies cannot retain mutable memory references.
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(JSON.stringify(value));
|
|
72
|
+
} catch {
|
|
73
|
+
throw new TypeError("Unserializable source snapshot");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function unavailable(
|
|
77
|
+
reason: DiskProfileIndeterminateReason,
|
|
78
|
+
lineNumber?: number): DiskProfileResult {
|
|
79
|
+
return lineNumber === undefined
|
|
80
|
+
? { status: "indeterminate", reason }
|
|
81
|
+
: { status: "indeterminate", reason, lineNumber };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Stateless synchronous read of the captured public active branch and file.
|
|
86
|
+
* No writes, SDK imports, UI, fallback policy, cache, ancestry repair or fsync.
|
|
87
|
+
* An orphan selected record can be corroborated: complete disk ancestry is NOT
|
|
88
|
+
* required. No external-writer/TOCTOU guarantee is implied by source checks.
|
|
89
|
+
* Missing disk is conservative and does not decide future memory-only activation.
|
|
90
|
+
*/
|
|
91
|
+
export function readSessionProfileDisk(
|
|
92
|
+
source: SessionProfileSource,
|
|
93
|
+
options: DiskProfileOptions = {},
|
|
94
|
+
): DiskProfileResult {
|
|
95
|
+
try {
|
|
96
|
+
const sessionId = source.getSessionId();
|
|
97
|
+
const path = source.getSessionFile();
|
|
98
|
+
const rawBranch = source.getBranch();
|
|
99
|
+
if (!typeString(sessionId) || !typeString(path) || !Array.isArray(rawBranch))
|
|
100
|
+
return unavailable("missing-source");
|
|
101
|
+
// Validate own required candidate metadata before serialization can hide
|
|
102
|
+
// missing/inherited fields. Ordinary branch entries have no profile meaning.
|
|
103
|
+
for (const raw of rawBranch) {
|
|
104
|
+
if (
|
|
105
|
+
isSessionProfileFamilyEntry(raw) &&
|
|
106
|
+
!hasSessionProfileCandidateMetadata(raw)
|
|
107
|
+
)
|
|
108
|
+
return unavailable("invalid-candidate-metadata");
|
|
109
|
+
}
|
|
110
|
+
const branch = snapshot(rawBranch) as unknown[];
|
|
111
|
+
const failed = new Set(options.knownFailedEntryIds);
|
|
112
|
+
let entryIndex = -1;
|
|
113
|
+
for (let index = branch.length - 1; index >= 0; index--) {
|
|
114
|
+
const candidate = branch[index];
|
|
115
|
+
if (
|
|
116
|
+
isSessionProfileFamilyEntry(candidate) &&
|
|
117
|
+
!failed.has((candidate as { id?: unknown }).id as string)
|
|
118
|
+
) {
|
|
119
|
+
entryIndex = index;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
let text: string;
|
|
124
|
+
try {
|
|
125
|
+
text = (options.readFile ?? ((file) => readFileSync(file, "utf8")))(path);
|
|
126
|
+
} catch {
|
|
127
|
+
return unavailable("unreadable-file");
|
|
128
|
+
}
|
|
129
|
+
if (
|
|
130
|
+
source.getSessionId() !== sessionId ||
|
|
131
|
+
source.getSessionFile() !== path ||
|
|
132
|
+
!isDeepStrictEqual(snapshot(source.getBranch()), branch)
|
|
133
|
+
)
|
|
134
|
+
return unavailable("source-changed");
|
|
135
|
+
const disk = new Map<
|
|
136
|
+
string,
|
|
137
|
+
{ value: Record<string, unknown>; lineNumber: number }
|
|
138
|
+
>();
|
|
139
|
+
let foundHeader = false;
|
|
140
|
+
const lines = text.split("\n");
|
|
141
|
+
for (let index = 0; index < lines.length; index++) {
|
|
142
|
+
if (!lines[index].trim()) continue;
|
|
143
|
+
const lineNumber = index + 1;
|
|
144
|
+
let value: unknown;
|
|
145
|
+
try {
|
|
146
|
+
value = JSON.parse(lines[index]);
|
|
147
|
+
} catch {
|
|
148
|
+
// Pi's session parser skips malformed lines, so a file Pi opens must
|
|
149
|
+
// not become permanently indeterminate here.
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (!record(value)) return unavailable("invalid-record", lineNumber);
|
|
153
|
+
if (!foundHeader) {
|
|
154
|
+
if (value.type !== "session" || value.id !== sessionId)
|
|
155
|
+
return unavailable("session-header-mismatch", lineNumber);
|
|
156
|
+
foundHeader = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (!metadata(value) || value.type === "session")
|
|
160
|
+
return unavailable("invalid-record", lineNumber);
|
|
161
|
+
if (disk.has(value.id as string) || value.id === sessionId)
|
|
162
|
+
return unavailable("duplicate-id", lineNumber);
|
|
163
|
+
disk.set(value.id as string, { value, lineNumber });
|
|
164
|
+
}
|
|
165
|
+
if (!foundHeader) return unavailable("missing-header");
|
|
166
|
+
if (entryIndex < 0) return { status: "absent" };
|
|
167
|
+
const selected = branch[entryIndex] as Record<string, unknown>;
|
|
168
|
+
const saved = disk.get(selected.id as string);
|
|
169
|
+
if (!saved) return unavailable("selected-record-missing");
|
|
170
|
+
if (!isDeepStrictEqual(selected, saved.value))
|
|
171
|
+
return unavailable("selected-record-mismatch", saved.lineNumber);
|
|
172
|
+
const semantic = readSessionProfileEntry({
|
|
173
|
+
type: selected.type as string,
|
|
174
|
+
customType: selected.customType as string,
|
|
175
|
+
data: selected.data,
|
|
176
|
+
});
|
|
177
|
+
return { ...semantic, entryIndex, lineNumber: saved.lineNumber };
|
|
178
|
+
} catch {
|
|
179
|
+
// Never expose exception messages or record contents (including getters).
|
|
180
|
+
return unavailable("unserializable-source");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
// `gentle-pi.session-profile/v1` entry with origin `local | repo | global` is
|
|
28
28
|
// the integration point with slice 3b-i.
|
|
29
29
|
import { createHash } from "node:crypto";
|
|
30
|
+
import { normalizeModelConfig, type AgentModelConfig } from "./model-routing-authority.ts";
|
|
30
31
|
import { profileRoleEntries, profilesFilePath, readProfilesFileResult } from "./agent-profiles.ts";
|
|
31
32
|
import { readProfilePinStatus, resolveProfilePin, resolveUnversionedProjectProfile } from "./agent-profile-pin.ts";
|
|
32
|
-
import type { AgentModelConfig } from "./model-routing-authority.ts";
|
|
33
33
|
import { readSessionProfileBinding } from "./session-profile-binding.ts";
|
|
34
34
|
import type { WorktreeIdentity, WorktreeResolver } from "./session-worktree-registry.ts";
|
|
35
35
|
|
|
@@ -242,6 +242,7 @@ function describe(profile: InheritedProfile | undefined): string {
|
|
|
242
242
|
*/
|
|
243
243
|
export function inheritedProfileDriftNotice(options: SessionProfileOptions): string | undefined {
|
|
244
244
|
const resolution = resolveSessionProfile(options);
|
|
245
|
+
if (resolution.kind === "explicit") return bindingDefinitionDriftNotice(options, resolution);
|
|
245
246
|
if (resolution.kind !== "inherited") return undefined;
|
|
246
247
|
const current = resolveInheritedProfile(options);
|
|
247
248
|
const key = fingerprint(current);
|
|
@@ -254,6 +255,34 @@ export function inheritedProfileDriftNotice(options: SessionProfileOptions): str
|
|
|
254
255
|
return `el Gentleman: the default profile changed to ${describe(current)}, this session keeps ${describe(resolution.profile)}. Press Enter on a profile in /gentle:profiles to adopt it.`;
|
|
255
256
|
}
|
|
256
257
|
|
|
258
|
+
// gentle-shell#2228: an explicit binding re-copies the profile definition at
|
|
259
|
+
// selection, so later edits to that entry in profiles.json no-op silently until
|
|
260
|
+
// the operator re-Enters. The same once-per-change fingerprint machine as the
|
|
261
|
+
// inherited notice names the drift; the comparison never adopts the edit.
|
|
262
|
+
function bindingDefinitionDriftNotice(
|
|
263
|
+
options: SessionProfileOptions,
|
|
264
|
+
resolution: { kind: "explicit"; name: string; modelProfiles: AgentModelConfig },
|
|
265
|
+
): string | undefined {
|
|
266
|
+
const file = readProfilesFileResult(profilesFilePath(options.configHome));
|
|
267
|
+
if (file.status !== "valid") return undefined;
|
|
268
|
+
const definition = file.file.profiles[resolution.name];
|
|
269
|
+
if (definition === undefined) return undefined;
|
|
270
|
+
// Both sides normalize through the store's own route normalizer: a bound
|
|
271
|
+
// snapshot taken from the panel holds normalized routes, while raw string
|
|
272
|
+
// forms from other callers must hash equal to the same definition.
|
|
273
|
+
const normalized = (config: AgentModelConfig) =>
|
|
274
|
+
Object.fromEntries(Object.entries(config).map(([agent, route]) => [agent, normalizeModelConfig(route)]));
|
|
275
|
+
const key = createHash("sha256").update(stableJson({ modelProfiles: normalized(definition) })).digest("hex");
|
|
276
|
+
const current = createHash("sha256").update(stableJson({ modelProfiles: normalized(resolution.modelProfiles) })).digest("hex");
|
|
277
|
+
if (key === current) return undefined;
|
|
278
|
+
const notified = registry().notified;
|
|
279
|
+
const seen = notified.get(options.sessionId!) ?? new Set<string>();
|
|
280
|
+
if (seen.has(key)) return undefined;
|
|
281
|
+
seen.add(key);
|
|
282
|
+
notified.set(options.sessionId!, seen);
|
|
283
|
+
return `el Gentleman: the profile definition changed to "${resolution.name}" (profiles.json), this session keeps the binding from selection. Press Enter on a profile in /gentle:profiles to adopt it.`;
|
|
284
|
+
}
|
|
285
|
+
|
|
257
286
|
/** Testing-only reset of every frozen snapshot and drift memory. */
|
|
258
287
|
export function resetFrozenInheritedProfilesForTesting(): void {
|
|
259
288
|
registry().frozen.clear();
|
|
@@ -11,7 +11,20 @@ import {
|
|
|
11
11
|
} from "./model-routing-authority.ts";
|
|
12
12
|
|
|
13
13
|
export const SESSION_PROFILE_CUSTOM_TYPE = "gentle-pi.session-profile/v1";
|
|
14
|
-
const SESSION_PROFILE_FAMILY = "gentle-pi.session-profile/";
|
|
14
|
+
export const SESSION_PROFILE_FAMILY = "gentle-pi.session-profile/";
|
|
15
|
+
|
|
16
|
+
/** True for any custom entry in the session-profile family, known version or not. */
|
|
17
|
+
export function isSessionProfileFamilyEntry(entry: unknown): boolean {
|
|
18
|
+
return (
|
|
19
|
+
typeof entry === "object" &&
|
|
20
|
+
entry !== null &&
|
|
21
|
+
(entry as { type?: unknown }).type === "custom" &&
|
|
22
|
+
typeof (entry as { customType?: unknown }).customType === "string" &&
|
|
23
|
+
(entry as { customType: string }).customType.startsWith(
|
|
24
|
+
SESSION_PROFILE_FAMILY,
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
15
28
|
|
|
16
29
|
export type SessionProfileOrigin = "user" | "local" | "repo" | "global";
|
|
17
30
|
|
|
@@ -148,12 +161,7 @@ export function createSessionProfileClear(): SessionProfileClearPayload {
|
|
|
148
161
|
export function readSessionProfileEntry(
|
|
149
162
|
entry: SessionProfileEntry,
|
|
150
163
|
): SessionProfileReadResult {
|
|
151
|
-
if (
|
|
152
|
-
entry.type !== "custom" ||
|
|
153
|
-
typeof entry.customType !== "string" ||
|
|
154
|
-
!entry.customType.startsWith(SESSION_PROFILE_FAMILY)
|
|
155
|
-
)
|
|
156
|
-
return { status: "absent" };
|
|
164
|
+
if (!isSessionProfileFamilyEntry(entry)) return { status: "absent" };
|
|
157
165
|
if (entry.customType !== SESSION_PROFILE_CUSTOM_TYPE)
|
|
158
166
|
return { status: "unsupported" };
|
|
159
167
|
const data = entry.data;
|