@pellux/goodvibes-tui 1.16.1 → 1.18.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.
Files changed (257) hide show
  1. package/CHANGELOG.md +138 -9
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +5490 -1806
  4. package/package.json +7 -7
  5. package/src/cli/completions/generate.ts +5 -4
  6. package/src/cli/config-overrides.ts +51 -11
  7. package/src/cli/doctor.ts +4 -5
  8. package/src/cli/entrypoint.ts +13 -8
  9. package/src/cli/help.ts +7 -6
  10. package/src/cli/launch-auto-update.ts +214 -0
  11. package/src/cli/management-commands.ts +82 -15
  12. package/src/cli/management.ts +0 -2
  13. package/src/cli/parser.ts +5 -0
  14. package/src/cli/status.ts +13 -0
  15. package/src/cli/surface-command.ts +5 -5
  16. package/src/cli/tui-startup.ts +60 -97
  17. package/src/cli/types.ts +2 -0
  18. package/src/config/index.ts +1 -1
  19. package/src/config/tui-extension-settings.ts +33 -0
  20. package/src/core/composer-state.ts +1 -1
  21. package/src/core/consolidation-receipt.ts +41 -0
  22. package/src/core/conversation-line-cache.ts +3 -3
  23. package/src/core/conversation-rendering.ts +18 -9
  24. package/src/core/conversation.ts +2 -2
  25. package/src/core/long-task-notifier.ts +7 -7
  26. package/src/core/memory-provenance.ts +67 -0
  27. package/src/core/pairing-devices.ts +49 -0
  28. package/src/core/pairing-handoff.ts +101 -0
  29. package/src/core/pairing-offers.ts +83 -0
  30. package/src/core/pairing-origin.ts +78 -0
  31. package/src/core/pairing-tailscale-gateway.ts +56 -0
  32. package/src/core/power-chip-source.ts +91 -0
  33. package/src/core/power-status.ts +95 -0
  34. package/src/core/stable-host.ts +171 -0
  35. package/src/core/stream-event-wiring.ts +29 -6
  36. package/src/core/system-message-noise.ts +1 -1
  37. package/src/core/system-message-router.ts +6 -6
  38. package/src/core/turn-budget-outcome.ts +60 -0
  39. package/src/core/turn-cancellation.ts +40 -1
  40. package/src/core/turn-event-wiring.ts +14 -8
  41. package/src/daemon/cli.ts +64 -58
  42. package/src/daemon/lifecycle.ts +41 -0
  43. package/src/daemon/service-commands.ts +5 -5
  44. package/src/export/cost-utils.ts +128 -6
  45. package/src/input/autocomplete.ts +2 -2
  46. package/src/input/command-registry.ts +55 -9
  47. package/src/input/commands/acp-runtime.ts +72 -0
  48. package/src/input/commands/ci-runtime.ts +5 -1
  49. package/src/input/commands/codebase-runtime.ts +6 -6
  50. package/src/input/commands/cost-runtime.ts +1 -1
  51. package/src/input/commands/devices-runtime.ts +109 -0
  52. package/src/input/commands/git-runtime.ts +28 -4
  53. package/src/input/commands/graph-runtime.ts +45 -0
  54. package/src/input/commands/health-runtime.ts +2 -2
  55. package/src/input/commands/intelligence-runtime.ts +1 -1
  56. package/src/input/commands/local-auth-runtime.ts +1 -1
  57. package/src/input/commands/local-provider-runtime.ts +49 -10
  58. package/src/input/commands/local-runtime.ts +53 -5
  59. package/src/input/commands/memory.ts +2 -2
  60. package/src/input/commands/operator-panel-runtime.ts +5 -5
  61. package/src/input/commands/operator-rpc.ts +32 -11
  62. package/src/input/commands/permissions-provenance.ts +1 -1
  63. package/src/input/commands/permissions-runtime.ts +99 -8
  64. package/src/input/commands/planning-runtime.ts +3 -3
  65. package/src/input/commands/platform-sandbox-runtime.ts +1 -1
  66. package/src/input/commands/power-runtime.ts +56 -0
  67. package/src/input/commands/provider-accounts-runtime.ts +71 -6
  68. package/src/input/commands/provider.ts +7 -5
  69. package/src/input/commands/qrcode-runtime.ts +14 -19
  70. package/src/input/commands/queue-runtime.ts +70 -0
  71. package/src/input/commands/recall-files-git.ts +13 -51
  72. package/src/input/commands/recall-files.ts +8 -11
  73. package/src/input/commands/recall-review.ts +2 -2
  74. package/src/input/commands/relay-runtime.ts +1 -1
  75. package/src/input/commands/remote-runtime.ts +1 -1
  76. package/src/input/commands/runtime-services.ts +4 -4
  77. package/src/input/commands/schedule-runtime.ts +14 -7
  78. package/src/input/commands/services-runtime.ts +1 -1
  79. package/src/input/commands/session-workflow.ts +2 -2
  80. package/src/input/commands/settings-sync-runtime.ts +2 -2
  81. package/src/input/commands/share-runtime.ts +1 -1
  82. package/src/input/commands/shell-core.ts +46 -68
  83. package/src/input/commands/subscription-runtime.ts +1 -1
  84. package/src/input/commands/update-runtime.ts +153 -96
  85. package/src/input/commands/work-plan-runtime.ts +1 -1
  86. package/src/input/commands/workstream-attempts.ts +6 -1
  87. package/src/input/commands/workstream-runtime.ts +46 -27
  88. package/src/input/commands/worktree-runtime.ts +35 -8
  89. package/src/input/commands.ts +10 -2
  90. package/src/input/config-modal-types.ts +14 -3
  91. package/src/input/config-modal.ts +75 -13
  92. package/src/input/feature-unit-layout.ts +93 -82
  93. package/src/input/feed-context-factory.ts +2 -2
  94. package/src/input/handler-command-route.ts +2 -2
  95. package/src/input/handler-content-actions.ts +1 -1
  96. package/src/input/handler-feed-routes.ts +7 -7
  97. package/src/input/handler-feed.ts +4 -4
  98. package/src/input/handler-interactions.ts +3 -2
  99. package/src/input/handler-modal-routes.ts +12 -4
  100. package/src/input/handler-modal-token-routes.ts +1 -1
  101. package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
  102. package/src/input/handler-onboarding.ts +3 -2
  103. package/src/input/handler-picker-routes.ts +26 -13
  104. package/src/input/handler-shortcuts.ts +29 -6
  105. package/src/input/handler.ts +2 -2
  106. package/src/input/keybindings.ts +21 -3
  107. package/src/input/onboarding/onboarding-feature-units.ts +34 -27
  108. package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
  109. package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
  110. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
  111. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
  112. package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
  113. package/src/input/panel-integration-actions.ts +2 -2
  114. package/src/input/panel-mouse-geometry.ts +1 -1
  115. package/src/input/panel-paste-flood-guard.ts +1 -1
  116. package/src/input/provider-key-intake.ts +167 -0
  117. package/src/input/sandbox-exec-config.ts +2 -2
  118. package/src/input/selection-modal.ts +1 -1
  119. package/src/input/settings-modal-activation.ts +5 -0
  120. package/src/input/settings-modal-adjustment.ts +4 -31
  121. package/src/input/settings-modal-data.ts +52 -57
  122. package/src/input/settings-modal-mutations.ts +12 -68
  123. package/src/input/settings-modal-types.ts +27 -17
  124. package/src/input/settings-modal.ts +74 -55
  125. package/src/main.ts +90 -90
  126. package/src/panels/agent-inspector-shared.ts +4 -4
  127. package/src/panels/base-panel.ts +2 -2
  128. package/src/panels/builtin/agent.ts +4 -4
  129. package/src/panels/builtin/development.ts +4 -4
  130. package/src/panels/builtin/knowledge.ts +1 -1
  131. package/src/panels/builtin/operations.ts +83 -11
  132. package/src/panels/builtin/session.ts +5 -5
  133. package/src/panels/builtin/shared.ts +16 -4
  134. package/src/panels/builtin-modals.ts +59 -42
  135. package/src/panels/builtin-panels.ts +1 -1
  136. package/src/panels/cost-tracker-panel.ts +124 -7
  137. package/src/panels/diff-panel.ts +39 -6
  138. package/src/panels/eval-registry.ts +2 -2
  139. package/src/panels/fleet-acts.ts +446 -0
  140. package/src/panels/fleet-deep-link.ts +1 -1
  141. package/src/panels/fleet-gateway.ts +117 -0
  142. package/src/panels/fleet-observed-render.ts +128 -0
  143. package/src/panels/fleet-panel-format.ts +163 -11
  144. package/src/panels/fleet-panel-worktree-detail.ts +14 -0
  145. package/src/panels/fleet-panel.ts +73 -57
  146. package/src/panels/fleet-read-model.ts +84 -14
  147. package/src/panels/fleet-spawn.ts +266 -0
  148. package/src/panels/fleet-steer.ts +2 -2
  149. package/src/panels/fleet-stop.ts +41 -5
  150. package/src/panels/fleet-tabs.ts +15 -16
  151. package/src/panels/fleet-transcript.ts +5 -5
  152. package/src/panels/git-panel.ts +2 -2
  153. package/src/panels/index.ts +4 -1
  154. package/src/panels/local-auth-panel.ts +1 -1
  155. package/src/panels/memory-consolidation-gateway.ts +94 -0
  156. package/src/panels/modals/devices-modal.ts +161 -0
  157. package/src/panels/modals/hooks-modal.ts +1 -1
  158. package/src/panels/modals/keybindings-modal.ts +2 -2
  159. package/src/panels/modals/knowledge-modal.ts +1 -1
  160. package/src/panels/modals/marketplace-modal.ts +4 -4
  161. package/src/panels/modals/memory-modal.ts +153 -9
  162. package/src/panels/modals/modal-surface-helpers.ts +1 -1
  163. package/src/panels/modals/modal-theme.ts +1 -1
  164. package/src/panels/modals/pairing-modal.ts +198 -68
  165. package/src/panels/modals/planning-modal.ts +2 -2
  166. package/src/panels/modals/plugins-modal.ts +1 -1
  167. package/src/panels/modals/policy-modal.ts +1 -1
  168. package/src/panels/modals/security-modal.ts +7 -1
  169. package/src/panels/modals/skills-modal.ts +1 -1
  170. package/src/panels/modals/work-plan-modal.ts +3 -3
  171. package/src/panels/notifications-feed.ts +127 -0
  172. package/src/panels/notifications-panel.ts +160 -0
  173. package/src/panels/panel-manager.ts +9 -9
  174. package/src/panels/plugins-panel.ts +4 -4
  175. package/src/panels/project-planning-answer-actions.ts +1 -1
  176. package/src/panels/provider-health-routes.ts +2 -2
  177. package/src/panels/skills-panel.ts +4 -5
  178. package/src/panels/token-budget-panel.ts +11 -3
  179. package/src/panels/types.ts +5 -5
  180. package/src/panels/workstream-graph-render.ts +123 -0
  181. package/src/permissions/broker-approval-card.ts +201 -0
  182. package/src/permissions/hunk-selection.ts +28 -4
  183. package/src/permissions/prompt.ts +244 -29
  184. package/src/renderer/autocomplete-overlay.ts +107 -54
  185. package/src/renderer/code-block.ts +2 -2
  186. package/src/renderer/compaction-history-modal.ts +1 -1
  187. package/src/renderer/compaction-preview.ts +1 -1
  188. package/src/renderer/compaction-quality.ts +1 -1
  189. package/src/renderer/composer-fragments.ts +69 -0
  190. package/src/renderer/compositor.ts +2 -2
  191. package/src/renderer/config-modal.ts +1 -1
  192. package/src/renderer/conversation-overlays.ts +1 -1
  193. package/src/renderer/footer-tips.ts +3 -3
  194. package/src/renderer/hint-grammar.ts +1 -1
  195. package/src/renderer/modal-factory.ts +1 -1
  196. package/src/renderer/model-picker-overlay.ts +1 -1
  197. package/src/renderer/model-workspace.ts +7 -2
  198. package/src/renderer/panel-composite.ts +2 -2
  199. package/src/renderer/panel-workspace-bar.ts +1 -1
  200. package/src/renderer/process-indicator.ts +1 -1
  201. package/src/renderer/selection-modal-overlay.ts +1 -1
  202. package/src/renderer/session-picker-modal.ts +2 -2
  203. package/src/renderer/settings-modal-helpers.ts +23 -5
  204. package/src/renderer/settings-modal.ts +129 -39
  205. package/src/renderer/shell-surface.ts +6 -0
  206. package/src/renderer/terminal-bg-probe.ts +1 -1
  207. package/src/renderer/theme.ts +3 -3
  208. package/src/renderer/tool-call.ts +13 -7
  209. package/src/renderer/tool-result-summary.ts +1 -1
  210. package/src/renderer/turn-injection.ts +7 -6
  211. package/src/renderer/ui-factory.ts +38 -1
  212. package/src/runtime/bootstrap-command-context.ts +7 -2
  213. package/src/runtime/bootstrap-command-parts.ts +9 -5
  214. package/src/runtime/bootstrap-core.ts +41 -41
  215. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  216. package/src/runtime/bootstrap-shell.ts +13 -5
  217. package/src/runtime/bootstrap.ts +34 -32
  218. package/src/runtime/channel-composition.ts +46 -0
  219. package/src/runtime/code-index-services.ts +3 -4
  220. package/src/runtime/daemon-attach-notices.ts +120 -0
  221. package/src/runtime/durability-services.ts +90 -0
  222. package/src/runtime/embedded-service-factories.ts +40 -0
  223. package/src/runtime/feature-settings.ts +147 -0
  224. package/src/runtime/fleet-services.ts +41 -0
  225. package/src/runtime/idle-power-services.ts +85 -0
  226. package/src/runtime/index.ts +6 -21
  227. package/src/runtime/interaction-seams.ts +109 -0
  228. package/src/runtime/legacy-daemon-migration.ts +29 -19
  229. package/src/runtime/memory-fold.ts +1 -1
  230. package/src/runtime/notification-dispatch.ts +110 -0
  231. package/src/runtime/onboarding/apply.ts +3 -29
  232. package/src/runtime/onboarding/fast-path.ts +39 -6
  233. package/src/runtime/onboarding/provider-key-capture.ts +126 -0
  234. package/src/runtime/onboarding/snapshot.ts +0 -1
  235. package/src/runtime/onboarding/types.ts +9 -3
  236. package/src/runtime/power-keepawake-remote.ts +75 -0
  237. package/src/runtime/process-lifecycle.ts +3 -1
  238. package/src/runtime/provider-fallback.ts +55 -0
  239. package/src/runtime/provider-optimizer-wiring.ts +55 -0
  240. package/src/runtime/release-artifacts.ts +19 -103
  241. package/src/runtime/resume-notice.ts +1 -1
  242. package/src/runtime/services.ts +113 -113
  243. package/src/runtime/session-continuity-hints.ts +38 -0
  244. package/src/runtime/surface-feature-flags.ts +40 -9
  245. package/src/runtime/terminal-output-guard.ts +2 -2
  246. package/src/runtime/trust/workspace-registration.ts +10 -2
  247. package/src/runtime/trust/workspace-trust.ts +58 -31
  248. package/src/runtime/ui-services.ts +6 -1
  249. package/src/runtime/update-check.ts +15 -69
  250. package/src/runtime/workstream-services.ts +2 -2
  251. package/src/shell/blocking-input.ts +108 -132
  252. package/src/shell/recovery-input-helpers.ts +53 -166
  253. package/src/shell/ui-openers.ts +38 -6
  254. package/src/utils/format-duration.ts +1 -1
  255. package/src/verification/verification-ledger.ts +21 -4
  256. package/src/version.ts +1 -1
  257. package/src/input/commands/flags-runtime.ts +0 -328
