gentle-pi 3.7.0 → 4.0.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 +37 -6
- package/assets/agents/gentle-ai-explore.md +4 -4
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -9
- package/assets/orchestrator-delegation.md +40 -41
- package/assets/orchestrator-memory.md +1 -22
- package/assets/orchestrator-skills.md +1 -1
- package/assets/orchestrator.md +12 -24
- package/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/gentle-shell.mjs +254 -29
- package/docs/delegated-verification.md +26 -1
- package/docs/gentle-agents-activity.md +24 -0
- package/docs/gentle-shell.md +92 -28
- package/docs/native-authority-architecture.md +2 -2
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +112 -206
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +26 -0
- package/extensions/child-safety.ts +23 -0
- package/extensions/gentle-agents.ts +429 -262
- package/extensions/gentle-ai.ts +781 -683
- package/extensions/gentle-shell.ts +1375 -88
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +18 -7
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1419 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/quiet-tools.ts +179 -87
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +16 -12
- package/extensions/startup-banner.ts +60 -31
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-protocol.ts +60 -0
- package/lib/agents-runner.ts +96 -98
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/bounded-writer-admission.ts +147 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/codemode-renderer.ts +185 -0
- package/lib/command-palette-catalog.ts +3 -9
- package/lib/command-palette.ts +25 -14
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-launcher.ts +24 -13
- package/lib/gentle-shell-resume-hint.ts +176 -0
- package/lib/history-capture-policy.ts +95 -0
- package/lib/model-routing-authority.ts +5 -1
- package/lib/nan-provider.ts +227 -0
- package/lib/native-review-cli.ts +49 -108
- package/lib/odd-phase-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- package/lib/overlay-repaint.ts +26 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +67 -17
- package/lib/review-candidate-view.ts +112 -25
- package/lib/review-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +156 -11
- package/lib/review-sidebar-state.ts +223 -0
- package/lib/selection-engine.ts +515 -0
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-worktree-registry.ts +14 -2
- package/lib/shell-bar.ts +179 -24
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +2 -1
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +78 -14
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +23 -13
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +66 -10
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +379 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +108 -0
- package/lib/visual-customize-view.ts +330 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/yolo-session-policy.ts +240 -0
- package/package.json +20 -8
- package/runtime/gentle-shell-launcher.mjs +23 -12
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- package/runtime/native-review-cli.mjs +49 -108
- package/runtime/review-risk-assessment.mjs +154 -9
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/gentle-ai-installer.mjs +14 -13
- package/scripts/mirror-odd-routing.mjs +2 -2
- package/scripts/run-test-suite.mjs +76 -0
- package/scripts/test-packed-runner.mjs +31 -14
- package/scripts/verify-package-files.mjs +11 -22
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +9 -15
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-profiles.test.ts +18 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +40 -0
- package/tests/agents-runner.test.ts +378 -89
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/autonomous-guard.test.ts +69 -1
- package/tests/bounded-writer-admission.test.ts +95 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- package/tests/card-style-policy.test.ts +55 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-context-files.test.ts +255 -0
- package/tests/child-safety.test.ts +82 -0
- package/tests/codemode-rendering.test.ts +491 -0
- package/tests/command-palette.test.ts +39 -3
- package/tests/delegated-key-learnings-contract.test.ts +0 -76
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +170 -2
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/generic-agent-tools.test.ts +54 -0
- package/tests/gentle-agents.test.ts +1047 -252
- package/tests/gentle-ai-binary.test.ts +3 -3
- package/tests/gentle-ai-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +68 -54
- package/tests/gentle-ai-renderer.test.ts +487 -8
- package/tests/gentle-ai.test.ts +288 -65
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-shell-bin.test.ts +651 -114
- package/tests/gentle-shell-launcher.test.ts +99 -52
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- package/tests/gentle-shell.test.ts +3839 -187
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +80 -8
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +29 -19
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/model-routing-authority.test.ts +16 -0
- package/tests/nan-provider.test.ts +471 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +6 -120
- package/tests/native-review-parity-runtime.test.ts +100 -3
- package/tests/odd-integration.test.ts +67 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +11 -6
- package/tests/odd-routing-contract.test.ts +86 -35
- package/tests/orchestrator-budget.test.ts +14 -39
- package/tests/orchestrator-rdd-ownership.test.ts +3 -3
- package/tests/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +252 -115
- package/tests/packed-runner-owned-path.test.ts +46 -0
- package/tests/persona-single-channel.test.ts +6 -6
- package/tests/provider-defect-handoff.test.ts +3 -11
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +409 -184
- package/tests/rdd-aware-verification-contract.test.ts +76 -1
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +176 -12
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +298 -0
- package/tests/review-contract-prompt.test.ts +108 -43
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-routing.test.ts +611 -5
- package/tests/review-controller-workspace-root.test.ts +163 -4
- package/tests/review-host-relay-routing.test.ts +338 -2
- package/tests/review-integration-v2-forward.test.ts +200 -0
- package/tests/review-ledger-contract.test.ts +10 -34
- package/tests/review-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +498 -6
- package/tests/review-sidebar-state.test.ts +402 -0
- package/tests/run-test-suite.test.ts +124 -0
- package/tests/runtime-harness.mjs +145 -786
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/selection-engine.test.ts +421 -0
- package/tests/session-messaging-grants.test.ts +255 -0
- package/tests/session-worktree-registry.test.ts +77 -0
- package/tests/shell-bar.test.ts +382 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +52 -0
- package/tests/shell-prompt.test.ts +94 -2
- package/tests/shell-sidebar-layout.test.ts +325 -21
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +87 -1
- package/tests/shell-usage-view.test.ts +27 -0
- package/tests/shell-usage.test.ts +73 -0
- package/tests/skill-registry.test.ts +50 -1
- package/tests/startup-banner.test.ts +130 -2
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -0
- package/tests/telemetry-trigger.test.ts +81 -20
- package/tests/theme-customization.test.ts +72 -0
- package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +110 -0
- package/tests/visual-customize-view.test.ts +418 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/yolo-customize.test.ts +256 -0
- package/tests/yolo-mode-runtime.test.ts +161 -0
- package/tests/yolo-mode.test.ts +261 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
package/lib/shell-bar.ts
CHANGED
|
@@ -2,7 +2,13 @@ import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works
|
|
|
2
2
|
import { GAUGE_CELLS, gaugeTone, paintGauge, renderGauge, type GaugeTone } from "./shell-gauge.ts";
|
|
3
3
|
import { renderUsageBar, selectUsageLimit, type ProviderUsage, type UsageWindow } from "./shell-usage.ts";
|
|
4
4
|
import { sanitizeTerminalText } from "./terminal-theme.ts";
|
|
5
|
-
import { CARD_TONE,
|
|
5
|
+
import { CARD_TONE, floatRows, panelInnerWidth, renderCard } from "./shell-card.ts";
|
|
6
|
+
import { REVIEW_SCOPE_UNAVAILABLE, REVIEW_SIDEBAR_LABELS, type ReviewSidebarSnapshot } from "./review-sidebar-state.ts";
|
|
7
|
+
import type { VisualSettings } from "./visual-customization-policy.ts";
|
|
8
|
+
import { renderChangesWidget, type ChangesModel } from "./shell-changes.ts";
|
|
9
|
+
|
|
10
|
+
type Presentation = Pick<VisualSettings, "density" | "visibility">;
|
|
11
|
+
type HeaderPresentation = Presentation & Partial<Pick<VisualSettings, "headerPlacement" | "statusPlacement">>;
|
|
6
12
|
|
|
7
13
|
export { gaugeTone, renderGauge, type GaugeTone };
|
|
8
14
|
|
|
@@ -11,6 +17,7 @@ export { gaugeTone, renderGauge, type GaugeTone };
|
|
|
11
17
|
// verified without a live TUI.
|
|
12
18
|
|
|
13
19
|
export interface ShellBarModel {
|
|
20
|
+
review?: ReviewSidebarSnapshot;
|
|
14
21
|
profile?: string;
|
|
15
22
|
changes?: { files: number; added: number; deleted: number; notice?: string };
|
|
16
23
|
cwd: string;
|
|
@@ -64,6 +71,12 @@ export interface ShellHeaderResult {
|
|
|
64
71
|
usageSpan?: HeaderUsageSpan;
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
export interface ShellHeaderChrome {
|
|
75
|
+
rows: string[];
|
|
76
|
+
headerRow: number;
|
|
77
|
+
usageSpan?: HeaderUsageSpan;
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
export interface ShellBarTheme {
|
|
68
81
|
fg(color: string, text: string): string;
|
|
69
82
|
bold(text: string): string;
|
|
@@ -105,7 +118,7 @@ export function formatTokens(count: number): string {
|
|
|
105
118
|
return `${Math.round(count / 1_000_000)}M`;
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
export function formatCost(total: number, subscription
|
|
121
|
+
export function formatCost(total: number, subscription = false): string {
|
|
109
122
|
const amount = total >= 1 ? total.toFixed(2) : total.toFixed(3);
|
|
110
123
|
return subscription ? `$${amount} sub` : `$${amount}`;
|
|
111
124
|
}
|
|
@@ -141,14 +154,20 @@ function costSegment(costTotal: number, subscription: boolean, theme: ShellBarTh
|
|
|
141
154
|
return theme.fg(ROLE.VALUE, formatCost(costTotal, subscription));
|
|
142
155
|
}
|
|
143
156
|
|
|
144
|
-
function buildSegments(model: ShellBarModel, theme: ShellBarTheme): string[] {
|
|
157
|
+
function buildSegments(model: ShellBarModel, theme: ShellBarTheme, presentation?: Presentation): string[] {
|
|
145
158
|
const location = locationSegment(model, theme);
|
|
146
159
|
const modelSegment = executionSegment(model.modelId, model.effort, theme);
|
|
147
160
|
const context = contextSegment(model.contextPercent, theme);
|
|
148
161
|
const cost = costSegment(model.costTotal, model.subscription, theme);
|
|
149
162
|
const usage = model.usage ? renderUsageBar(model.usage, theme, model.modelId) : undefined;
|
|
150
163
|
const statuses = model.statuses.map((status) => theme.fg(ROLE.STATUS, sanitizeStatus(status)));
|
|
151
|
-
return [
|
|
164
|
+
return [
|
|
165
|
+
...(presentation?.density === "minimal" ? [] : [theme.fg(ROLE.BRAND, SHELL_BAR_BRAND)]),
|
|
166
|
+
location,
|
|
167
|
+
...(presentation?.visibility.modelDetails === false ? [] : [modelSegment]),
|
|
168
|
+
...(presentation?.visibility.usageCost === false ? [] : [context, cost, ...(usage ? [usage] : [])]),
|
|
169
|
+
...statuses,
|
|
170
|
+
];
|
|
152
171
|
}
|
|
153
172
|
|
|
154
173
|
// When the line overflows, the location gives way first: the path shrinks to
|
|
@@ -177,14 +196,14 @@ function joinSegments(segments: string[], theme: ShellBarTheme): string {
|
|
|
177
196
|
|
|
178
197
|
// Sidebar groups use structured fields, never positional compact-bar segments
|
|
179
198
|
// or inferred meanings from opaque extension status strings.
|
|
180
|
-
export function renderShellSidebarBar(model: ShellBarModel, theme: ShellBarTheme, width: number): string[] {
|
|
199
|
+
export function renderShellSidebarBar(model: ShellBarModel, theme: ShellBarTheme, width: number, presentation?: Presentation): string[] {
|
|
181
200
|
const value = (text: string) => theme.fg(ROLE.VALUE, theme.bold(text));
|
|
182
201
|
const label = (text: string) => theme.fg(ROLE.LABEL, text);
|
|
183
202
|
const changes = model.changes;
|
|
184
203
|
const branch = model.branch ? `${label("Branch")} ${value(model.branch)}` : "";
|
|
185
204
|
// Pre-wrap values before indenting so Unicode/ANSI continuation lines keep
|
|
186
205
|
// the same inset without consuming the card's right border.
|
|
187
|
-
const innerWidth =
|
|
206
|
+
const innerWidth = panelInnerWidth(theme, width);
|
|
188
207
|
const inset = Math.min(1, innerWidth - 1);
|
|
189
208
|
// Model, effort, context, cost, and the per-model usage table now live in
|
|
190
209
|
// the always-visible header row (and /gentle:usage for the full table);
|
|
@@ -200,7 +219,7 @@ export function renderShellSidebarBar(model: ShellBarModel, theme: ShellBarTheme
|
|
|
200
219
|
...(model.profile ? [`${label("Profile")} ${value(sanitizeStatus(model.profile))}`] : []),
|
|
201
220
|
],
|
|
202
221
|
},
|
|
203
|
-
{
|
|
222
|
+
...(presentation?.visibility.changes === false ? [] : [{
|
|
204
223
|
title: "Changes",
|
|
205
224
|
lines: [
|
|
206
225
|
changes?.files
|
|
@@ -209,7 +228,15 @@ export function renderShellSidebarBar(model: ShellBarModel, theme: ShellBarTheme
|
|
|
209
228
|
...(changes?.notice ? [theme.fg("warning", sanitizeStatus(changes.notice))] : []),
|
|
210
229
|
label("/gentle:changes"),
|
|
211
230
|
],
|
|
212
|
-
},
|
|
231
|
+
}]),
|
|
232
|
+
...(model.review && presentation?.visibility.rdd !== false ? [{
|
|
233
|
+
title: "🌹 RDD",
|
|
234
|
+
lines: [
|
|
235
|
+
value(REVIEW_SIDEBAR_LABELS[model.review.state]),
|
|
236
|
+
// Unknown scope is an internal sentinel, not something the user acts on.
|
|
237
|
+
...(model.review.scope !== REVIEW_SCOPE_UNAVAILABLE ? [label(sanitizeStatus(model.review.scope))] : []),
|
|
238
|
+
],
|
|
239
|
+
}] : []),
|
|
213
240
|
{ title: "Integrations", lines: model.statuses.length
|
|
214
241
|
? model.statuses.map((status) => theme.fg(ROLE.STATUS, sanitizeStatus(status)))
|
|
215
242
|
: [label("No status reported")] },
|
|
@@ -217,11 +244,11 @@ export function renderShellSidebarBar(model: ShellBarModel, theme: ShellBarTheme
|
|
|
217
244
|
// Wrap and indent every group line before it reaches the card, so Unicode and
|
|
218
245
|
// ANSI continuation lines keep the same inset without consuming the right border.
|
|
219
246
|
const body = groups.flatMap((group, index) => [
|
|
220
|
-
...(index ? [""] : []),
|
|
221
|
-
label(group.title),
|
|
247
|
+
...(index && (!presentation || presentation.density === "comfortable") ? [""] : []),
|
|
248
|
+
...(presentation?.density === "minimal" ? [] : [label(group.title)]),
|
|
222
249
|
...group.lines.flatMap((line) => wrapTextWithAnsi(line, innerWidth - inset).map((part) => " ".repeat(inset) + part)),
|
|
223
250
|
]);
|
|
224
|
-
return renderCard({ title: "Status", body, tone: CARD_TONE.INFO }, theme, width, { expanded: true });
|
|
251
|
+
return renderCard({ title: "Status", body, tone: CARD_TONE.INFO }, theme, width, { expanded: true, panel: true });
|
|
225
252
|
}
|
|
226
253
|
|
|
227
254
|
const HEADER_BRAND = "✿ Gentle Shell";
|
|
@@ -230,9 +257,10 @@ const HEADER_BRAND = "✿ Gentle Shell";
|
|
|
230
257
|
// effort, then the whole location keeps the brand and the bare model id
|
|
231
258
|
// alive as long as anything can still share the row with the right-aligned
|
|
232
259
|
// counters.
|
|
233
|
-
function headerLeftStages(model: ShellHeaderModel, theme: ShellBarTheme): string[][] {
|
|
260
|
+
function headerLeftStages(model: ShellHeaderModel, theme: ShellBarTheme, showModelDetails: boolean): string[][] {
|
|
234
261
|
const brand = theme.fg(ROLE.BRAND, theme.bold(HEADER_BRAND));
|
|
235
262
|
const location = locationSegment(model, theme);
|
|
263
|
+
if (!showModelDetails) return [[brand, location], [brand]];
|
|
236
264
|
const withEffort = executionSegment(model.modelId, model.effort, theme);
|
|
237
265
|
const modelOnly = executionSegment(model.modelId, undefined, theme);
|
|
238
266
|
const withProfile = model.profile ? `${withEffort} ${theme.fg(ROLE.LABEL, "·")} ${theme.fg(ROLE.MODEL, sanitizeStatus(model.profile))}` : withEffort;
|
|
@@ -271,12 +299,72 @@ function usageSegmentText(windows: UsageWindow[], theme: ShellBarTheme, stage: U
|
|
|
271
299
|
return hint ? `${head} ${theme.fg(ROLE.LABEL, "·")} ${theme.fg(USAGE_HINT_ROLE, hint)}` : head;
|
|
272
300
|
}
|
|
273
301
|
|
|
274
|
-
|
|
302
|
+
// The float header is a full-width INFO background bar with a two-column
|
|
303
|
+
// inset on each side, closed below by the header edge line. floatRows paints
|
|
304
|
+
// the background inside one-column transparent margins; those margin cells
|
|
305
|
+
// are painted too, so the bar reaches both edges. Undefined for neon, a width
|
|
306
|
+
// under the float minimum, or a theme without a background.
|
|
307
|
+
function floatHeaderRow(theme: ShellBarTheme, width: number, content: (width: number) => string): string | undefined {
|
|
308
|
+
const painted = floatHeaderPaint(theme, width, content);
|
|
309
|
+
if (painted === undefined) return undefined;
|
|
310
|
+
const { inner, open } = painted;
|
|
311
|
+
// The background spans the full width: the margin cells are painted too, so
|
|
312
|
+
// the bar reaches both edges with no frame on its sides.
|
|
313
|
+
const side = `${open} `;
|
|
314
|
+
return `${side}${inner.slice(open.length, -BG_RESET.length)}${side}${BG_RESET}`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function fitHeaderContent(text: string, width: number): string {
|
|
318
|
+
const clipped = visibleWidth(text) <= width ? text : truncateToWidth(text, width, "…");
|
|
319
|
+
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function renderShellHeaderBar(model: ShellHeaderModel, theme: ShellBarTheme, width: number, usageHint?: string, presentation?: Presentation): ShellHeaderResult {
|
|
323
|
+
const targetWidth = Math.max(0, Math.floor(width));
|
|
324
|
+
let content: ShellHeaderResult | undefined;
|
|
325
|
+
let contentWidth = targetWidth;
|
|
326
|
+
const row = floatHeaderRow(theme, targetWidth, (inner) => {
|
|
327
|
+
contentWidth = inner;
|
|
328
|
+
content = headerContent(model, theme, inner, usageHint, presentation);
|
|
329
|
+
return content.text;
|
|
330
|
+
});
|
|
331
|
+
if (row === undefined || !content) return headerContent(model, theme, width, usageHint, presentation);
|
|
332
|
+
const { usageSpan } = content;
|
|
333
|
+
// The inset splits evenly around the content, so its left share is where
|
|
334
|
+
// the content's columns start.
|
|
335
|
+
const offset = (targetWidth - contentWidth) / 2;
|
|
336
|
+
return usageSpan ? { text: row, usageSpan: { start: usageSpan.start + offset, end: usageSpan.end + offset } } : { text: row };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Shared geometry for the rail header and the below-editor header widget. */
|
|
340
|
+
export function renderShellHeaderChrome(model: ShellHeaderModel, theme: ShellBarTheme, width: number, usageHint?: string, presentation?: HeaderPresentation): ShellHeaderChrome {
|
|
341
|
+
const { text, usageSpan } = renderShellHeaderBar(model, theme, width, usageHint, presentation);
|
|
342
|
+
const targetWidth = Math.max(0, Math.floor(width));
|
|
343
|
+
const painted = floatHeaderPaint(theme, targetWidth, () => "");
|
|
344
|
+
const rule = renderShellHeaderRule(theme, targetWidth);
|
|
345
|
+
if (painted === undefined) return { rows: [text, rule], headerRow: 0, usageSpan };
|
|
346
|
+
const padding = `${painted.open}${" ".repeat(targetWidth)}${BG_RESET}`;
|
|
347
|
+
if (presentation?.headerPlacement === "below-input") {
|
|
348
|
+
// A lower one-eighth block hugs the painted padding in the next row.
|
|
349
|
+
const upperEdge = theme.fg(HEADER_RULE_ROLE, "▁".repeat(targetWidth));
|
|
350
|
+
return { rows: [upperEdge, padding, text, padding], headerRow: 2, usageSpan };
|
|
351
|
+
}
|
|
352
|
+
return { rows: [padding, text, padding, rule], headerRow: 1, usageSpan };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** The usage segment is interactive only on the content row, never padding or the edge. */
|
|
356
|
+
export function shellHeaderUsageHit(chrome: ShellHeaderChrome, x: number, y: number): boolean {
|
|
357
|
+
const { usageSpan } = chrome;
|
|
358
|
+
return y === chrome.headerRow && usageSpan !== undefined && x >= usageSpan.start && x < usageSpan.end;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function headerContent(model: ShellHeaderModel, theme: ShellBarTheme, width: number, usageHint?: string, presentation?: Presentation): ShellHeaderResult {
|
|
275
362
|
const targetWidth = Math.max(0, Math.floor(width));
|
|
276
363
|
const ctxCost = joinSegments([contextSegment(model.contextPercent, theme), costSegment(model.costTotal, model.subscription, theme)], theme);
|
|
277
364
|
const windows = model.usage ? (selectUsageLimit(model.usage, model.modelId)?.windows ?? []) : [];
|
|
278
|
-
const leftStages = headerLeftStages(model, theme)
|
|
279
|
-
|
|
365
|
+
const leftStages = headerLeftStages(model, theme, presentation?.visibility.modelDetails !== false)
|
|
366
|
+
.map((stage) => presentation?.density === "minimal" ? stage.slice(1) : stage);
|
|
367
|
+
const minimalLeft = Math.max(0, leftStages.length - 2); // brand + bare model id, before dropping the model too
|
|
280
368
|
// One flat, ordered cascade — never a per-stage nested search — so the
|
|
281
369
|
// left group fully degrades (profile → effort → location) before usage
|
|
282
370
|
// ever gives anything up, and usage fully degrades (gauges → secondary
|
|
@@ -285,26 +373,69 @@ export function renderShellHeaderBar(model: ShellHeaderModel, theme: ShellBarThe
|
|
|
285
373
|
// Unlike the compact bar's usage meter (hidden with no data), this is a
|
|
286
374
|
// standing, clickable affordance — even with nothing to report it still
|
|
287
375
|
// reads "usage" plus the shortcut hint, unless disabled, width permitting.
|
|
288
|
-
const usageStages: Array<UsageStage | undefined> = ["full", "text", "primary", undefined];
|
|
376
|
+
const usageStages: Array<UsageStage | undefined> = presentation?.visibility.usageCost === false ? [undefined] : ["full", "text", "primary", undefined];
|
|
289
377
|
const attempts: Array<{ leftIndex: number; usageStage: UsageStage | undefined }> = [
|
|
290
|
-
...leftStages.slice(0, minimalLeft).map((_, leftIndex) => ({ leftIndex, usageStage:
|
|
378
|
+
...leftStages.slice(0, minimalLeft).map((_, leftIndex) => ({ leftIndex, usageStage: usageStages[0] })),
|
|
291
379
|
...usageStages.map((usageStage) => ({ leftIndex: minimalLeft, usageStage })),
|
|
292
380
|
{ leftIndex: leftStages.length - 1, usageStage: undefined },
|
|
293
381
|
];
|
|
294
382
|
for (const { leftIndex, usageStage } of attempts) {
|
|
295
383
|
const usageText = usageStage ? usageSegmentText(windows, theme, usageStage, usageHint) : undefined;
|
|
296
|
-
const right = usageText ? joinSegments([ctxCost, usageText], theme) : ctxCost;
|
|
384
|
+
const right = presentation?.visibility.usageCost === false ? "" : usageText ? joinSegments([ctxCost, usageText], theme) : ctxCost;
|
|
297
385
|
const left = joinSegments(leftStages[leftIndex]!, theme);
|
|
298
|
-
if (visibleWidth(left) + RIGHT_PADDING + visibleWidth(right) > targetWidth) continue;
|
|
386
|
+
if (visibleWidth(left) + (right ? RIGHT_PADDING : 0) + visibleWidth(right) > targetWidth) continue;
|
|
299
387
|
const text = left + " ".repeat(targetWidth - visibleWidth(left) - visibleWidth(right)) + right;
|
|
300
388
|
if (!usageText) return { text };
|
|
301
389
|
const usageStart = visibleWidth(left) + (targetWidth - visibleWidth(left) - visibleWidth(right)) + visibleWidth(ctxCost) + visibleWidth(` ${SHELL_BAR_SEPARATOR} `);
|
|
302
390
|
return { text, usageSpan: { start: usageStart, end: usageStart + visibleWidth(usageText) } };
|
|
303
391
|
}
|
|
304
|
-
const brand = theme.fg(ROLE.BRAND, theme.bold(HEADER_BRAND));
|
|
392
|
+
const brand = presentation?.density === "minimal" ? "" : theme.fg(ROLE.BRAND, theme.bold(HEADER_BRAND));
|
|
305
393
|
return { text: visibleWidth(brand) <= targetWidth ? brand : "" };
|
|
306
394
|
}
|
|
307
395
|
|
|
396
|
+
// A single owner supplies real captured filenames, not ShellBarModel's totals.
|
|
397
|
+
// Undefined keeps every legacy surface alive for style/width/theme transitions.
|
|
398
|
+
export function renderShellBelowInputFloat(model: ShellBarModel, theme: ShellBarTheme, width: number, usageHint?: string, presentation?: HeaderPresentation, changes?: ChangesModel): ShellHeaderChrome | undefined {
|
|
399
|
+
if (presentation?.headerPlacement !== "below-input") return undefined;
|
|
400
|
+
const targetWidth = Math.max(0, Math.floor(width));
|
|
401
|
+
if (floatHeaderPaint(theme, targetWidth, () => "") === undefined) return undefined;
|
|
402
|
+
const chrome = renderShellHeaderChrome(buildShellHeaderModel(model), theme, targetWidth, usageHint, presentation);
|
|
403
|
+
const changesRow = changes?.files.length && presentation.visibility.changes !== false
|
|
404
|
+
? floatHeaderRow(theme, targetWidth, (inner) => renderChangesWidget(changes, theme, inner)[0] ?? "")
|
|
405
|
+
: undefined;
|
|
406
|
+
const statuses = presentation.statusPlacement === "hidden" ? [] : model.statuses.map(sanitizeStatus).filter(Boolean);
|
|
407
|
+
const statusRow = statuses.length
|
|
408
|
+
? floatHeaderRow(theme, targetWidth, () => joinSegments(statuses.map((status) => theme.fg(ROLE.STATUS, status)), theme))
|
|
409
|
+
: undefined;
|
|
410
|
+
return {
|
|
411
|
+
rows: [
|
|
412
|
+
chrome.rows[0]!,
|
|
413
|
+
chrome.rows[1]!,
|
|
414
|
+
...(changesRow ? [changesRow] : []),
|
|
415
|
+
chrome.rows[2]!,
|
|
416
|
+
...(statusRow ? [statusRow] : []),
|
|
417
|
+
chrome.rows[3]!,
|
|
418
|
+
],
|
|
419
|
+
headerRow: changesRow ? 3 : 2,
|
|
420
|
+
usageSpan: chrome.usageSpan,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// The bottom bar when it is the only status row of a narrow fullscreen
|
|
425
|
+
// terminal (the header sits below the input and steps aside). It reuses the
|
|
426
|
+
// header row's own cascade, so context, cost and usage outlive the location
|
|
427
|
+
// on small screens, and keeps extension statuses on a second line instead of
|
|
428
|
+
// dropping them the way the header deliberately does.
|
|
429
|
+
export function renderShellBottomOnlyBar(model: ShellBarModel, theme: ShellBarTheme, width: number, usageHint?: string, presentation?: HeaderPresentation, changes?: ChangesModel): string[] {
|
|
430
|
+
const grouped = renderShellBelowInputFloat(model, theme, width, usageHint, presentation, changes);
|
|
431
|
+
if (grouped) return grouped.rows;
|
|
432
|
+
const headerModel = buildShellHeaderModel(model);
|
|
433
|
+
// Neon, missing backgrounds and sub-minimum widths keep the old row count.
|
|
434
|
+
const rows = [headerContent(headerModel, theme, width, usageHint, presentation).text];
|
|
435
|
+
const statuses = model.statuses.map(sanitizeStatus).filter((status) => status.length > 0).map((status) => theme.fg(ROLE.STATUS, status));
|
|
436
|
+
return statuses.length ? [...rows, truncateToWidth(joinSegments(statuses, theme), Math.max(0, Math.floor(width)), "…")] : rows;
|
|
437
|
+
}
|
|
438
|
+
|
|
308
439
|
// The rule row painted directly under the header bar: one full-width horizontal
|
|
309
440
|
// line in the same theme role as the editor frame (PROMPT_FRAME_ROLE in
|
|
310
441
|
// extensions/gentle-shell.ts), so the status row and the prompt read as one
|
|
@@ -313,13 +444,37 @@ export function renderShellHeaderBar(model: ShellHeaderModel, theme: ShellBarThe
|
|
|
313
444
|
// with it.
|
|
314
445
|
const HEADER_RULE_CHAR = "─";
|
|
315
446
|
const HEADER_RULE_ROLE = "border";
|
|
447
|
+
const HEADER_EDGE_CHAR = "▔";
|
|
448
|
+
const BG_RESET = "\x1b[49m";
|
|
449
|
+
|
|
450
|
+
// Paints the header content with floatRows and returns the row without its
|
|
451
|
+
// transparent margins plus the background opener. The painted row is
|
|
452
|
+
// `<open> <content> <reset>`; the opener has no spaces, so the first space ends
|
|
453
|
+
// it. Undefined when the float style does not apply.
|
|
454
|
+
function floatHeaderPaint(theme: ShellBarTheme, width: number, content: (width: number) => string): { inner: string; open: string } | undefined {
|
|
455
|
+
let floated = false;
|
|
456
|
+
const [row] = floatRows(CARD_TONE.INFO, theme, width, (inner) => {
|
|
457
|
+
floated = inner !== width;
|
|
458
|
+
return floated ? { body: [` ${fitHeaderContent(content(inner - 2), inner - 2)} `] } : {};
|
|
459
|
+
});
|
|
460
|
+
if (!floated || row === undefined) return undefined;
|
|
461
|
+
const inner = row.slice(1, -1);
|
|
462
|
+
return { inner, open: inner.slice(0, inner.indexOf(" ")) };
|
|
463
|
+
}
|
|
316
464
|
|
|
465
|
+
// In the float style the rule closes the hanging header tab with an upper
|
|
466
|
+
// one-eighth block line. Box-drawing lines sit mid-cell: a transparent `└──┘`
|
|
467
|
+
// leaves half an unpainted row under the tab, and a painted one overshoots it.
|
|
468
|
+
// `▔` hugs the top of its cell, so the line touches the tab's background
|
|
469
|
+
// exactly. The edge stays transparent beneath the painted bottom padding.
|
|
317
470
|
export function renderShellHeaderRule(theme: ShellBarTheme, width: number): string {
|
|
318
|
-
|
|
471
|
+
const targetWidth = Math.max(0, Math.floor(width));
|
|
472
|
+
if (floatHeaderPaint(theme, targetWidth, () => "") !== undefined) return theme.fg(HEADER_RULE_ROLE, HEADER_EDGE_CHAR.repeat(targetWidth));
|
|
473
|
+
return theme.fg(HEADER_RULE_ROLE, HEADER_RULE_CHAR.repeat(targetWidth));
|
|
319
474
|
}
|
|
320
475
|
|
|
321
|
-
export function renderShellBar(model: ShellBarModel, theme: ShellBarTheme, width: number): string[] {
|
|
322
|
-
let segments = buildSegments(model, theme);
|
|
476
|
+
export function renderShellBar(model: ShellBarModel, theme: ShellBarTheme, width: number, presentation?: Presentation): string[] {
|
|
477
|
+
let segments = buildSegments(model, theme, presentation);
|
|
323
478
|
const right = model.sessionName ? theme.fg(ROLE.SESSION, model.sessionName) : undefined;
|
|
324
479
|
|
|
325
480
|
let left = joinSegments(segments, theme);
|
|
@@ -329,7 +484,7 @@ export function renderShellBar(model: ShellBarModel, theme: ShellBarTheme, width
|
|
|
329
484
|
}
|
|
330
485
|
|
|
331
486
|
if (visibleWidth(left) > width) {
|
|
332
|
-
segments = buildSegments(compactModel(model), theme);
|
|
487
|
+
segments = buildSegments(compactModel(model), theme, presentation);
|
|
333
488
|
left = joinSegments(segments, theme);
|
|
334
489
|
}
|
|
335
490
|
while (segments.length > 1 && visibleWidth(left) > width) {
|