@pellux/goodvibes-agent 1.4.4 → 1.5.1

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 (230) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +52 -7
  3. package/dist/package/main.js +7491 -12253
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +1 -1
  6. package/docs/getting-started.md +2 -2
  7. package/docs/release-and-publishing.md +1 -1
  8. package/docs/tools-and-commands.md +5 -5
  9. package/package.json +4 -2
  10. package/release/live-verification/live-verification.json +13 -13
  11. package/release/live-verification/live-verification.md +15 -15
  12. package/release/performance-snapshot.json +2 -2
  13. package/release/release-notes.md +5 -7
  14. package/release/release-readiness.json +6 -6
  15. package/src/agent/behavior-discovery-summary.ts +4 -18
  16. package/src/agent/calendar-registry.ts +322 -0
  17. package/src/agent/competitive-feature-inventory.ts +268 -130
  18. package/src/agent/document-registry.ts +5 -1
  19. package/src/agent/email/email-service.ts +350 -0
  20. package/src/agent/email/imap-client.ts +596 -0
  21. package/src/agent/email/smtp-client.ts +453 -0
  22. package/src/agent/ics-calendar.ts +662 -0
  23. package/src/agent/ics-timezone.ts +172 -0
  24. package/src/agent/markdown-frontmatter.ts +31 -0
  25. package/src/agent/memory-safety.ts +1 -1
  26. package/src/agent/note-registry.ts +3 -9
  27. package/src/agent/persona-discovery.ts +3 -15
  28. package/src/agent/persona-registry.ts +1 -10
  29. package/src/agent/research-source-registry.ts +5 -1
  30. package/src/agent/routine-discovery.ts +3 -15
  31. package/src/agent/runtime-profile.ts +3 -0
  32. package/src/agent/skill-discovery.ts +3 -15
  33. package/src/agent/skill-draft-proposer.ts +203 -0
  34. package/src/agent/skill-draft-runner.ts +201 -0
  35. package/src/agent/skill-registry.ts +36 -1
  36. package/src/agent/skill-standard.ts +99 -0
  37. package/src/agent/vibe-file.ts +7 -22
  38. package/src/cli/completion.ts +1 -1
  39. package/src/cli/config-overrides.ts +10 -1
  40. package/src/cli/redaction.ts +17 -5
  41. package/src/config/provider-model.ts +2 -1
  42. package/src/config/secret-config.ts +13 -6
  43. package/src/core/activity-feed.ts +97 -0
  44. package/src/core/away-digest.ts +161 -0
  45. package/src/core/conversation-rendering.ts +7 -3
  46. package/src/core/conversation.ts +22 -15
  47. package/src/core/hardware-profile.ts +362 -0
  48. package/src/core/last-seen-store.ts +78 -0
  49. package/src/core/plain-language.ts +52 -0
  50. package/src/core/setup-incomplete-hint.ts +90 -0
  51. package/src/core/system-message-router.ts +38 -87
  52. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  53. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  54. package/src/input/agent-workspace-categories.ts +40 -125
  55. package/src/input/agent-workspace-command-editor.ts +4 -0
  56. package/src/input/agent-workspace-host-category.ts +0 -5
  57. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  58. package/src/input/agent-workspace-navigation.ts +10 -3
  59. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  60. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  61. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  62. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  63. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  64. package/src/input/agent-workspace-profile-editors.ts +155 -0
  65. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  66. package/src/input/agent-workspace-types.ts +5 -1
  67. package/src/input/agent-workspace.ts +65 -39
  68. package/src/input/command-registry.ts +18 -5
  69. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  70. package/src/input/commands/agent-skills-runtime.ts +60 -3
  71. package/src/input/commands/calendar-runtime.ts +209 -0
  72. package/src/input/commands/channels-runtime.ts +1 -0
  73. package/src/input/commands/command-error.ts +9 -0
  74. package/src/input/commands/compat-runtime.ts +1 -0
  75. package/src/input/commands/config.ts +1 -0
  76. package/src/input/commands/conversation-runtime.ts +1 -1
  77. package/src/input/commands/delegation-runtime.ts +5 -6
  78. package/src/input/commands/email-runtime.ts +387 -0
  79. package/src/input/commands/experience-runtime.ts +17 -4
  80. package/src/input/commands/guidance-runtime.ts +1 -1
  81. package/src/input/commands/health-runtime.ts +6 -1
  82. package/src/input/commands/knowledge-format.ts +73 -0
  83. package/src/input/commands/knowledge.ts +9 -74
  84. package/src/input/commands/local-provider-runtime.ts +2 -1
  85. package/src/input/commands/local-runtime.ts +10 -4
  86. package/src/input/commands/mcp-runtime.ts +7 -0
  87. package/src/input/commands/notify-runtime.ts +17 -1
  88. package/src/input/commands/onboarding-runtime.ts +1 -0
  89. package/src/input/commands/operator-actions-runtime.ts +1 -0
  90. package/src/input/commands/operator-runtime.ts +3 -0
  91. package/src/input/commands/personas-runtime.ts +1 -0
  92. package/src/input/commands/platform-access-runtime.ts +40 -17
  93. package/src/input/commands/product-runtime.ts +6 -1
  94. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  95. package/src/input/commands/qrcode-runtime.ts +1 -0
  96. package/src/input/commands/routines-runtime.ts +1 -0
  97. package/src/input/commands/runtime-services.ts +0 -13
  98. package/src/input/commands/security-runtime.ts +1 -0
  99. package/src/input/commands/session-content.ts +1 -0
  100. package/src/input/commands/shell-core.ts +5 -2
  101. package/src/input/commands/subscription-runtime.ts +33 -9
  102. package/src/input/commands/support-bundle-runtime.ts +8 -1
  103. package/src/input/commands/tasks-runtime.ts +1 -0
  104. package/src/input/commands/tts-runtime.ts +1 -0
  105. package/src/input/commands/vibe-runtime.ts +1 -0
  106. package/src/input/commands.ts +4 -0
  107. package/src/input/feed-context-factory.ts +3 -12
  108. package/src/input/handler-command-route.ts +7 -60
  109. package/src/input/handler-feed-routes.ts +0 -194
  110. package/src/input/handler-feed.ts +2 -54
  111. package/src/input/handler-interactions.ts +0 -2
  112. package/src/input/handler-modal-stack.ts +0 -9
  113. package/src/input/handler-picker-routes.ts +24 -1
  114. package/src/input/handler-shortcuts.ts +11 -40
  115. package/src/input/handler-ui-state.ts +13 -0
  116. package/src/input/handler.ts +8 -35
  117. package/src/input/keybindings.ts +40 -17
  118. package/src/input/model-picker-local-fit.ts +130 -0
  119. package/src/input/shell-passthrough.ts +58 -0
  120. package/src/input/submission-router.ts +0 -5
  121. package/src/main.ts +129 -90
  122. package/src/renderer/activity-sidebar.ts +186 -0
  123. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  124. package/src/renderer/agent-workspace-style.ts +1 -1
  125. package/src/renderer/agent-workspace.ts +14 -2
  126. package/src/renderer/compositor.ts +37 -125
  127. package/src/renderer/conversation-overlays.ts +3 -3
  128. package/src/renderer/help-overlay.ts +7 -5
  129. package/src/renderer/model-workspace.ts +101 -86
  130. package/src/{panels → renderer}/polish.ts +3 -3
  131. package/src/renderer/shell-surface.ts +4 -5
  132. package/src/renderer/status-token.ts +31 -11
  133. package/src/renderer/tab-strip.ts +1 -1
  134. package/src/renderer/tool-call.ts +7 -8
  135. package/src/renderer/tool-labels.ts +92 -0
  136. package/src/renderer/ui-factory.ts +48 -96
  137. package/src/runtime/bootstrap-command-context.ts +0 -5
  138. package/src/runtime/bootstrap-command-parts.ts +6 -15
  139. package/src/runtime/bootstrap-core.ts +34 -13
  140. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  141. package/src/runtime/bootstrap-shell.ts +3 -50
  142. package/src/runtime/bootstrap.ts +3 -4
  143. package/src/runtime/context.ts +1 -1
  144. package/src/runtime/diagnostics/panels/index.ts +0 -1
  145. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  146. package/src/runtime/execution-ledger.ts +16 -1
  147. package/src/runtime/index.ts +6 -1
  148. package/src/runtime/onboarding/index.ts +1 -0
  149. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  150. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  151. package/src/runtime/services.ts +16 -4
  152. package/src/runtime/terminal-output-guard.ts +7 -0
  153. package/src/runtime/tool-permission-safety.ts +1 -1
  154. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  155. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  156. package/src/runtime/ui/model-picker/types.ts +2 -2
  157. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  158. package/src/runtime/ui/provider-health/types.ts +2 -2
  159. package/src/runtime/ui-services.ts +0 -2
  160. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  161. package/src/shell/autonomy-surfacing.ts +272 -0
  162. package/src/shell/session-continuity-hints.ts +0 -6
  163. package/src/shell/startup-wiring.ts +221 -0
  164. package/src/shell/ui-openers.ts +18 -29
  165. package/src/tools/agent-context-policy.ts +1 -1
  166. package/src/tools/agent-harness-background-processes.ts +4 -4
  167. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  168. package/src/tools/agent-harness-command-runner.ts +6 -1
  169. package/src/tools/agent-harness-document-ops.ts +26 -53
  170. package/src/tools/agent-harness-execution-history.ts +1 -13
  171. package/src/tools/agent-harness-execution-posture.ts +0 -15
  172. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  173. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  174. package/src/tools/agent-harness-metadata.ts +16 -0
  175. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  176. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  177. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  178. package/src/tools/agent-harness-tool-schema.ts +13 -14
  179. package/src/tools/agent-harness-tool.ts +27 -23
  180. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  181. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  182. package/src/tools/agent-workspace-tool.ts +2 -33
  183. package/src/utils/terminal-width.ts +9 -3
  184. package/src/version.ts +1 -1
  185. package/src/input/agent-workspace-panel-route.ts +0 -44
  186. package/src/input/panel-integration-actions.ts +0 -26
  187. package/src/panels/approval-panel.ts +0 -149
  188. package/src/panels/automation-control-panel.ts +0 -212
  189. package/src/panels/base-panel.ts +0 -254
  190. package/src/panels/builtin/agent.ts +0 -58
  191. package/src/panels/builtin/knowledge.ts +0 -26
  192. package/src/panels/builtin/operations.ts +0 -121
  193. package/src/panels/builtin/session.ts +0 -138
  194. package/src/panels/builtin/shared.ts +0 -275
  195. package/src/panels/builtin/usage.ts +0 -21
  196. package/src/panels/builtin-panels.ts +0 -23
  197. package/src/panels/confirm-state.ts +0 -61
  198. package/src/panels/context-visualizer-panel.ts +0 -204
  199. package/src/panels/cost-tracker-panel.ts +0 -444
  200. package/src/panels/docs-panel.ts +0 -285
  201. package/src/panels/index.ts +0 -25
  202. package/src/panels/knowledge-panel.ts +0 -417
  203. package/src/panels/memory-panel.ts +0 -226
  204. package/src/panels/panel-list-panel.ts +0 -464
  205. package/src/panels/panel-manager.ts +0 -570
  206. package/src/panels/provider-accounts-panel.ts +0 -233
  207. package/src/panels/provider-health-domains.ts +0 -208
  208. package/src/panels/provider-health-panel.ts +0 -720
  209. package/src/panels/provider-health-tracker.ts +0 -115
  210. package/src/panels/provider-stats-panel.ts +0 -366
  211. package/src/panels/qr-panel.ts +0 -207
  212. package/src/panels/schedule-panel.ts +0 -321
  213. package/src/panels/scrollable-list-panel.ts +0 -491
  214. package/src/panels/search-focus.ts +0 -32
  215. package/src/panels/security-panel.ts +0 -295
  216. package/src/panels/session-browser-panel.ts +0 -395
  217. package/src/panels/session-maintenance.ts +0 -125
  218. package/src/panels/subscription-panel.ts +0 -263
  219. package/src/panels/system-messages-panel.ts +0 -230
  220. package/src/panels/tasks-panel.ts +0 -344
  221. package/src/panels/thinking-panel.ts +0 -304
  222. package/src/panels/token-budget-panel.ts +0 -475
  223. package/src/panels/tool-inspector-panel.ts +0 -436
  224. package/src/panels/types.ts +0 -54
  225. package/src/renderer/panel-composite.ts +0 -158
  226. package/src/renderer/panel-tab-bar.ts +0 -69
  227. package/src/renderer/panel-workspace-bar.ts +0 -42
  228. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  229. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  230. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  export const AGENT_HARNESS_MODES = [
2
- 'summary', 'modes', 'mode', 'route_decision', 'cli_commands', 'cli_command', 'panels', 'panel', 'open_panel',
2
+ 'summary', 'modes', 'mode', 'route_decision', 'cli_commands', 'cli_command',
3
3
  'ui_surfaces', 'ui_surface', 'open_ui_surface',
4
4
  'shortcuts', 'keybindings', 'keybinding', 'run_keybinding', 'set_keybinding', 'reset_keybinding',
5
5
  'commands', 'command', 'run_command', 'channels', 'channel', 'channel_setup_guide', 'channel_triage', 'channel_deliveries', 'notifications', 'notification_target',
@@ -32,6 +32,12 @@ export const AGENT_HARNESS_MODES = [
32
32
  'service_posture', 'service_endpoint',
33
33
  'connected_host', 'connected_host_status', 'connected_host_capability',
34
34
  'daemon', 'daemon_status',
35
+ 'propose_skill_drafts',
36
+ ] as const;
37
+
38
+ const BACKGROUND_PROCESS_ACTION_VALUES = [
39
+ 'start', 'spawn', 'run', 'stop', 'kill', 'cancel', 'wait', 'list',
40
+ 'status', 'poll', 'log', 'output', 'write', 'capabilities', 'doctor', 'parity',
35
41
  ] as const;
36
42
 
37
43
  const KEY_COMBO_PARAMETER_SCHEMA = {
@@ -42,10 +48,12 @@ const KEY_COMBO_PARAMETER_SCHEMA = {
42
48
  } as const;
43
49
 
44
50
  export const AGENT_HARNESS_PARAMETER_PROPERTIES = {
51
+ // The full mode catalog is intentionally NOT inlined as an enum:
52
+ // it would bloat every model prompt. Dispatch still validates against
53
+ // AGENT_HARNESS_MODES and unknown modes return suggestions.
45
54
  mode: {
46
55
  type: 'string',
47
- enum: AGENT_HARNESS_MODES,
48
- description: 'Harness operation. Start with summary or a plural catalog mode.',
56
+ description: 'Operation. Start with "summary"; mode:"modes" lists the catalog.',
49
57
  },
50
58
  query: {
51
59
  type: 'string',
@@ -131,12 +139,12 @@ export const AGENT_HARNESS_PARAMETER_PROPERTIES = {
131
139
  },
132
140
  processAction: {
133
141
  type: 'string',
134
- enum: ['start', 'spawn', 'run', 'stop', 'kill', 'cancel', 'wait', 'list', 'status', 'poll', 'log', 'output', 'write', 'capabilities', 'doctor', 'parity'],
142
+ enum: BACKGROUND_PROCESS_ACTION_VALUES,
135
143
  description: 'Background process lifecycle action or process-style alias.',
136
144
  },
137
145
  action: {
138
146
  type: 'string',
139
- enum: ['start', 'spawn', 'run', 'stop', 'kill', 'cancel', 'wait', 'list', 'status', 'poll', 'log', 'output', 'write', 'capabilities', 'doctor', 'parity'],
147
+ enum: BACKGROUND_PROCESS_ACTION_VALUES,
140
148
  description: 'Process-style action alias for run_background_process.',
141
149
  },
142
150
  cwd: {
@@ -216,10 +224,6 @@ export const AGENT_HARNESS_PARAMETER_PROPERTIES = {
216
224
  type: 'string',
217
225
  description: 'UI surface id for ui_surface or open_ui_surface modes.',
218
226
  },
219
- panelId: {
220
- type: 'string',
221
- description: 'Built-in panel id for panel or open_panel modes.',
222
- },
223
227
  actionId: {
224
228
  type: 'string',
225
229
  description: 'Workspace action id or keybinding action id.',
@@ -332,11 +336,6 @@ export const AGENT_HARNESS_PARAMETER_PROPERTIES = {
332
336
  type: 'number',
333
337
  description: 'Maximum catalog entries to return.',
334
338
  },
335
- pane: {
336
- type: 'string',
337
- enum: ['top', 'bottom'],
338
- description: 'Preferred pane for open_panel.',
339
- },
340
339
  confirm: {
341
340
  type: 'boolean',
342
341
  description: 'Required true for confirmed harness effects.',
@@ -8,11 +8,12 @@ import { channelReadinessCatalogStatus, describeHarnessChannel, describeHarnessC
8
8
  import { blockedHarnessCliCommandTokens, describeHarnessCliCommand, listHarnessCliCommands, totalHarnessCliCommands } from './agent-harness-cli-metadata.ts';
9
9
  import { describeHarnessCommand, listHarnessCommands } from './agent-harness-command-catalog.ts';
10
10
  import { describeLearningCandidate, learningCuratorCatalogStatus, learningCuratorSummary } from './agent-harness-learning-curator.ts';
11
+ import { runSkillDraftProposer } from '../agent/skill-draft-runner.ts';
12
+ import { AgentSkillRegistry } from '../agent/skill-registry.ts';
11
13
  import { delegationPostureCatalogStatus, delegationPostureSummary, describeHarnessDelegationRoute } from './agent-harness-delegation-posture.ts';
12
14
  import { describeHarnessKeybinding, listHarnessKeybindings, listHarnessShortcuts, resetHarnessKeybinding, runHarnessKeybinding, setHarnessKeybinding, totalHarnessKeybindings, totalHarnessShortcuts } from './agent-harness-keybinding-metadata.ts';
13
15
  import { describeHarnessMediaProvider, mediaPostureCatalogStatus, mediaPostureSummary } from './agent-harness-media-posture.ts';
14
16
  import { describeHarnessNotificationTarget, listHarnessNotificationTargets, notificationTargetCatalogStatus } from './agent-harness-notification-metadata.ts';
15
- import { describeHarnessPanel, listHarnessPanels, openHarnessPanel, totalHarnessPanels } from './agent-harness-panel-metadata.ts';
16
17
  import { connectedHostStatusSummary } from './agent-harness-connected-host-status.ts';
17
18
  import { backgroundProcessCatalogStatus, backgroundProcessSummary, describeBackgroundProcess, runBackgroundProcessAction } from './agent-harness-background-processes.ts';
18
19
  import { describeDocumentOpsLane, documentOpsCatalogStatus, documentOpsSummary } from './agent-harness-document-ops.ts';
@@ -81,7 +82,6 @@ function compactHarnessModeGuide(): Record<string, unknown> {
81
82
  function detailedHarnessModelAccessGuide(): Record<string, string> {
82
83
  return {
83
84
  cliCommands: 'Prefer workspace action:"cli_commands|cli_command" for CLI discovery. Lower-level cli command modes remain available.',
84
- panels: 'Prefer workspace action:"panels|panel|open_panel"; visible navigation needs confirm:true and explicitUserRequest.',
85
85
  uiSurfaces: 'Prefer workspace action:"surfaces|surface|open" for visible UI and computer action:"browser|open_browser" for browser/PWA. Lower-level UI modes remain available.',
86
86
  shortcuts: 'Prefer workspace action:"shortcuts|keybindings|keybinding|run_keybinding|set_keybinding|reset_keybinding"; effects need confirmation.',
87
87
  slashCommands: 'Prefer workspace action:"commands|command|run_command"; slash-command execution needs confirmation.',
@@ -131,7 +131,7 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
131
131
  return {
132
132
  definition: {
133
133
  name: 'agent_harness',
134
- description: 'Inspect or operate GoodVibes Agent harness surfaces.',
134
+ description: 'Inspect or operate Agent harness surfaces; mode:"modes" lists all.',
135
135
  parameters: {
136
136
  type: 'object',
137
137
  properties: AGENT_HARNESS_PARAMETER_PROPERTIES,
@@ -143,7 +143,13 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
143
143
  },
144
144
  execute: async (rawArgs) => {
145
145
  const args = rawArgs as AgentHarnessToolArgs;
146
- if (!isMode(args.mode)) return error(`Unknown agent_harness mode: ${String(args.mode)}`);
146
+ if (!isMode(args.mode)) {
147
+ const requested = String(args.mode).toLowerCase();
148
+ const suggestions = AGENT_HARNESS_MODES
149
+ .filter((mode) => mode.includes(requested) || requested.includes(mode))
150
+ .slice(0, 5);
151
+ return error(`Unknown agent_harness mode: ${String(args.mode)}. ${suggestions.length > 0 ? `Closest modes: ${suggestions.join(', ')}. ` : ''}Use mode:"modes" to list the full catalog.`);
152
+ }
147
153
  try {
148
154
  if (args.mode === 'summary') {
149
155
  const channelReadiness = channelReadinessCatalogStatus(deps.commandContext);
@@ -214,7 +220,6 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
214
220
  harnessModes: HARNESS_MODE_DESCRIPTORS.length,
215
221
  cliCommands: totalHarnessCliCommands(),
216
222
  blockedCliCommandTokens: blockedHarnessCliCommandTokens(),
217
- panels: totalHarnessPanels(deps.commandContext),
218
223
  uiSurfaces: totalHarnessUiSurfaces(),
219
224
  shortcuts: totalHarnessShortcuts(deps.commandContext),
220
225
  keybindings: totalHarnessKeybindings(deps.commandContext),
@@ -282,24 +287,6 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
282
287
  if (args.mode === 'cli_command') {
283
288
  return output(describeHarnessCliCommand(args));
284
289
  }
285
- if (args.mode === 'panels') {
286
- const panels = listHarnessPanels(deps.commandContext, args);
287
- return output({
288
- panels,
289
- returned: panels.length,
290
- total: totalHarnessPanels(deps.commandContext),
291
- policy: 'Panel modes expose Agent/TUI operator view catalog and open state. open_panel is confirmation-gated and routes through the current Agent operator surface.',
292
- });
293
- }
294
- if (args.mode === 'panel') {
295
- const panel = describeHarnessPanel(deps.commandContext, args);
296
- return panel ? output(panel) : error(`Unknown panel ${readString(args.panelId || args.target || args.query) || '<missing>'}.`);
297
- }
298
- if (args.mode === 'open_panel') {
299
- const confirmationError = requireConfirmedAction(args, 'Panel routing');
300
- if (confirmationError) return error(confirmationError);
301
- return output(openHarnessPanel(deps.commandContext, args));
302
- }
303
290
  if (args.mode === 'ui_surfaces') {
304
291
  const surfaces = listHarnessUiSurfaces(deps.commandContext, args);
305
292
  return output({ surfaces, returned: surfaces.length, total: totalHarnessUiSurfaces() });
@@ -717,6 +704,23 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
717
704
  includeParameters: args.includeParameters === true,
718
705
  }));
719
706
  }
707
+ if (args.mode === 'propose_skill_drafts') {
708
+ const confirmationError = requireConfirmedAction(args, 'Skill draft proposal');
709
+ if (confirmationError) return error(confirmationError);
710
+ const shellPaths = deps.commandContext.workspace?.shellPaths;
711
+ if (!shellPaths) return error('Skill draft proposal requires an active workspace.');
712
+ const registry = AgentSkillRegistry.fromShellPaths(shellPaths);
713
+ const result = runSkillDraftProposer(deps.commandContext, registry);
714
+ return output({
715
+ proposed: result.proposed,
716
+ skipped: result.skipped,
717
+ skillIds: result.skillIds,
718
+ message: result.proposed > 0
719
+ ? `Drafted ${result.proposed} skill${result.proposed !== 1 ? 's' : ''} for review. Find them under Memory > Skills with enabled:false.`
720
+ : 'No new skill drafts this pass.',
721
+ policy: 'Drafted skills are disabled and require review before use. Enable them under Memory > Skills.',
722
+ });
723
+ }
720
724
  return error(`Unhandled agent_harness mode: ${args.mode}`);
721
725
  } catch (err) {
722
726
  return error(formatHarnessError(err));
@@ -101,16 +101,6 @@ function openPanelWorkspaceSurface(
101
101
  options.opener();
102
102
  return opened(surface, { categoryId: options.categoryId, panelId: options.panelId, route: 'named-opener' });
103
103
  }
104
- if (context.showPanel) {
105
- const pane = optionalPane(args);
106
- context.showPanel(options.panelId, pane);
107
- return opened(surface, {
108
- categoryId: options.categoryId,
109
- panelId: options.panelId,
110
- pane: pane ?? 'default',
111
- route: 'panel-route',
112
- });
113
- }
114
104
  return routeUnavailable(surface);
115
105
  }
116
106
 
@@ -183,17 +173,17 @@ const UI_SURFACES: readonly UiSurfaceDefinition[] = [
183
173
  },
184
174
  {
185
175
  id: 'panel-picker',
186
- label: 'Panel Picker',
176
+ label: 'Workspace Picker',
187
177
  kind: 'picker',
188
- summary: 'Operator panel route into Agent Workspace home.',
178
+ summary: 'Operator route into the Agent Workspace home.',
189
179
  command: 'Ctrl+P',
190
- preferredModelRoute: `Use ${agentHarnessModes('panels', 'panel', 'open_panel')} for panel catalog and routing, or mode:"workspace_actions" for concrete model operation.`,
191
- available: (context) => typeof context.openPanelPicker === 'function' || typeof context.openAgentWorkspace === 'function',
180
+ preferredModelRoute: 'Use mode:"workspace_actions" for concrete model operation, or mode:"open_ui_surface" for visible routing.',
181
+ available: (context) => typeof context.openWorkspacePicker === 'function' || typeof context.openAgentWorkspace === 'function',
192
182
  open: (context) => {
193
183
  const surface = findSurfaceById('panel-picker')!;
194
- if (context.openPanelPicker) {
195
- context.openPanelPicker();
196
- return opened(surface, { categoryId: 'home', route: 'panel-picker' });
184
+ if (context.openWorkspacePicker) {
185
+ context.openWorkspacePicker();
186
+ return opened(surface, { categoryId: 'home', route: 'workspace-picker' });
197
187
  }
198
188
  return openAgentWorkspaceCategory(context, surface, 'home');
199
189
  },
@@ -206,7 +196,7 @@ const UI_SURFACES: readonly UiSurfaceDefinition[] = [
206
196
  command: '/security',
207
197
  preferredModelRoute: `Use mode:"workspace_actions" for security review actions or ${agentHarnessModes('run_command')} for confirmed /security review output.`,
208
198
  parameters: ['pane'],
209
- available: (context) => typeof context.openAgentWorkspace === 'function' || typeof context.openSecurityPanel === 'function' || typeof context.showPanel === 'function',
199
+ available: (context) => typeof context.openAgentWorkspace === 'function' || typeof context.openSecurityPanel === 'function',
210
200
  open: (context, args) => {
211
201
  const surface = findSurfaceById('security-panel')!;
212
202
  return openPanelWorkspaceSurface(context, args, surface, {
@@ -224,7 +214,7 @@ const UI_SURFACES: readonly UiSurfaceDefinition[] = [
224
214
  command: '/knowledge',
225
215
  preferredModelRoute: `Use agent_knowledge, agent_knowledge_ingest, mode:"workspace_actions", or ${agentHarnessModes('run_command')} for confirmed /knowledge operation.`,
226
216
  parameters: ['pane'],
227
- available: (context) => typeof context.openAgentWorkspace === 'function' || typeof context.openKnowledgePanel === 'function' || typeof context.showPanel === 'function',
217
+ available: (context) => typeof context.openAgentWorkspace === 'function' || typeof context.openKnowledgePanel === 'function',
228
218
  open: (context, args) => {
229
219
  const surface = findSurfaceById('knowledge-panel')!;
230
220
  return openPanelWorkspaceSurface(context, args, surface, {
@@ -242,7 +232,7 @@ const UI_SURFACES: readonly UiSurfaceDefinition[] = [
242
232
  command: '/subscription',
243
233
  preferredModelRoute: `Use mode:"workspace_actions" or ${agentHarnessModes('run_command')} for confirmed /subscription mirrors.`,
244
234
  parameters: ['pane'],
245
- available: (context) => typeof context.openAgentWorkspace === 'function' || typeof context.openSubscriptionPanel === 'function' || typeof context.showPanel === 'function',
235
+ available: (context) => typeof context.openAgentWorkspace === 'function' || typeof context.openSubscriptionPanel === 'function',
246
236
  open: (context, args) => {
247
237
  const surface = findSurfaceById('subscription-panel')!;
248
238
  return openPanelWorkspaceSurface(context, args, surface, {
@@ -49,7 +49,7 @@ export async function runWorkspaceAction(
49
49
  if (action.kind === 'settings-import') {
50
50
  const preview = previewAgentWorkspaceTuiSettingsImport(deps.commandContext);
51
51
  if (!preview) return error('GoodVibes settings import is unavailable in this runtime.');
52
- if (args.confirm !== true) {
52
+ if (args.confirm !== true && !readString(args.explicitUserRequest)) {
53
53
  return output({
54
54
  status: 'confirmation_required',
55
55
  action: describeWorkspaceAction(category, action, { lookup }),
@@ -57,10 +57,9 @@ export async function runWorkspaceAction(
57
57
  next: 'Run with confirm:true and explicitUserRequest after the user asks to import these settings.',
58
58
  });
59
59
  }
60
+ const confirmationError = requireConfirmedAction(args, 'GoodVibes settings import');
61
+ if (confirmationError) return error(confirmationError);
60
62
  const explicitUserRequest = readString(args.explicitUserRequest);
61
- if (!explicitUserRequest) {
62
- return error('GoodVibes settings import requires explicitUserRequest when confirm is true.');
63
- }
64
63
  const outcome = await importAgentWorkspaceTuiSettings(deps.commandContext);
65
64
  return output({
66
65
  status: outcome.status,
@@ -11,9 +11,6 @@ type AgentWorkspaceToolAction =
11
11
  | 'surfaces'
12
12
  | 'surface'
13
13
  | 'open'
14
- | 'panels'
15
- | 'panel'
16
- | 'open_panel'
17
14
  | 'shortcuts'
18
15
  | 'keybindings'
19
16
  | 'keybinding'
@@ -33,7 +30,6 @@ interface AgentWorkspaceToolArgs {
33
30
  readonly actionId?: unknown;
34
31
  readonly workspaceActionId?: unknown;
35
32
  readonly surfaceId?: unknown;
36
- readonly panelId?: unknown;
37
33
  readonly command?: unknown;
38
34
  readonly commandName?: unknown;
39
35
  readonly args?: unknown;
@@ -47,7 +43,6 @@ interface AgentWorkspaceToolArgs {
47
43
  readonly combos?: unknown;
48
44
  readonly key?: unknown;
49
45
  readonly value?: unknown;
50
- readonly pane?: unknown;
51
46
  readonly includeParameters?: unknown;
52
47
  readonly limit?: unknown;
53
48
  readonly confirm?: unknown;
@@ -79,9 +74,6 @@ function normalizeWorkspaceAction(value: unknown): AgentWorkspaceToolAction | nu
79
74
  if (action === 'surfaces' || action === 'ui_surfaces' || action === 'screens' || action === 'views') return 'surfaces';
80
75
  if (action === 'surface' || action === 'ui_surface' || action === 'screen' || action === 'view') return 'surface';
81
76
  if (action === 'open' || action === 'navigate' || action === 'open_surface' || action === 'open_ui_surface') return 'open';
82
- if (action === 'panels' || action === 'panes') return 'panels';
83
- if (action === 'panel' || action === 'pane') return 'panel';
84
- if (action === 'open_panel' || action === 'open_pane') return 'open_panel';
85
77
  if (action === 'shortcuts' || action === 'shortcut_help' || action === 'help') return 'shortcuts';
86
78
  if (action === 'keybindings' || action === 'bindings' || action === 'keys') return 'keybindings';
87
79
  if (action === 'keybinding' || action === 'binding' || action === 'key') return 'keybinding';
@@ -100,7 +92,6 @@ function readAction(args: AgentWorkspaceToolArgs): AgentWorkspaceToolAction {
100
92
  const explicit = normalizeWorkspaceAction(args.action) ?? normalizeWorkspaceAction(args.mode);
101
93
  if (explicit) return explicit;
102
94
  if (readString(args.surfaceId)) return 'surface';
103
- if (readString(args.panelId)) return 'panel';
104
95
  if (readString(args.actionId) || readString(args.workspaceActionId)) return 'action';
105
96
  if (readString(args.command) || readString(args.commandName)) return 'command';
106
97
  if (readString(args.key)) return 'keybinding';
@@ -120,10 +111,6 @@ function surfaceLookup(args: AgentWorkspaceToolArgs): string {
120
111
  return readString(args.surfaceId) || readString(args.id);
121
112
  }
122
113
 
123
- function panelLookup(args: AgentWorkspaceToolArgs): string {
124
- return readString(args.panelId) || readString(args.id);
125
- }
126
-
127
114
  function confirmedArgs(args: AgentWorkspaceToolArgs): Record<string, unknown> {
128
115
  return compactArgs({
129
116
  confirm: args.confirm,
@@ -173,19 +160,6 @@ function surfaceArgs(mode: 'ui_surface' | 'open_ui_surface', args: AgentWorkspac
173
160
  });
174
161
  }
175
162
 
176
- function panelArgs(mode: 'panel' | 'open_panel', args: AgentWorkspaceToolArgs): Record<string, unknown> {
177
- const panelId = panelLookup(args);
178
- return compactArgs({
179
- mode,
180
- panelId,
181
- target: panelId ? undefined : args.target,
182
- query: panelId ? undefined : args.query,
183
- pane: args.pane,
184
- includeParameters: mode === 'panel' ? args.includeParameters : undefined,
185
- ...(mode === 'open_panel' ? confirmedArgs(args) : {}),
186
- });
187
- }
188
-
189
163
  function keybindingArgs(mode: 'keybinding' | 'run_keybinding' | 'set_keybinding' | 'reset_keybinding', args: AgentWorkspaceToolArgs): Record<string, unknown> {
190
164
  const actionId = lookupId(args);
191
165
  return compactArgs({
@@ -232,15 +206,14 @@ export function createAgentWorkspaceTool(deps: AgentWorkspaceToolDeps): Tool {
232
206
  properties: {
233
207
  action: {
234
208
  type: 'string',
235
- enum: ['status', 'actions', 'action', 'run', 'surfaces', 'surface', 'open', 'panels', 'panel', 'open_panel', 'shortcuts', 'keybindings', 'keybinding', 'run_keybinding', 'set_keybinding', 'reset_keybinding', 'commands', 'command', 'run_command', 'cli_commands', 'cli_command'],
209
+ enum: ['status', 'actions', 'action', 'run', 'surfaces', 'surface', 'open', 'shortcuts', 'keybindings', 'keybinding', 'run_keybinding', 'set_keybinding', 'reset_keybinding', 'commands', 'command', 'run_command', 'cli_commands', 'cli_command'],
236
210
  description: 'Inspect catalogs, open UI, or run approved workspace actions.',
237
211
  },
238
212
  mode: { type: 'string', description: 'Alias for action.' },
239
- id: { type: 'string', description: 'Generic action, surface, panel, or keybinding id.' },
213
+ id: { type: 'string', description: 'Generic action, surface, or keybinding id.' },
240
214
  actionId: { type: 'string', description: 'Workspace action or keybinding action id.' },
241
215
  workspaceActionId: { type: 'string', description: 'Workspace action id alias.' },
242
216
  surfaceId: { type: 'string', description: 'UI surface id.' },
243
- panelId: { type: 'string', description: 'Panel id.' },
244
217
  command: { type: 'string', description: 'Slash or CLI command string.' },
245
218
  commandName: { type: 'string', description: 'Slash or CLI command name.' },
246
219
  args: { type: 'array', items: { type: 'string' }, description: 'Command arguments when commandName is used.' },
@@ -254,7 +227,6 @@ export function createAgentWorkspaceTool(deps: AgentWorkspaceToolDeps): Tool {
254
227
  combos: { type: 'array', items: { type: 'object' }, description: 'Multiple keybinding combos.' },
255
228
  key: { type: 'string', description: 'Keybinding lookup key.' },
256
229
  value: { anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }], description: 'Keybinding value such as Ctrl+G.' },
257
- pane: { type: 'string', enum: ['top', 'bottom'], description: 'Preferred pane for panel open.' },
258
230
  includeParameters: { type: 'boolean', description: 'Include detailed schemas or route metadata.' },
259
231
  limit: { type: 'number', description: 'Maximum rows returned for catalog actions.' },
260
232
  confirm: { type: 'boolean', description: 'Required true for UI opens and side-effecting actions.' },
@@ -276,9 +248,6 @@ export function createAgentWorkspaceTool(deps: AgentWorkspaceToolDeps): Tool {
276
248
  if (action === 'surfaces') return harnessTool.execute(compactArgs({ mode: 'ui_surfaces', target: args.target, query: args.query, limit: args.limit, includeParameters: args.includeParameters }));
277
249
  if (action === 'surface') return harnessTool.execute(surfaceArgs('ui_surface', args));
278
250
  if (action === 'open') return harnessTool.execute(surfaceArgs('open_ui_surface', args));
279
- if (action === 'panels') return harnessTool.execute(compactArgs({ mode: 'panels', target: args.target, query: args.query, limit: args.limit, includeParameters: args.includeParameters }));
280
- if (action === 'panel') return harnessTool.execute(panelArgs('panel', args));
281
- if (action === 'open_panel') return harnessTool.execute(panelArgs('open_panel', args));
282
251
  if (action === 'shortcuts') return harnessTool.execute(compactArgs({ mode: 'shortcuts', target: args.target, query: args.query, limit: args.limit, includeParameters: args.includeParameters }));
283
252
  if (action === 'keybindings') return harnessTool.execute(compactArgs({ mode: 'keybindings', target: args.target, query: args.query, limit: args.limit, includeParameters: args.includeParameters }));
284
253
  if (action === 'keybinding') return harnessTool.execute(keybindingArgs('keybinding', args));
@@ -139,14 +139,20 @@ export function wrapText(text: string, width: number): string[] {
139
139
  if (currentLine) lines.push(currentLine);
140
140
  let remaining = word;
141
141
  while (getDisplayWidth(remaining) > width) {
142
+ const codePoints = [...remaining];
142
143
  let splitIdx = 0;
143
144
  let currentWidth = 0;
144
- for (let i = 0; i < remaining.length; i++) {
145
- const charWidth = getDisplayWidth(remaining[i]!);
145
+ for (const cp of codePoints) {
146
+ const charWidth = getDisplayWidth(cp);
146
147
  if (currentWidth + charWidth > width) break;
147
148
  currentWidth += charWidth;
148
- splitIdx = i + 1;
149
+ splitIdx += cp.length;
149
150
  }
151
+ // Safety: guarantee at least one code point of progress even if a
152
+ // single grapheme is wider than the available width (e.g. CJK/emoji
153
+ // at width===1). Without this guard splitIdx stays 0, remaining
154
+ // never shrinks, and the while-loop hangs.
155
+ if (splitIdx === 0) splitIdx = codePoints[0]!.length;
150
156
  lines.push(remaining.slice(0, splitIdx));
151
157
  remaining = remaining.slice(splitIdx);
152
158
  }
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '1.4.4';
9
+ let _version = '1.5.1';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
12
12
  readonly version?: unknown;
@@ -1,44 +0,0 @@
1
- import type { AgentWorkspaceCategoryId } from './agent-workspace-types.ts';
2
-
3
- const PANEL_CATEGORY_ROUTES = {
4
- knowledge: 'knowledge',
5
- memory: 'memory',
6
- 'work-plan': 'work',
7
- 'project-planning': 'work',
8
- plan: 'work',
9
- approval: 'work',
10
- tasks: 'work',
11
- 'provider-health': 'setup',
12
- automation: 'automation',
13
- schedule: 'automation',
14
- providers: 'setup',
15
- accounts: 'setup',
16
- subscription: 'setup',
17
- cost: 'setup',
18
- tokens: 'setup',
19
- security: 'tools',
20
- policy: 'tools',
21
- tools: 'tools',
22
- 'qr-code': 'channels',
23
- sessions: 'conversation',
24
- context: 'conversation',
25
- thinking: 'conversation',
26
- 'system-messages': 'conversation',
27
- docs: 'home',
28
- 'panel-list': 'home',
29
- } as const satisfies Readonly<Record<string, AgentWorkspaceCategoryId>>;
30
-
31
- type AgentWorkspacePanelRouteId = keyof typeof PANEL_CATEGORY_ROUTES;
32
-
33
- function hasPanelCategoryRoute(panelId: string): panelId is AgentWorkspacePanelRouteId {
34
- return Object.prototype.hasOwnProperty.call(PANEL_CATEGORY_ROUTES, panelId);
35
- }
36
-
37
- export function agentWorkspaceCategoryForPanel(panelId: string): AgentWorkspaceCategoryId {
38
- return hasPanelCategoryRoute(panelId) ? PANEL_CATEGORY_ROUTES[panelId] : 'home';
39
- }
40
-
41
- export function agentWorkspaceCommandForPanel(panelId: string): string {
42
- const category = agentWorkspaceCategoryForPanel(panelId);
43
- return category === 'setup' ? '/agent' : `/agent ${category}`;
44
- }
@@ -1,26 +0,0 @@
1
- import type { CommandContext } from './command-registry.ts';
2
- import { logger } from '@pellux/goodvibes-sdk/platform/utils';
3
- import type { Panel } from '../panels/types.ts';
4
- import type { PanelManager } from '../panels/panel-manager.ts';
5
- import { ApprovalPanel } from '../panels/approval-panel.ts';
6
-
7
- export function handlePanelIntegrationAction(
8
- panelManager: PanelManager,
9
- activePanel: Panel | null,
10
- key: string,
11
- commandContext?: CommandContext,
12
- ): boolean {
13
- if (!activePanel) return false;
14
-
15
- if ((key === 'enter' || key === 'return') && activePanel instanceof ApprovalPanel) {
16
- const command = activePanel.getSelectedCommand();
17
- if (!command || !commandContext?.executeCommand) return false;
18
- const parts = command.replace(/^\//, '').split(/\s+/).filter(Boolean);
19
- const [name, ...args] = parts;
20
- if (!name) return false;
21
- void commandContext.executeCommand(name, args).catch((err) => { logger.debug('approval panel command dispatch failed', { err }); });
22
- return true;
23
- }
24
-
25
- return false;
26
- }
@@ -1,149 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
3
- import {
4
- buildGuidanceLine,
5
- buildKeyValueLine,
6
- buildPanelLine,
7
- DEFAULT_PANEL_PALETTE,
8
- } from './polish.ts';
9
- import type { PolicyRuntimeState } from '@/runtime/index.ts';
10
- import { buildPermissionRuleSuggestions } from '@/runtime/index.ts';
11
-
12
- const C = {
13
- ...DEFAULT_PANEL_PALETTE,
14
- headerBg: '#111827',
15
- } as const;
16
-
17
- const APPROVAL_ROWS = [
18
- ['shell', 'why prompted: side effects, destructive ops, secret exposure, escalation', 'review via /approval review shell'],
19
- ['file', 'why prompted: config mutation, notebook edits, secret-bearing paths', 'review via /approval review file'],
20
- ['network', 'why prompted: external hosts, fetch scope, egress policy', 'review via /approval review network'],
21
- ['delegate', 'why prompted: explicit TUI build handoff, write scope, result ownership', 'review via /approval review delegate'],
22
- ['mcp', 'why prompted: trust escalation, host scope, path scope, coherence mismatch', 'review via /approval review mcp'],
23
- ['remote', 'why prompted: build-host trust, remote write scope, artifact requirements', 'review via /approval review remote'],
24
- ['hook', 'why prompted: deny/mutate authority, blocking behavior, execution provenance', 'review via /approval review hook'],
25
- ['plugin', 'why prompted: install/update lifecycle, provenance, capability grants', 'review via /approval review plugin'],
26
- ] as const;
27
-
28
- type ApprovalRow = (typeof APPROVAL_ROWS)[number];
29
-
30
- export class ApprovalPanel extends ScrollableListPanel<ApprovalRow> {
31
- private readonly policyRuntimeState: Pick<PolicyRuntimeState, 'getSnapshot'>;
32
-
33
- public constructor(policyRuntimeState: Pick<PolicyRuntimeState, 'getSnapshot'>) {
34
- super('approval', 'Approval', 'A', 'monitoring');
35
- this.showSelectionGutter = true; // I5: non-color selection affordance
36
- this.policyRuntimeState = policyRuntimeState;
37
- }
38
-
39
- protected override getPalette() { return C; }
40
- protected override getEmptyStateMessage() { return ' No approval lanes defined.'; }
41
-
42
- protected getItems(): readonly ApprovalRow[] {
43
- return APPROVAL_ROWS;
44
- }
45
-
46
- protected renderItem(row: ApprovalRow, index: number, selected: boolean, width: number): Line {
47
- const bg = selected ? C.selectBg : undefined;
48
- return buildPanelLine(width, [
49
- [' ', C.label],
50
- [row[0].padEnd(10), C.info, bg],
51
- [row[1].slice(0, Math.max(0, width - 18)), C.value, bg],
52
- ]);
53
- }
54
-
55
- public handleInput(key: string): boolean {
56
- if (key === 'home') {
57
- this.selectedIndex = 0;
58
- this.markDirty();
59
- return true;
60
- }
61
- if (key === 'end') {
62
- this.selectedIndex = APPROVAL_ROWS.length - 1;
63
- this.markDirty();
64
- return true;
65
- }
66
- if (key === 'enter' || key === 'return') {
67
- return true;
68
- }
69
- return super.handleInput(key);
70
- }
71
-
72
- public getSelectedCommand(): string | null {
73
- const selected = APPROVAL_ROWS[this.selectedIndex] ?? null;
74
- return selected ? selected[2].replace('review via ', '').trim() : null;
75
- }
76
-
77
- public render(width: number, height: number): Line[] {
78
- this.clampSelection();
79
- const policySnapshot = this.policyRuntimeState.getSnapshot();
80
- const approvalCount = policySnapshot.recentPermissionAudit.filter((e) => e.approved === true).length;
81
- const denialCount = policySnapshot.recentPermissionAudit.filter((e) => e.approved === false).length;
82
- const pendingCount = policySnapshot.recentPermissionAudit.filter((e) => e.approved === undefined).length;
83
-
84
- const selected = APPROVAL_ROWS[this.selectedIndex] ?? null;
85
- const detailLines: Line[] = [];
86
- if (selected) {
87
- detailLines.push(buildPanelLine(width, [[' Selected Lane', C.label]]));
88
- detailLines.push(buildKeyValueLine(width, [
89
- { label: 'lane', value: selected[0], valueColor: C.info },
90
- { label: 'next review', value: selected[2], valueColor: C.dim },
91
- ], C));
92
- detailLines.push(buildPanelLine(width, [[` ${selected[1]}`, C.value]]));
93
- detailLines.push(buildGuidanceLine(width, selected[2].replace('review via ', ''), `open the ${selected[0]} review path`, C));
94
- }
95
-
96
- const recentAuditLines: Line[] = [];
97
- for (const entry of policySnapshot.recentPermissionAudit.slice(0, 5)) {
98
- const decision = entry.approved === undefined ? 'pending' : entry.approved ? 'approved' : 'denied';
99
- const decisionColor = entry.approved === undefined ? C.info : entry.approved ? C.good : C.bad;
100
- recentAuditLines.push(buildPanelLine(width, [
101
- [` ${decision.padEnd(8)}`, decisionColor],
102
- [`${entry.tool}`.padEnd(14), C.label],
103
- [entry.summary.slice(0, Math.max(0, width - 28)), C.value],
104
- ]));
105
- if (entry.reasons[0]) {
106
- recentAuditLines.push(buildPanelLine(width, [[` ${entry.reasons[0]}`, C.dim]]));
107
- }
108
- }
109
- if (recentAuditLines.length === 0) {
110
- recentAuditLines.push(buildPanelLine(width, [[` No recent approval pressure. Live requests and decisions will appear here.`, C.dim]]));
111
- }
112
-
113
- const ruleSuggestionLines: Line[] = [];
114
- for (const suggestion of buildPermissionRuleSuggestions(policySnapshot.recentPermissionAudit).slice(0, 3)) {
115
- ruleSuggestionLines.push(buildPanelLine(width, [[` ${suggestion.summary}`, C.info]]));
116
- ruleSuggestionLines.push(buildGuidanceLine(width, suggestion.command, suggestion.reason, C));
117
- }
118
- if (ruleSuggestionLines.length === 0) {
119
- ruleSuggestionLines.push(buildPanelLine(width, [[` No repeated denials currently suggest a durable rule.`, C.dim]]));
120
- }
121
-
122
- const headerLines: Line[] = [
123
- buildPanelLine(width, [[' Approval posture', C.label]]),
124
- buildKeyValueLine(width, [
125
- { label: 'why prompted', value: 'risk summary', valueColor: C.value },
126
- { label: 'what-if', value: '/security review', valueColor: C.info },
127
- { label: 'operator', value: '/approval + /security', valueColor: C.good },
128
- ], C),
129
- buildPanelLine(width, [
130
- [' \u2713 ', C.good],
131
- [`approvals (${approvalCount}) `, C.good],
132
- ['\u2715 ', C.bad],
133
- [`denials (${denialCount}) `, C.bad],
134
- ['\u25cb ', C.info],
135
- [`pending (${pendingCount})`, C.info],
136
- ]),
137
- buildGuidanceLine(width, '/approval review shell', 'inspect the highest-risk approval lane and refine scoped review posture', C),
138
- ...detailLines,
139
- ...recentAuditLines,
140
- ...ruleSuggestionLines,
141
- ];
142
-
143
- return this.renderList(width, height, {
144
- title: 'Approval Control Room',
145
- header: headerLines,
146
- footer: [buildPanelLine(width, [[` Up/Down move Home/End jump selected lane opens the next command path`, C.dim]])],
147
- });
148
- }
149
- }