@@ -22,15 +22,15 @@ import {
22
22
  DEFAULT_PANEL_PALETTE,
23
23
  type PanelWorkspaceSection,
24
24
  } from './polish.ts';
25
- import { calcSessionCost, isModelPriced, computeBudgetBreach, readBudgetAlertUsd, BUDGET_ALERT_USD_CONFIG_KEY, type BudgetAlertConfigAccess } from '../export/cost-utils.ts';
25
+ import { calcSessionCost, describePricingSource, isModelPriced, computeBudgetBreach, readBudgetAlertUsd, writeManualModelPrice, BUDGET_ALERT_USD_CONFIG_KEY, type BudgetAlertConfigAccess } from '../export/cost-utils.ts';
26
26
  import { abbreviateCount } from '../utils/format-number.ts';
27
27
  import { isTextBackspace } from '../input/delete-key-policy.ts';
28
28
 
29
29
  // Pricing lookups are provided by ../export/cost-utils.ts (single source of truth).
30
30
 
31
- /** unpriced=true renders an honest "unpriced" marker instead of a $0.00 that could be mistaken for a real zero cost. */
31
+ /** unpriced=true renders the explicit "price unknown" marker instead of a $0.00 that could be mistaken for a real zero cost. */
32
32
  function formatCost(usd: number, unpriced = false): string {
33
- if (unpriced) return 'unpriced';
33
+ if (unpriced) return 'price unknown';
34
34
  if (usd === 0) return '$0.00';
35
35
  if (usd < 0.0001) return '<$0.0001';
36
36
  if (usd < 0.01) return `$${usd.toFixed(4)}`;
@@ -127,6 +127,12 @@ export class CostTrackerPanel extends BasePanel {
127
127
  // secret so it is echoed directly.
128
128
  private budgetEntry: string | null = null;
129
129
 
130
+ // Draft buffer for the in-panel manual-price entry mode ('p' key): the one
131
+ // action away from any price display (and from the "price unknown" marker).
132
+ // Format: "input,output" in USD per 1M tokens; persists to the
133
+ // pricing.modelPrices settings key live on Enter.
134
+ private priceEntry: string | null = null;
135
+
130
136
  // Scroll offset for agent list
131
137
  private scrollOffset = 0;
132
138
 
@@ -347,11 +353,12 @@ export class CostTrackerPanel extends BasePanel {
347
353
  * `Panel.isCapturingTextBurst`.
348
354
  */
349
355
  isCapturingTextBurst(): boolean {
350
- return this.budgetEntry !== null;
356
+ return this.budgetEntry !== null || this.priceEntry !== null;
351
357
  }
352
358
 
353
359
  handleInput(key: string): boolean {
354
360
  if (this.budgetEntry !== null) return this.handleBudgetEntryInput(key);
361
+ if (this.priceEntry !== null) return this.handlePriceEntryInput(key);
355
362
 
356
363
  if (key === 'b') {
357
364
  const threshold = this.currentBudgetThreshold();
@@ -360,6 +367,16 @@ export class CostTrackerPanel extends BasePanel {
360
367
  return true;
361
368
  }
362
369
 
370
+ if (key === 'p') {
371
+ // One action from any price display: edit the manual price for the
372
+ // session's provider:model. Needs a wired config (production path) —
373
+ // without one the key falls through rather than opening a dead editor.
374
+ if (!this.configAccess) return false;
375
+ this.priceEntry = '';
376
+ this.markDirty();
377
+ return true;
378
+ }
379
+
363
380
  switch (key) {
364
381
  case 'up': return this.scroll(-1);
365
382
  case 'down': return this.scroll(1);
@@ -402,6 +419,52 @@ export class CostTrackerPanel extends BasePanel {
402
419
  return true; // absorb everything else while entry is active
403
420
  }
404
421
 
422
+ /** True when the session model can carry a manual price (pricing.modelPrices keys are `provider:model`). */
423
+ private canEditSessionModelPrice(): boolean {
424
+ return this.sessionModel !== 'unknown' && this.sessionModel.includes(':');
425
+ }
426
+
427
+ private handlePriceEntryInput(key: string): boolean {
428
+ if (key === 'escape') {
429
+ this.priceEntry = null;
430
+ this.markDirty();
431
+ return true;
432
+ }
433
+ if (key === 'enter' || key === 'return') {
434
+ const raw = (this.priceEntry ?? '').trim();
435
+ if (!this.canEditSessionModelPrice()) {
436
+ // The prompt already explains why; Enter just closes it.
437
+ this.priceEntry = null;
438
+ this.markDirty();
439
+ return true;
440
+ }
441
+ const parts = raw.split(/[\s,]+/).filter((part) => part.length > 0);
442
+ const input = Number(parts[0]);
443
+ const output = Number(parts[1]);
444
+ if (parts.length === 2 && Number.isFinite(input) && input >= 0 && Number.isFinite(output) && output >= 0) {
445
+ writeManualModelPrice(this.configAccess!, this.sessionModel, { input, output });
446
+ this.priceEntry = null;
447
+ }
448
+ // Malformed input keeps the editor open so the draft can be corrected.
449
+ this.markDirty();
450
+ return true;
451
+ }
452
+ if (isTextBackspace(key)) {
453
+ if (this.priceEntry && this.priceEntry.length > 0) {
454
+ this.priceEntry = this.priceEntry.slice(0, -1);
455
+ this.markDirty();
456
+ }
457
+ return true;
458
+ }
459
+ // Two USD amounts separated by a comma (or space) — digits, dots, comma, space.
460
+ if (key.length === 1 && /[0-9., ]/.test(key)) {
461
+ this.priceEntry = (this.priceEntry ?? '') + key;
462
+ this.markDirty();
463
+ return true;
464
+ }
465
+ return true; // absorb everything else while entry is active
466
+ }
467
+
405
468
  /**
406
469
  * Scroll the agent list. Over-consumption fix: only absorbs the key (and
407
470
  * only advances) when the agent list is actually long enough to scroll —
@@ -427,20 +490,29 @@ export class CostTrackerPanel extends BasePanel {
427
490
  render(width: number, height: number): Line[] {
428
491
  if (height <= 0 || width <= 0) return [];
429
492
  if (this.budgetEntry !== null) return this.renderBudgetEntryPrompt(width, height);
493
+ if (this.priceEntry !== null) return this.renderPriceEntryPrompt(width, height);
430
494
 
431
495
  const totalInputTokens = this.sessionUsage.input + this.sessionUsage.cacheRead + this.sessionUsage.cacheWrite;
432
496
  const sessionCost = calcSessionCost(this.sessionUsage.input, this.sessionUsage.output, this.sessionUsage.cacheRead, this.sessionUsage.cacheWrite, this.sessionModel);
433
497
  const budgetThreshold = this.currentBudgetThreshold();
434
498
  const overBudget = computeBudgetBreach(sessionCost, budgetThreshold);
435
499
  const sparkline = buildSparkline(this.costHistory);
436
- const costStr = formatCost(sessionCost, !isModelPriced(this.sessionModel));
500
+ const sessionPriced = isModelPriced(this.sessionModel);
501
+ const costStr = formatCost(sessionCost, !sessionPriced);
437
502
  const costFg = overBudget ? C.bad : C.cost;
503
+ // Dollars carry their source: "your price" for a manual entry,
504
+ // "catalog price, as of <date>" for the dated catalog, etc. The
505
+ // "price unknown" marker carries the one-key action to fix it instead.
506
+ const sourceDescription = sessionPriced ? describePricingSource(this.sessionModel) : null;
507
+ const sourceStr = sessionPriced
508
+ ? (sourceDescription ? ` (${sourceDescription})` : '')
509
+ : ' — press p to set a price';
438
510
  const budgetStr = budgetThreshold > 0
439
511
  ? ` / ${formatCost(budgetThreshold)}`
440
512
  : '';
441
513
  const alertStr = overBudget ? ' ! OVER BUDGET' : '';
442
514
  const sessionLines: Line[] = [
443
- this.renderKeyValue(width, ' Total', `${costStr}${budgetStr}${alertStr}`, costFg),
515
+ this.renderKeyValue(width, ' Total', `${costStr}${budgetStr}${alertStr}${sourceStr}`, costFg),
444
516
  ];
445
517
  // Budget meter — the single most important glance for this panel: how much
446
518
  // of the configured budget the session has consumed. Only shown when a
@@ -455,6 +527,13 @@ export class CostTrackerPanel extends BasePanel {
455
527
  { filled: meterFg, empty: C.separator, label: C.label },
456
528
  { prefix: ' Budget [', suffix: `] ${pctStr}` },
457
529
  ));
530
+ // Unpriced-spend honesty wherever budget state shows: a meter over a
531
+ // partially-unpriced session must say the % undercounts real spend.
532
+ const anyUnpricedSpend = (!sessionPriced && (totalInputTokens > 0 || this.sessionUsage.output > 0))
533
+ || Array.from(this.agents.values()).some((agent) => agent.inputTokens > 0 && !isModelPriced(agent.model));
534
+ if (anyUnpricedSpend) {
535
+ sessionLines.push(this.renderKeyValue(width, '', 'some spend has no known price and is not counted above', C.warn));
536
+ }
458
537
  }
459
538
  if (sparkline.length > 0) sessionLines.push(this.renderLabeledLine(width, ' Trend', sparkline, C.good));
460
539
  sessionLines.push(this.renderKeyValue(width, ' Input', formatTokens(this.sessionUsage.input), C.label));
@@ -481,9 +560,11 @@ export class CostTrackerPanel extends BasePanel {
481
560
  { keys: 'Up/Down', label: 'scroll agents' },
482
561
  { keys: 'PgUp/PgDn', label: 'page' },
483
562
  { keys: 'b', label: 'set budget' },
563
+ { keys: 'p', label: 'set model price' },
484
564
  ], DEFAULT_PANEL_PALETTE)
485
565
  : buildKeyboardHints(width, [
486
566
  { keys: 'b', label: 'set budget alert' },
567
+ { keys: 'p', label: 'set model price' },
487
568
  ], DEFAULT_PANEL_PALETTE);
488
569
  if (agentList.length > 0) {
489
570
  const planCost = agentList.reduce((sum, a) => sum + a.cost, 0);
@@ -562,7 +643,7 @@ export class CostTrackerPanel extends BasePanel {
562
643
  width,
563
644
  ' No agents spawned this session',
564
645
  'Agent-level cost estimates appear here once delegated or background agents start running.',
565
- // WO-160: 'b' already opens the in-panel budget-entry field (see
646
+ // 'b' already opens the in-panel budget-entry field (see
566
647
  // handleInput/handleBudgetEntryInput above) and is advertised in
567
648
  // the footer's 'b: set budget' hint, so the printed
568
649
  // '/cost budget <usd>' command was a redundant action substitute.
@@ -626,4 +707,40 @@ export class CostTrackerPanel extends BasePanel {
626
707
  return workspace.slice(0, height);
627
708
  }
628
709
 
710
+ // Non-masked entry for the 'p' manual-price key. Two USD-per-1M-token
711
+ // amounts (input,output); persists to pricing.modelPrices for the session's
712
+ // provider:model live. A model without a provider prefix cannot carry a
713
+ // manual price (the settings key is keyed provider:model) — the prompt says
714
+ // so instead of accepting a value it could not store.
715
+ private renderPriceEntryPrompt(width: number, height: number): Line[] {
716
+ const editable = this.canEditSessionModelPrice();
717
+ const draft = this.priceEntry ?? '';
718
+ const display = `${draft}█`;
719
+ const promptLines: Line[] = editable
720
+ ? [
721
+ buildPanelLine(width, [[` Set your manual price for ${this.sessionModel} (USD per 1M tokens).`, C.label]]),
722
+ buildPanelLine(width, [[' Format: input,output — for example 3.00,15.00', C.dim]]),
723
+ buildPanelLine(width, [['', C.label]]),
724
+ buildPanelLine(width, [[' Price ', C.label], [display, C.cost]]),
725
+ buildPanelLine(width, [['', C.label]]),
726
+ buildPanelLine(width, [[' [Enter] Save [Esc] Cancel [Backspace] Delete char', C.dim]]),
727
+ ]
728
+ : [
729
+ buildPanelLine(width, [[` The current model (${this.sessionModel}) has no provider prefix, so a`, C.label]]),
730
+ buildPanelLine(width, [[' manual price cannot be stored for it (prices are keyed provider:model).', C.label]]),
731
+ buildPanelLine(width, [['', C.label]]),
732
+ buildPanelLine(width, [[' [Esc] Close', C.dim]]),
733
+ ];
734
+ const workspace = buildPanelWorkspace(width, height, {
735
+ title: ' Cost Tracker — Model Price',
736
+ intro: editable
737
+ ? 'Your price wins over provider and catalog prices, immediately and everywhere.'
738
+ : 'Manual prices are stored per provider:model.',
739
+ sections: [{ lines: promptLines }],
740
+ palette: DEFAULT_PANEL_PALETTE,
741
+ });
742
+ while (workspace.length < height) workspace.push(createEmptyLine(width));
743
+ return workspace.slice(0, height);
744
+ }
745
+
629
746
  }
@@ -5,7 +5,7 @@
5
5
  import type { Line } from '../types/grid.ts';
6
6
  import { createStyledCell, createEmptyLine } from '../types/grid.ts';
7
7
  import { truncateDisplay, getDisplayWidth } from '../utils/terminal-width.ts';
8
- import { GitService } from '@pellux/goodvibes-sdk/platform/git';
8
+ import { GitService, type StructuredDiff, type StructuredDiffFile } from '@pellux/goodvibes-sdk/platform/git';
9
9
  import { BasePanel } from './base-panel.ts';
10
10
  import { UI_TONES, DIFF_TONES } from '../renderer/ui-primitives.ts';
11
11
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
@@ -27,10 +27,10 @@ import {
27
27
  // that shares it, including the workspace-chrome aliases (info/dim/value/
28
28
  // empty) so the raw-hex count never grows. The title band itself is NOT
29
29
  // overridden — buildPanelWorkspace always falls back to the canonical
30
- // DEFAULT_PANEL_PALETTE.headerBg (WO-002: one title band everywhere).
30
+ // DEFAULT_PANEL_PALETTE.headerBg (one title band everywhere).
31
31
  // ---------------------------------------------------------------------------
32
32
 
33
- // Hunk blue is the shared DIFF_TONES token (WO-204) — diff-view.ts (conversation)
33
+ // Hunk blue is the shared DIFF_TONES token — diff-view.ts (conversation)
34
34
  // and git-panel.ts's inline diff converge onto this file's pre-existing value.
35
35
  const HUNK_BLUE: string = DIFF_TONES.hunk;
36
36
  // Context rows and line-number gutter use the shared theme's muted/dim
@@ -39,7 +39,7 @@ const CONTEXT_GRAY = UI_TONES.fg.muted;
39
39
  const FILENAME_WHITE = '#ffffff';
40
40
  // Add/del text colors are the shared DIFF_TONES tokens, whose values ARE this
41
41
  // panel's shipped colors — this panel is the reference diff look (diff-view
42
- // was unwired dead code until WO-204, so "majority of surfaces" was a mirage);
42
+ // was unwired dead code until, so "majority of surfaces" was a mirage);
43
43
  // the conversation surface converges onto these, not the reverse.
44
44
  const ADD_GREEN: string = DIFF_TONES.add;
45
45
  const ADD_BG = '#001a0d';
@@ -199,6 +199,19 @@ function extractDiffFilePath(trimmed: string): string {
199
199
  return 'unknown';
200
200
  }
201
201
 
202
+ /** Reconstruct one file's unified-diff text from a StructuredDiffFile (header + hunks, prefixes re-attached; no size cap). */
203
+ function reconstructStructuredFile(file: StructuredDiffFile): string {
204
+ const out: string[] = [...file.headerLines];
205
+ for (const hunk of file.hunks) {
206
+ out.push(hunk.header);
207
+ for (const line of hunk.lines) {
208
+ const prefix = line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : ' ';
209
+ out.push(`${prefix}${line.text}`);
210
+ }
211
+ }
212
+ return out.join('\n');
213
+ }
214
+
202
215
  function splitIntoDiffEntries(raw: string): DiffEntry[] {
203
216
  const entries: DiffEntry[] = [];
204
217
  // Split on "diff --git" / "diff --cc" / "diff --combined" lines
@@ -306,6 +319,26 @@ export class DiffPanel extends BasePanel {
306
319
  this.markDirty();
307
320
  }
308
321
 
322
+ /**
323
+ * Ingest a StructuredDiff (GitService.diffStructured — the FULL, uncapped
324
+ * working-tree diff). Each file becomes one DiffEntry (unified text
325
+ * reconstructed) so the existing tabs/renderer work unchanged; the old
326
+ * 4,000-char slice is gone and a diff of any length renders complete.
327
+ */
328
+ loadStructuredDiff(diff: StructuredDiff): void {
329
+ this.entries = diff.files.map((file) => {
330
+ const raw = reconstructStructuredFile(file);
331
+ return {
332
+ filePath: file.newPath ?? file.oldPath ?? 'unknown',
333
+ raw,
334
+ lines: parseDiff(raw),
335
+ };
336
+ });
337
+ this.selectedFile = 0;
338
+ this.scrollOffset = 0;
339
+ this.markDirty();
340
+ }
341
+
309
342
  /**
310
343
  * Defensive gate mirroring GitPanel's own notGitRepo messaging — short-
311
344
  * circuits before any of this panel's self-load git spawns run, instead of
@@ -521,7 +554,7 @@ export class DiffPanel extends BasePanel {
521
554
  }
522
555
  }
523
556
 
524
- // W6.1 (the purge): 'o' used to open the currently selected file in the
557
+ // (the purge): 'o' used to open the currently selected file in the
525
558
  // preview panel via a staged pendingOpenPreview flag + a
526
559
  // handlePanelIntegrationAction cross-panel hook (the same bridge
527
560
  // FileExplorerPanel used). 'preview' is DELETE-disposition with no
@@ -597,7 +630,7 @@ export class DiffPanel extends BasePanel {
597
630
  width,
598
631
  ' No diff to display.',
599
632
  'Load a git diff or select a changed file to populate the workspace. w=working h=HEAD s=staged self-load right here.',
600
- // WO-160: corrected — bare /diff loads files changed *this
633
+ // corrected — bare /diff loads files changed *this
601
634
  // session* (falling back to the full HEAD diff when none are
602
635
  // tracked yet), not the working-tree diff the w key already
603
636
  // loads in-panel; the old summary text described the wrong
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * EvalRegistry — holds the latest evaluation harness run state.
3
3
  *
4
- * W6.1 (the purge): extracted out of eval-panel.ts. 'eval' was
4
+ * (the purge): extracted out of eval-panel.ts. 'eval' was
5
5
  * DELETE-disposition (no surviving human surface — the evaluation harness
6
6
  * is driven and reviewed via the `/eval` CLI command, input/commands/eval.ts)
7
7
  * but this registry is still a live consumer of `/eval run` output, so it
8
8
  * survives as a standalone read-model rather than being deleted with the
9
- * view. See .goodvibes/audit/2026-07-04-wave6-briefs.json (W6.1).
9
+ * view. See .goodvibes/audit/2026-07-04-wave6-briefs.json.
10
10
  */
11
11
 
12
12
  import type { EvalSuiteResult, EvalGateResult } from '@/runtime/index.ts';