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
|
@@ -25,7 +25,7 @@ type LayoutRoot = Component & { [NODE]?: () => LayoutNode };
|
|
|
25
25
|
type Host = TUI & { mode?: string; layoutRoot?: LayoutRoot };
|
|
26
26
|
type SidebarCache = { revision: number };
|
|
27
27
|
type RailHit = { key: string; component: Component; startY: number; height: number; width: number };
|
|
28
|
-
type SectionCacheEntry = { component: Component; digest: string | undefined; revision: number; contentWidth: number; theme: ShellBarTheme; lines: string[] };
|
|
28
|
+
type SectionCacheEntry = { component: Component; digest: string | undefined; revision: number; contentWidth: number; theme: ShellBarTheme; themeSignature: string; lines: string[] };
|
|
29
29
|
type SidebarPresentation = { scrollTop: number; output: LayoutNode };
|
|
30
30
|
type PreparedRail = {
|
|
31
31
|
revision: number;
|
|
@@ -35,6 +35,7 @@ type PreparedRail = {
|
|
|
35
35
|
cardStyle: CardStyle;
|
|
36
36
|
root: LayoutRoot;
|
|
37
37
|
theme: ShellBarTheme;
|
|
38
|
+
themeSignature: string;
|
|
38
39
|
parts: Array<[string, SidebarRail]>;
|
|
39
40
|
digests: Array<string | undefined>;
|
|
40
41
|
contentWidth: number;
|
|
@@ -74,6 +75,20 @@ function railDigest(rail: SidebarRail): string | undefined {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
// Pi passes extensions one live theme proxy, so identity never changes on a
|
|
79
|
+
// theme switch, and the switch only calls ui.invalidate(), which does not reach
|
|
80
|
+
// the rail inside the layout tree. How the current theme paints a few roles is
|
|
81
|
+
// what changes, so the memo keys on that: a digest-only section (Status,
|
|
82
|
+
// Agents, Todos) would otherwise keep the previous theme's colors.
|
|
83
|
+
const THEME_SIGNATURE_ROLES = ["accent", "border", "borderAccent", "muted", "dim", "text", "success", "warning", "error"];
|
|
84
|
+
function themeSignature(theme: ShellBarTheme): string {
|
|
85
|
+
try {
|
|
86
|
+
return THEME_SIGNATURE_ROLES.map((role) => theme.fg(role, "·")).join("") + theme.bold("·");
|
|
87
|
+
} catch {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
77
92
|
export const STATUS_OWNER = { HEADER: "header", BOTTOM: "bottom" } as const;
|
|
78
93
|
export type StatusOwner = (typeof STATUS_OWNER)[keyof typeof STATUS_OWNER];
|
|
79
94
|
export interface StatusOwnerInput {
|
|
@@ -217,9 +232,13 @@ export function installSidebar(tui: TUI, theme: ShellBarTheme, placement: () =>
|
|
|
217
232
|
}
|
|
218
233
|
const style = cardStyle();
|
|
219
234
|
const parts = [...state.parts.entries()];
|
|
235
|
+
// Digest closures read live session state, so they must be evaluated once
|
|
236
|
+
// per frame and stored for the next comparison (issue #1681): the sections
|
|
237
|
+
// loop below reuses this pass instead of calling each digest a second time.
|
|
220
238
|
const digests = parts.map(([, rail]) => railDigest(rail));
|
|
239
|
+
const signature = themeSignature(theme);
|
|
221
240
|
const unchanged = prepared?.revision === cache.revision &&
|
|
222
|
-
prepared.width === width && prepared.mode === host.mode && prepared.headerPlacement === headerPlacement() && prepared.cardStyle === style && prepared.root === root && prepared.theme === theme &&
|
|
241
|
+
prepared.width === width && prepared.mode === host.mode && prepared.headerPlacement === headerPlacement() && prepared.cardStyle === style && prepared.root === root && prepared.theme === theme && prepared.themeSignature === signature &&
|
|
223
242
|
prepared.parts.length === parts.length && prepared.parts.every(([key, part], index) => parts[index]?.[0] === key && parts[index]?.[1] === part) &&
|
|
224
243
|
prepared.digests.length === digests.length && prepared.digests.every((digest, index) => digest === digests[index]);
|
|
225
244
|
if (unchanged) {
|
|
@@ -228,6 +247,7 @@ export function installSidebar(tui: TUI, theme: ShellBarTheme, placement: () =>
|
|
|
228
247
|
return prepared.active;
|
|
229
248
|
}
|
|
230
249
|
try {
|
|
250
|
+
const digestByPart = new Map(parts.map(([key], index) => [key, digests[index]]));
|
|
231
251
|
// The header is a full-width sibling row, not a rail section: it reads
|
|
232
252
|
// the terminal width (minus the rail's right inset), never the
|
|
233
253
|
// 50-column rail's content width.
|
|
@@ -241,16 +261,16 @@ export function installSidebar(tui: TUI, theme: ShellBarTheme, placement: () =>
|
|
|
241
261
|
sectionCache.delete(key);
|
|
242
262
|
return { key, component, lines: [] as string[] };
|
|
243
263
|
}
|
|
244
|
-
const digest =
|
|
264
|
+
const digest = digestByPart.get(key);
|
|
245
265
|
const existing = sectionCache.get(key);
|
|
246
|
-
const reusable = existing?.component === component && existing.contentWidth === contentWidth && existing.theme === theme &&
|
|
266
|
+
const reusable = existing?.component === component && existing.contentWidth === contentWidth && existing.theme === theme && existing.themeSignature === signature &&
|
|
247
267
|
(digest !== undefined ? existing.digest === digest : existing.digest === undefined && existing.revision === cache.revision);
|
|
248
268
|
const lines = reusable ? existing.lines : (() => {
|
|
249
269
|
const rendered = [...(component.render(contentWidth - RAIL_PADDING * 2) ?? [])];
|
|
250
270
|
while (rendered.length && rendered[rendered.length - 1]?.trim() === "") rendered.pop();
|
|
251
271
|
return rendered;
|
|
252
272
|
})();
|
|
253
|
-
sectionCache.set(key, { component, digest, revision: cache.revision, contentWidth, theme, lines });
|
|
273
|
+
sectionCache.set(key, { component, digest, revision: cache.revision, contentWidth, theme, themeSignature: signature, lines });
|
|
254
274
|
return { key, component, lines };
|
|
255
275
|
}).filter((section) => section.component !== undefined && section.lines.length > 0) as Array<{ key: string; component: Component; lines: string[] }>;
|
|
256
276
|
// The header carries the brand once it is active; the banner is the
|
|
@@ -276,7 +296,7 @@ export function installSidebar(tui: TUI, theme: ShellBarTheme, placement: () =>
|
|
|
276
296
|
// Height is owned by the native ScrollView, never by the transcript.
|
|
277
297
|
const active = railLines.length > 0 && railLines.every((line) => visibleWidth(line) <= contentWidth);
|
|
278
298
|
headerLines = headerActive && headerPlacement() === "top" ? preparedHeaderLines : [];
|
|
279
|
-
prepared = { revision: cache.revision, width, mode: host.mode, headerPlacement: headerPlacement(), cardStyle: style, root, theme, parts, digests, contentWidth, active, lines: railLines, hits, headerLines, headerActive: headerLines.length > 0 };
|
|
299
|
+
prepared = { revision: cache.revision, width, mode: host.mode, headerPlacement: headerPlacement(), cardStyle: style, root, theme, themeSignature: signature, parts, digests, contentWidth, active, lines: railLines, hits, headerLines, headerActive: headerLines.length > 0 };
|
|
280
300
|
state.active = active;
|
|
281
301
|
return active;
|
|
282
302
|
} catch {
|
package/lib/shell-todo.ts
CHANGED
|
@@ -90,6 +90,21 @@ export interface TodoRenderOptions {
|
|
|
90
90
|
export const TODO_DETAILS_KEY = "gentleTodo";
|
|
91
91
|
export const TODO_TOOL_NAME = "todo";
|
|
92
92
|
export const TODO_GLYPH = "❀";
|
|
93
|
+
/** Emitted on pi.events after each successful todo write; gentle-agents publishes it as session progress. */
|
|
94
|
+
export const TODO_PROGRESS_EVENT = "gentle-pi:todo:progress";
|
|
95
|
+
|
|
96
|
+
export interface TodoProgress {
|
|
97
|
+
sessionId: string;
|
|
98
|
+
done: number;
|
|
99
|
+
total: number;
|
|
100
|
+
/** Title of the first in-progress task, or null when none is in progress. */
|
|
101
|
+
current: string | null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function todoProgress(state: TodoState, sessionId: string): TodoProgress {
|
|
105
|
+
const current = state.tasks.find((task) => task.status === TODO_STATUS.IN_PROGRESS)?.title ?? null;
|
|
106
|
+
return { sessionId, done: countStatus(state, TODO_STATUS.DONE), total: state.tasks.length, current };
|
|
107
|
+
}
|
|
93
108
|
const STATUS_ALIASES: Record<string, TodoStatus> = {
|
|
94
109
|
completed: TODO_STATUS.DONE,
|
|
95
110
|
complete: TODO_STATUS.DONE,
|
|
@@ -261,13 +276,14 @@ export function replayTodo(entries: readonly unknown[]): TodoState {
|
|
|
261
276
|
// gentle-shell#1367, #1662: the system prompt is a cached prefix for every
|
|
262
277
|
// provider, so the block it carries is byte-stable for the whole session:
|
|
263
278
|
// rules only, no task content and no counters. The current list travels as a
|
|
264
|
-
//
|
|
265
|
-
//
|
|
279
|
+
// hidden transcript message appended when it changes (todoStateMessage,
|
|
280
|
+
// gentle-shell#2195), so requests stay append-only and a change costs only
|
|
281
|
+
// the tail of the conversation, never the prefix.
|
|
266
282
|
const TODO_PROMPT_BLOCK = [
|
|
267
283
|
"## Todo list",
|
|
268
|
-
"
|
|
284
|
+
"A small task (Task Size) uses no todo list: do it and report. For tracked or multi-step work, keep it current with the `todo` tool: mark a task in_progress before starting it, done right after finishing it, and rewrite the whole list with `write` whenever the plan changes. Update it before you end the turn.",
|
|
269
285
|
"A blocked task waits on something outside the list: do not work on it until its condition holds. When the plan changes, mark the tasks it made obsolete dropped instead of leaving them pending or marking them done.",
|
|
270
|
-
"While tasks stay open, the current list arrives as
|
|
286
|
+
"While tasks stay open, the current list arrives as a hidden conversation message whenever it changes; the latest one is current, and it is not repeated here.",
|
|
271
287
|
].join("\n");
|
|
272
288
|
|
|
273
289
|
/** The byte-stable rules block appended to every system prompt. */
|
|
@@ -275,7 +291,10 @@ export function todoPromptBlock(): string {
|
|
|
275
291
|
return TODO_PROMPT_BLOCK;
|
|
276
292
|
}
|
|
277
293
|
|
|
278
|
-
/**
|
|
294
|
+
/** Persisted once an open list closes, so an earlier snapshot never reads as current work (#2195). */
|
|
295
|
+
export const TODO_CLOSED_STATE = "Current todo list: no open tasks.";
|
|
296
|
+
|
|
297
|
+
/** The open-task snapshot delivered as a hidden transcript message; undefined when nothing is open. */
|
|
279
298
|
export function todoStateMessage(state: TodoState): string | undefined {
|
|
280
299
|
if (todoSummary(state).open === 0) return undefined;
|
|
281
300
|
const lines = state.tasks.map((task, index) => `${index + 1}. [${task.status}] ${task.title}${task.note ? ` — ${task.note}` : ""}`);
|
package/lib/shell-usage-view.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Key, matchesKey, truncateToWidth, visibleWidth, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
2
2
|
import { renderUsagePanel, type ActiveProvider, type UsagePanelScope, type UsageSourceRegistry, type UsageStore, type UsageTheme } from "./shell-usage.ts";
|
|
3
|
+
import type { GaugePaint } from "./shell-gauge.ts";
|
|
3
4
|
import { paintHoverable } from "./shell-hover.ts";
|
|
4
5
|
|
|
5
6
|
// Gentle Shell subscriptions overlay: a framed panel over the usage store.
|
|
@@ -16,6 +17,9 @@ export interface UsageViewDeps {
|
|
|
16
17
|
// provider the store holds; with it, exactly the targeted providers, in
|
|
17
18
|
// scope order, explaining the ones without data.
|
|
18
19
|
scope?(): UsagePanelScope | undefined;
|
|
20
|
+
// Optional: gauge-specific theme roles resolved from the installed theme
|
|
21
|
+
// source; without it the panel paints with the shared roles.
|
|
22
|
+
gauge?(): GaugePaint | undefined;
|
|
19
23
|
onRefresh(): Promise<void>;
|
|
20
24
|
onClose(): void;
|
|
21
25
|
requestRender(): void;
|
|
@@ -106,7 +110,7 @@ export class UsageView {
|
|
|
106
110
|
const inner = width - 2;
|
|
107
111
|
const title = this.refreshing ? REFRESHING : TITLE;
|
|
108
112
|
const top = theme.fg(FRAME_ROLE, "╭─ ") + theme.fg(TITLE_ROLE, title) + theme.fg(FRAME_ROLE, ` ${rule(inner - visibleWidth(title) - 3)}╮`);
|
|
109
|
-
const body = renderUsagePanel(this.store.all(), theme, inner - 2, this.deps.now(), this.deps.active(), this.deps.registry?.(), this.deps.scope?.()).map(
|
|
113
|
+
const body = renderUsagePanel(this.store.all(), theme, inner - 2, this.deps.now(), this.deps.active(), this.deps.registry?.(), this.deps.scope?.(), this.deps.gauge?.()).map(
|
|
110
114
|
(line) => `${theme.fg(FRAME_ROLE, "│")} ${fit(line, inner - 2)} ${theme.fg(FRAME_ROLE, "│")}`,
|
|
111
115
|
);
|
|
112
116
|
const hints = KEYS.map(([key, label]) => ({ key, label, text: `${key} ${label}`, action: (key === "r" ? "refresh" : "close") as HintAction }));
|
package/lib/shell-usage.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
import { parseModelRef } from "./agents-config.ts";
|
|
3
|
-
import { paintGauge } from "./shell-gauge.ts";
|
|
3
|
+
import { paintGauge, type GaugePaint } from "./shell-gauge.ts";
|
|
4
4
|
|
|
5
5
|
// Gentle Shell subscription usage: the rate-limit windows each connected
|
|
6
6
|
// provider reports. Codex sends them as SSE headers and through its usage
|
|
@@ -514,16 +514,16 @@ export function accountIdFromToken(token: string): string | undefined {
|
|
|
514
514
|
}
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
-
function paintMeter(percent: number, cells: number, theme: UsageTheme): string {
|
|
518
|
-
return paintGauge(percent, theme, cells);
|
|
517
|
+
function paintMeter(percent: number, cells: number, theme: UsageTheme, paint?: GaugePaint): string {
|
|
518
|
+
return paintGauge(percent, theme, cells, paint);
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
-
export function renderUsageBar(usage: ProviderUsage, theme: UsageTheme, activeModelId?: string): string | undefined {
|
|
521
|
+
export function renderUsageBar(usage: ProviderUsage, theme: UsageTheme, activeModelId?: string, paint?: GaugePaint): string | undefined {
|
|
522
522
|
const main = selectUsageLimit(usage, activeModelId);
|
|
523
523
|
const [first, ...rest] = main?.windows ?? [];
|
|
524
524
|
if (!first) return undefined;
|
|
525
525
|
// An unlabeled window prints as the name, the meter and the percentage.
|
|
526
|
-
const head = [theme.fg(ROLE.LABEL, main.name), ...(first.label.length === 0 ? [] : [theme.fg(ROLE.LABEL, first.label)]), paintMeter(first.usedPercent, 8, theme), theme.fg(ROLE.PERCENT, `${Math.round(first.usedPercent)}%`)].join(" ");
|
|
526
|
+
const head = [theme.fg(ROLE.LABEL, main.name), ...(first.label.length === 0 ? [] : [theme.fg(ROLE.LABEL, first.label)]), paintMeter(first.usedPercent, 8, theme, paint), theme.fg(ROLE.PERCENT, `${Math.round(first.usedPercent)}%`)].join(" ");
|
|
527
527
|
const tail = rest.map((window) => `${theme.fg(ROLE.SEPARATOR, "·")} ${theme.fg(ROLE.LABEL, window.label)} ${theme.fg(ROLE.PERCENT, `${Math.round(window.usedPercent)}%`)}`);
|
|
528
528
|
return [head, ...tail].join(" ");
|
|
529
529
|
}
|
|
@@ -574,7 +574,7 @@ export interface UsagePanelScope {
|
|
|
574
574
|
// snapshot keeps that snapshot and says so beside it, and a provider recorded
|
|
575
575
|
// under a previous profile's routing is never presented as current scope after
|
|
576
576
|
// a profile switch.
|
|
577
|
-
export function renderUsagePanel(usages: ProviderUsage[], theme: UsageTheme, width: number, now: number, active?: ActiveProvider, registry?: UsageSourceRegistry, scope?: UsagePanelScope): string[] {
|
|
577
|
+
export function renderUsagePanel(usages: ProviderUsage[], theme: UsageTheme, width: number, now: number, active?: ActiveProvider, registry?: UsageSourceRegistry, scope?: UsagePanelScope, paint?: GaugePaint): string[] {
|
|
578
578
|
const activeUsage = active ? usages.find((usage) => usage.provider === active.provider) : undefined;
|
|
579
579
|
const byProvider = new Map(usages.map((usage) => [usage.provider, usage] as const));
|
|
580
580
|
const rows = scope
|
|
@@ -607,7 +607,7 @@ export function renderUsagePanel(usages: ProviderUsage[], theme: UsageTheme, wid
|
|
|
607
607
|
const percent = `${Math.round(row.window.usedPercent)}%`.padStart(4);
|
|
608
608
|
const reset = formatReset(row.window.resetAt, now);
|
|
609
609
|
const tail = reset.length > 0 ? ` ${theme.fg(ROLE.SEPARATOR, "·")} ${theme.fg(ROLE.RESET, reset)}` : "";
|
|
610
|
-
lines.push(` ${theme.fg(ROLE.LABEL, row.name.padEnd(nameWidth))} ${paintMeter(row.window.usedPercent, PANEL_METER_CELLS, theme)} ${theme.fg(ROLE.PERCENT, percent)}${tail}`);
|
|
610
|
+
lines.push(` ${theme.fg(ROLE.LABEL, row.name.padEnd(nameWidth))} ${paintMeter(row.window.usedPercent, PANEL_METER_CELLS, theme, paint)} ${theme.fg(ROLE.PERCENT, percent)}${tail}`);
|
|
611
611
|
}
|
|
612
612
|
// A failure after a good snapshot never hides either fact: the last good
|
|
613
613
|
// snapshot stays on its rows and the failed refresh says so beside it.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { namesExistingFeatureDocumentTask } from "./bounded-writer-admission.ts";
|
|
2
|
+
import { contextBackstopReached, type MeasuredContext } from "./context-backstop.ts";
|
|
3
|
+
|
|
4
|
+
// odd/tasks/verifier-admission.md: the Verification rule reserves the independent verifier for a change that names a
|
|
5
|
+
// Task Size high-risk item, yet a bench run still launched it on a small, not high-risk task. In the primary session a
|
|
6
|
+
// verifier launch is admitted only when its task or context declares a named high-risk item, carries a task of an
|
|
7
|
+
// existing feature document (the same lines writer admission accepts), or the parent's measured context reaches the
|
|
8
|
+
// context backstop. The agent catalog has no generic verifier alias, so only `gentle-ai-verify` is gated.
|
|
9
|
+
const VERIFIER_NAMES = ["gentle-ai-verify"];
|
|
10
|
+
// A declaration that starts a line (optionally as a list item) or a sentence (after `.`, `!` or `?` and a space) with
|
|
11
|
+
// `Risk:` whose first entry is `item N` with N in 1-6, for example `Risk: item 3 (shared parser)`, `- Risk: item 2,
|
|
12
|
+
// item 3 (reason)` or `... on top of ebf3f35. Risk: item 1 (migration)`. `Risk: none` and a mention inside a
|
|
13
|
+
// sentence ("Do not write Risk: item 3") do not start one.
|
|
14
|
+
const HIGH_RISK_ITEM_LINE = /(?:^[ \t]*(?:[-*+][ \t]+)?|[.!?][ \t]+)Risk:[ \t]*item[ \t]+[1-6](?!\d)/m;
|
|
15
|
+
export const VERIFIER_RISK_REJECTION =
|
|
16
|
+
"An independent verifier runs only for a change that declares a named high-risk item (`Risk: item N` from the Task Size list in the task). This change declares none: run its focused test and suite inline yourself now; do not retry the launch, and never add a Risk line or create a feature document only to get a verifier admitted.";
|
|
17
|
+
|
|
18
|
+
export function rejectUnriskedVerifierLaunch(
|
|
19
|
+
input: unknown,
|
|
20
|
+
options: { root: string; contextUsage: MeasuredContext | undefined; env?: Readonly<Record<string, string | undefined>> },
|
|
21
|
+
): { block: true; reason: string } | undefined {
|
|
22
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return undefined;
|
|
23
|
+
const record = input as Record<string, unknown>;
|
|
24
|
+
if (typeof record.agent !== "string" || !VERIFIER_NAMES.includes(record.agent)) return undefined;
|
|
25
|
+
const values = [record.task, record.context];
|
|
26
|
+
if (values.some(value => typeof value === "string" && HIGH_RISK_ITEM_LINE.test(value))) return undefined;
|
|
27
|
+
if (namesExistingFeatureDocumentTask(values, options.root)) return undefined;
|
|
28
|
+
if (contextBackstopReached(options.contextUsage, options.env)) return undefined;
|
|
29
|
+
return { block: true, reason: VERIFIER_RISK_REJECTION };
|
|
30
|
+
}
|
|
@@ -32,6 +32,8 @@ interface PrivateEditor {
|
|
|
32
32
|
onChange?: (text: string) => void;
|
|
33
33
|
pastes: Map<number, string>;
|
|
34
34
|
pasteCounter: number;
|
|
35
|
+
/** Host-owned atomic spans (gentle-shell#2464 image chips) that NORMAL motions must not split. */
|
|
36
|
+
atomicSpans?(line: string): Array<{ start: number; end: number }>;
|
|
35
37
|
undoStack: Stack;
|
|
36
38
|
history: string[];
|
|
37
39
|
historyIndex: number;
|
|
@@ -151,12 +153,13 @@ function highlightRenderedRow(row: string, chunk: LayoutLine, left: number, righ
|
|
|
151
153
|
return result;
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
function boundaries(line: string,
|
|
156
|
+
function boundaries(line: string, editor: Pick<PrivateEditor, "pastes" | "atomicSpans">): number[] {
|
|
155
157
|
const units = [...segmenter.segment(line)].map((part) => part.index);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
const spans = [...line.matchAll(marker)]
|
|
159
|
+
.filter((match) => editor.pastes.has(Number(match[1])))
|
|
160
|
+
.map((match) => ({ start: match.index, end: match.index + match[0].length }));
|
|
161
|
+
if (typeof editor.atomicSpans === "function") spans.push(...editor.atomicSpans(line));
|
|
162
|
+
for (const { start, end } of spans) {
|
|
160
163
|
for (let i = units.length - 1; i >= 0; i--) {
|
|
161
164
|
if (units[i]! > start && units[i]! < end) units.splice(i, 1);
|
|
162
165
|
}
|
|
@@ -177,7 +180,7 @@ function assertUniquePastes(editor: PrivateEditor): void {
|
|
|
177
180
|
function assertPosition(editor: PrivateEditor, pos: Position): void {
|
|
178
181
|
assertUniquePastes(editor);
|
|
179
182
|
const line = editor.state.lines[pos.line];
|
|
180
|
-
if (line === undefined || !Number.isInteger(pos.col) || !boundaries(line, editor
|
|
183
|
+
if (line === undefined || !Number.isInteger(pos.col) || !boundaries(line, editor).includes(pos.col)) {
|
|
181
184
|
throw new Error("Cursor must be on a valid grapheme or paste boundary");
|
|
182
185
|
}
|
|
183
186
|
}
|
|
@@ -265,7 +268,7 @@ export function createVimEditorAdapter(value: unknown, version: string, editorCl
|
|
|
265
268
|
},
|
|
266
269
|
motionBoundaries(): number[][] {
|
|
267
270
|
assertUniquePastes(editor);
|
|
268
|
-
return editor.state.lines.map((line) => boundaries(line, editor
|
|
271
|
+
return editor.state.lines.map((line) => boundaries(line, editor));
|
|
269
272
|
},
|
|
270
273
|
move(pos: Position): void {
|
|
271
274
|
assertPosition(editor, pos);
|
|
@@ -277,7 +280,7 @@ export function createVimEditorAdapter(value: unknown, version: string, editorCl
|
|
|
277
280
|
moveByGraphemes(delta: number): void {
|
|
278
281
|
if (!Number.isInteger(delta)) throw new Error("Invalid motion");
|
|
279
282
|
const positions = editor.state.lines.flatMap((line, index) =>
|
|
280
|
-
boundaries(line, editor
|
|
283
|
+
boundaries(line, editor).map((col) => ({ line: index, col })));
|
|
281
284
|
const current = editor.getCursor();
|
|
282
285
|
const at = positions.findIndex((p) => p.line === current.line && p.col === current.col);
|
|
283
286
|
if (at < 0) throw new Error("Invalid cursor boundary");
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Pinned artifacts for the optional local voice dictation (S1-S3). Nothing here is
|
|
2
|
+
// fetched or resolved at runtime: every version, size and digest below was read
|
|
3
|
+
// from the public npm registry and Hugging Face metadata for the exact versions,
|
|
4
|
+
// and the tarball layouts were inspected for the facts the comments state.
|
|
5
|
+
// Installation lives in voice-install.ts; this module is data only.
|
|
6
|
+
|
|
7
|
+
export type VoiceTarget = "darwin-x64" | "darwin-arm64" | "linux-x64" | "linux-arm64" | "win32-x64";
|
|
8
|
+
|
|
9
|
+
export const VOICE_TARGETS: readonly VoiceTarget[] = ["darwin-x64", "darwin-arm64", "linux-x64", "linux-arm64", "win32-x64"];
|
|
10
|
+
|
|
11
|
+
// A redirect hop is followed only when its URL is HTTPS on this origin and its
|
|
12
|
+
// path starts with the prefix. Anything else ends the download.
|
|
13
|
+
export interface VoiceRedirectRule {
|
|
14
|
+
origin: string;
|
|
15
|
+
pathPrefix: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface VoiceNpmPackage {
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
license: string;
|
|
22
|
+
tarball: string;
|
|
23
|
+
// The packed tarball size in bytes and its registry `dist.integrity`.
|
|
24
|
+
size: number;
|
|
25
|
+
integrity: string;
|
|
26
|
+
// Registry `dist.unpackedSize` and `dist.fileCount`: hard limits for extraction.
|
|
27
|
+
unpackedSize: number;
|
|
28
|
+
fileCount: number;
|
|
29
|
+
// Paths relative to the package root that must be present after extraction.
|
|
30
|
+
requiredFiles: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface VoiceRuntimeManifest {
|
|
34
|
+
// The installed directory name; also part of the install marker.
|
|
35
|
+
id: string;
|
|
36
|
+
target: VoiceTarget;
|
|
37
|
+
packages: VoiceNpmPackage[];
|
|
38
|
+
redirects: VoiceRedirectRule[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface VoiceModelFile {
|
|
42
|
+
name: string;
|
|
43
|
+
size: number;
|
|
44
|
+
sha256: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface VoiceModelManifest {
|
|
48
|
+
id: string;
|
|
49
|
+
revision: string;
|
|
50
|
+
// Files are fetched from `${baseUrl}/${name}`.
|
|
51
|
+
baseUrl: string;
|
|
52
|
+
files: VoiceModelFile[];
|
|
53
|
+
redirects: VoiceRedirectRule[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const SHERPA_VERSION = "1.13.8";
|
|
57
|
+
|
|
58
|
+
const npmTarball = (name: string, version: string) => `https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`;
|
|
59
|
+
|
|
60
|
+
// sherpa-onnx-node holds only JavaScript. Its addon-static-import.js loads
|
|
61
|
+
// `../sherpa-onnx-<platform>-<arch>/sherpa-onnx.node` (Windows spelled `win`), so the
|
|
62
|
+
// platform package must be installed as a sibling directory under node_modules.
|
|
63
|
+
export const SHERPA_ONNX_NODE: VoiceNpmPackage = {
|
|
64
|
+
name: "sherpa-onnx-node",
|
|
65
|
+
version: SHERPA_VERSION,
|
|
66
|
+
license: "Apache-2.0",
|
|
67
|
+
tarball: npmTarball("sherpa-onnx-node", SHERPA_VERSION),
|
|
68
|
+
size: 11954,
|
|
69
|
+
integrity: "sha512-MsDMBdhLFTZ1GwvcGSSQhnS7g/EA8OMH6IYysCVUOM7j8Icty9KRc0E6YT1A5fWBsZwRfKOeh88QC95aRvS8ag==",
|
|
70
|
+
unpackedSize: 61221,
|
|
71
|
+
fileCount: 19,
|
|
72
|
+
requiredFiles: ["package.json", "sherpa-onnx.js", "addon.js", "addon-static-import.js", "non-streaming-asr.js"],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function sherpaPlatform(target: VoiceTarget, size: number, integrity: string, unpackedSize: number, fileCount: number): VoiceNpmPackage {
|
|
76
|
+
const name = `sherpa-onnx-${target.replace(/^win32-/, "win-")}`;
|
|
77
|
+
return {
|
|
78
|
+
name,
|
|
79
|
+
version: SHERPA_VERSION,
|
|
80
|
+
license: "Apache-2.0",
|
|
81
|
+
tarball: npmTarball(name, SHERPA_VERSION),
|
|
82
|
+
size,
|
|
83
|
+
integrity,
|
|
84
|
+
unpackedSize,
|
|
85
|
+
fileCount,
|
|
86
|
+
requiredFiles: ["package.json", "sherpa-onnx.node"],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Each platform package bundles sherpa-onnx.node, the sherpa-onnx C/C++ API
|
|
91
|
+
// libraries and onnxruntime, all built for the named architecture.
|
|
92
|
+
export const SHERPA_ONNX_PLATFORM_PACKAGES: Readonly<Record<VoiceTarget, VoiceNpmPackage>> = {
|
|
93
|
+
"darwin-x64": sherpaPlatform("darwin-x64", 11191481, "sha512-7BLRpjM6w4f9W46/nmkmq8lEKUayhebvcpslCVQ+6QN2uReYlZEMDZlSpXMjme+hUFrPfRz8P3UNq8ep/4d19g==", 37678686, 7),
|
|
94
|
+
"darwin-arm64": sherpaPlatform("darwin-arm64", 10047754, "sha512-FPNgJMgnWVl/KhRTIhG3KL3A4Om63Rn4YKXc9/uHY7SzLcvqLJLc/h7UBWJwduXvv7K18t5NpxHR6XgXn4sjWw==", 34089738, 7),
|
|
95
|
+
"linux-x64": sherpaPlatform("linux-x64", 11089653, "sha512-6plnhjagsSeTntCgnlag86hWbs/uZE9Crms1LgOb68/1nKsIQjMd+WG519m+aPwT6TrsBOiEMzrx41t8sL5L5g==", 33381117, 7),
|
|
96
|
+
"linux-arm64": sherpaPlatform("linux-arm64", 13910679, "sha512-Tlg7a70b/Wge3OF8IgTHF9jhSVCsLyKQKhwc4BsJ5A+dL/SrFtGBjzuHp4XeLhiiOT7afCxX5PdSn/D4c8Lnuw==", 40306856, 7),
|
|
97
|
+
"win32-x64": sherpaPlatform("win32-x64", 8894875, "sha512-oZF1c9VPOKtMwn83Bboc5XSWL+76BRoyB3eUuVnCknBKxwSULZU2Foia9VHWzU+n4I12rPsP6z6H9Rp1hD9o8g==", 23453246, 8),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// node-cpal has no dependencies or install scripts and needs Node >= 22. Its
|
|
101
|
+
// tarball ships every prebuilt binary; the default backend loads
|
|
102
|
+
// bin/<platform>-<arch>/index.node through facade.js. The five binary headers were
|
|
103
|
+
// inspected (not loaded) and each is built for its own architecture. Published
|
|
104
|
+
// from saeta-eth/node-cpal tag v1.0.0, commit 20e2ac8d1922de7c4af50431e2ddc21a421cf764.
|
|
105
|
+
// 0.1.1 is not used: its darwin-x64 and linux-arm64 binaries were built for the
|
|
106
|
+
// other architecture.
|
|
107
|
+
const NODE_CPAL_VERSION = "1.0.0";
|
|
108
|
+
|
|
109
|
+
export const NODE_CPAL: VoiceNpmPackage = {
|
|
110
|
+
name: "node-cpal",
|
|
111
|
+
version: NODE_CPAL_VERSION,
|
|
112
|
+
license: "ISC",
|
|
113
|
+
tarball: npmTarball("node-cpal", NODE_CPAL_VERSION),
|
|
114
|
+
size: 7723259,
|
|
115
|
+
integrity: "sha512-d71jIzKDjw1HepqaJQMPd+/k3ZLqYZWhLAv6a8aafIGnYoA1QWlShA5iB5CgijIJtQxq9PdQAk99JQKspqlnTA==",
|
|
116
|
+
unpackedSize: 20363834,
|
|
117
|
+
fileCount: 40,
|
|
118
|
+
requiredFiles: ["package.json", "index.js", "facade.js", "cpal-values.js"],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// The oldest glibc the pinned Linux native binaries accept.
|
|
122
|
+
export const VOICE_LINUX_MIN_GLIBC = "2.34";
|
|
123
|
+
|
|
124
|
+
export function voiceRuntimeManifest(target: VoiceTarget): VoiceRuntimeManifest {
|
|
125
|
+
const packages = [
|
|
126
|
+
SHERPA_ONNX_NODE,
|
|
127
|
+
SHERPA_ONNX_PLATFORM_PACKAGES[target],
|
|
128
|
+
{ ...NODE_CPAL, requiredFiles: [...NODE_CPAL.requiredFiles, `bin/${target}/index.node`] },
|
|
129
|
+
];
|
|
130
|
+
// The id names both native versions, so a runtime installed with other pins
|
|
131
|
+
// lives in another directory and is never reported as this one.
|
|
132
|
+
// npm serves these tarballs directly; no redirect is expected or followed.
|
|
133
|
+
return { id: `sherpa-onnx-${SHERPA_VERSION}-node-cpal-${NODE_CPAL_VERSION}-${target}`, target, packages, redirects: [] };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const PARAKEET_REPO = "csukuangfj/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8";
|
|
137
|
+
const PARAKEET_REVISION = "2bda32ec70b097a55adaa07d9a7173915b43cc78";
|
|
138
|
+
|
|
139
|
+
// The int8 sherpa-onnx export of NVIDIA Parakeet TDT 0.6B v3 at a fixed commit.
|
|
140
|
+
// Observed redirects: tokens.txt answers 307 with a same-origin resolve-cache path,
|
|
141
|
+
// the LFS/Xet files answer 302 to us.aws.cdn.hf.co/xet-bridge-us/. Other CDN
|
|
142
|
+
// regions were not observed, so they are refused rather than guessed.
|
|
143
|
+
export const PARAKEET_MODEL_MANIFEST: VoiceModelManifest = {
|
|
144
|
+
id: `parakeet-tdt-0.6b-v3-int8-${PARAKEET_REVISION.slice(0, 12)}`,
|
|
145
|
+
revision: PARAKEET_REVISION,
|
|
146
|
+
baseUrl: `https://huggingface.co/${PARAKEET_REPO}/resolve/${PARAKEET_REVISION}`,
|
|
147
|
+
files: [
|
|
148
|
+
{ name: "encoder.int8.onnx", size: 652184281, sha256: "acfc2b4456377e15d04f0243af540b7fe7c992f8d898d751cf134c3a55fd2247" },
|
|
149
|
+
{ name: "decoder.int8.onnx", size: 11845275, sha256: "179e50c43d1a9de79c8a24149a2f9bac6eb5981823f2a2ed88d655b24248db4e" },
|
|
150
|
+
{ name: "joiner.int8.onnx", size: 6355277, sha256: "3164c13fc2821009440d20fcb5fdc78bff28b4db2f8d0f0b329101719c0948b3" },
|
|
151
|
+
{ name: "tokens.txt", size: 93939, sha256: "d58544679ea4bc6ac563d1f545eb7d474bd6cfa467f0a6e2c1dc1c7d37e3c35d" },
|
|
152
|
+
],
|
|
153
|
+
redirects: [
|
|
154
|
+
{ origin: "https://huggingface.co", pathPrefix: `/api/resolve-cache/models/${PARAKEET_REPO}/${PARAKEET_REVISION}/` },
|
|
155
|
+
{ origin: "https://us.aws.cdn.hf.co", pathPrefix: "/xet-bridge-us/" },
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export interface VoiceNotice {
|
|
160
|
+
component: string;
|
|
161
|
+
license: string;
|
|
162
|
+
source: string;
|
|
163
|
+
attribution?: string;
|
|
164
|
+
// What is known to be missing from the distributed artifact, stated plainly.
|
|
165
|
+
unresolved?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const VOICE_NOTICES: readonly VoiceNotice[] = [
|
|
169
|
+
{
|
|
170
|
+
component: "parakeet-tdt-0.6b-v3",
|
|
171
|
+
license: "CC-BY-4.0",
|
|
172
|
+
source: "https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3",
|
|
173
|
+
attribution: "Parakeet TDT 0.6B v3 by NVIDIA, licensed under CC-BY-4.0; int8 sherpa-onnx export by csukuangfj.",
|
|
174
|
+
unresolved: `The ${PARAKEET_REPO} export has no license file or model card license of its own; the license is taken from the upstream NVIDIA model.`,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
component: "sherpa-onnx-node",
|
|
178
|
+
license: "Apache-2.0",
|
|
179
|
+
source: "https://github.com/k2-fsa/sherpa-onnx",
|
|
180
|
+
unresolved: "The sherpa-onnx npm tarballs ship no LICENSE or NOTICE file, and the platform packages bundle onnxruntime (MIT) without its license text.",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
component: "node-cpal",
|
|
184
|
+
license: "ISC",
|
|
185
|
+
source: "https://github.com/saeta-eth/node-cpal",
|
|
186
|
+
unresolved: "The node-cpal@1.0.0 tarball ships no LICENSE or NOTICE file, and its prebuilt binaries link Rust crates whose notices are not included.",
|
|
187
|
+
},
|
|
188
|
+
];
|