@pellux/goodvibes-agent 0.1.117 → 1.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.
Files changed (253) hide show
  1. package/CHANGELOG.md +13 -4
  2. package/README.md +35 -14
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +176073 -170980
  5. package/docs/README.md +11 -5
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +3 -3
  8. package/docs/getting-started.md +29 -15
  9. package/docs/knowledge-artifacts-and-multimodal.md +91 -0
  10. package/docs/project-planning.md +79 -0
  11. package/docs/providers-and-routing.md +46 -0
  12. package/docs/release-and-publishing.md +44 -9
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +2 -5
  16. package/src/agent/channel-delivery.ts +201 -0
  17. package/src/agent/media-generation.ts +159 -0
  18. package/src/agent/memory-prompt.ts +0 -1
  19. package/src/agent/note-registry.ts +329 -0
  20. package/src/agent/operator-actions.ts +343 -0
  21. package/src/agent/persona-registry.ts +15 -14
  22. package/src/agent/record-labels.ts +107 -0
  23. package/src/agent/reminder-schedule-format.ts +33 -24
  24. package/src/agent/reminder-schedule.ts +26 -25
  25. package/src/agent/routine-registry.ts +13 -12
  26. package/src/agent/routine-schedule-args.ts +2 -1
  27. package/src/agent/routine-schedule-format.ts +77 -53
  28. package/src/agent/routine-schedule-promotion.ts +34 -32
  29. package/src/agent/routine-schedule-receipts.ts +28 -26
  30. package/src/agent/runtime-profile-starters.ts +2 -2
  31. package/src/agent/runtime-profile.ts +18 -17
  32. package/src/agent/skill-registry.ts +25 -24
  33. package/src/cli/agent-knowledge-args.ts +5 -1
  34. package/src/cli/agent-knowledge-command.ts +39 -33
  35. package/src/cli/agent-knowledge-format.ts +80 -67
  36. package/src/cli/agent-knowledge-methods.ts +1 -1
  37. package/src/cli/agent-knowledge-runtime.ts +32 -26
  38. package/src/cli/bundle-command.ts +13 -8
  39. package/src/cli/config-overrides.ts +37 -36
  40. package/src/cli/external-runtime.ts +10 -4
  41. package/src/cli/help.ts +29 -11
  42. package/src/cli/local-library-command.ts +134 -68
  43. package/src/cli/management-commands.ts +98 -62
  44. package/src/cli/management.ts +52 -26
  45. package/src/cli/memory-command.ts +66 -32
  46. package/src/cli/parser.ts +37 -24
  47. package/src/cli/profiles-command.ts +52 -35
  48. package/src/cli/provider-auth-routes.ts +2 -1
  49. package/src/cli/routines-command.ts +44 -36
  50. package/src/cli/service-posture.ts +6 -6
  51. package/src/cli/status.ts +46 -121
  52. package/src/core/conversation-message-snapshot.ts +131 -0
  53. package/src/input/agent-workspace-activation.ts +33 -7
  54. package/src/input/agent-workspace-basic-command-editor-submission.ts +7 -3
  55. package/src/input/agent-workspace-basic-command-editors.ts +30 -10
  56. package/src/input/agent-workspace-categories.ts +276 -64
  57. package/src/input/agent-workspace-channel-command-editor-submission.ts +34 -0
  58. package/src/input/agent-workspace-channel-command-editors.ts +23 -5
  59. package/src/input/agent-workspace-channels.ts +35 -2
  60. package/src/input/agent-workspace-command-editor.ts +18 -2
  61. package/src/input/agent-workspace-config-reader.ts +16 -0
  62. package/src/input/agent-workspace-delegation-editor-submission.ts +1 -1
  63. package/src/input/agent-workspace-editors.ts +140 -2
  64. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  65. package/src/input/agent-workspace-learned-behavior.ts +2 -2
  66. package/src/input/agent-workspace-library-command-editor-submission.ts +1 -1
  67. package/src/input/agent-workspace-library-command-editors.ts +2 -2
  68. package/src/input/agent-workspace-local-operations.ts +218 -0
  69. package/src/input/agent-workspace-local-selection.ts +75 -0
  70. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  71. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  72. package/src/input/agent-workspace-memory-command-editors.ts +1 -1
  73. package/src/input/agent-workspace-memory-editor.ts +2 -2
  74. package/src/input/agent-workspace-navigation.ts +38 -2
  75. package/src/input/agent-workspace-notify-editor-submission.ts +16 -2
  76. package/src/input/agent-workspace-operations-command-editor-submission.ts +63 -1
  77. package/src/input/agent-workspace-operations-command-editors.ts +80 -3
  78. package/src/input/agent-workspace-panel-route.ts +43 -0
  79. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  80. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  81. package/src/input/agent-workspace-search.ts +169 -0
  82. package/src/input/agent-workspace-setup.ts +22 -11
  83. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +6 -6
  84. package/src/input/agent-workspace-skill-bundle-command-editors.ts +10 -10
  85. package/src/input/agent-workspace-snapshot.ts +112 -13
  86. package/src/input/agent-workspace-task-command-editors.ts +4 -4
  87. package/src/input/agent-workspace-token.ts +18 -2
  88. package/src/input/agent-workspace-types.ts +92 -4
  89. package/src/input/agent-workspace-voice-media.ts +3 -6
  90. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  91. package/src/input/agent-workspace.ts +136 -208
  92. package/src/input/command-registry.ts +4 -1
  93. package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
  94. package/src/input/commands/agent-skills-runtime.ts +83 -70
  95. package/src/input/commands/agent-workspace-runtime.ts +23 -7
  96. package/src/input/commands/brief-runtime.ts +25 -24
  97. package/src/input/commands/channels-runtime.ts +145 -31
  98. package/src/input/commands/delegation-runtime.ts +29 -23
  99. package/src/input/commands/experience-runtime.ts +6 -4
  100. package/src/input/commands/guidance-runtime.ts +4 -4
  101. package/src/input/commands/health-runtime.ts +140 -115
  102. package/src/input/commands/knowledge.ts +57 -56
  103. package/src/input/commands/local-provider-runtime.ts +36 -18
  104. package/src/input/commands/local-runtime.ts +138 -16
  105. package/src/input/commands/local-setup-review.ts +7 -7
  106. package/src/input/commands/mcp-runtime.ts +56 -35
  107. package/src/input/commands/notify-runtime.ts +38 -9
  108. package/src/input/commands/operator-actions-runtime.ts +138 -0
  109. package/src/input/commands/operator-runtime.ts +6 -17
  110. package/src/input/commands/personas-runtime.ts +45 -30
  111. package/src/input/commands/planning-runtime.ts +1 -1
  112. package/src/input/commands/platform-access-runtime.ts +29 -29
  113. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  114. package/src/input/commands/qrcode-runtime.ts +45 -6
  115. package/src/input/commands/recall-bundle.ts +11 -11
  116. package/src/input/commands/recall-capture.ts +13 -11
  117. package/src/input/commands/recall-query.ts +29 -21
  118. package/src/input/commands/recall-review.ts +2 -1
  119. package/src/input/commands/routines-runtime.ts +59 -43
  120. package/src/input/commands/schedule-runtime.ts +33 -20
  121. package/src/input/commands/security-runtime.ts +4 -4
  122. package/src/input/commands/session-content.ts +80 -78
  123. package/src/input/commands/session-workflow.ts +132 -93
  124. package/src/input/commands/session.ts +3 -174
  125. package/src/input/commands/shell-core.ts +32 -17
  126. package/src/input/commands/subscription-runtime.ts +53 -179
  127. package/src/input/commands/tasks-runtime.ts +20 -20
  128. package/src/input/commands/work-plan-runtime.ts +33 -8
  129. package/src/input/commands.ts +2 -2
  130. package/src/input/feed-context-factory.ts +2 -1
  131. package/src/input/file-picker.ts +3 -2
  132. package/src/input/handler-command-route.ts +4 -7
  133. package/src/input/handler-content-actions.ts +89 -1
  134. package/src/input/handler-feed-routes.ts +19 -12
  135. package/src/input/handler-feed.ts +6 -3
  136. package/src/input/handler-interactions.ts +7 -5
  137. package/src/input/handler-modal-stack.ts +3 -3
  138. package/src/input/handler-onboarding.ts +24 -80
  139. package/src/input/handler-shortcuts.ts +15 -4
  140. package/src/input/handler.ts +47 -17
  141. package/src/input/input-history.ts +5 -6
  142. package/src/input/keybindings.ts +22 -11
  143. package/src/input/onboarding/onboarding-wizard-apply.ts +13 -0
  144. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  145. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +147 -57
  146. package/src/input/onboarding/onboarding-wizard-steps.ts +46 -29
  147. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  148. package/src/input/onboarding/onboarding-wizard.ts +68 -0
  149. package/src/input/profile-picker-modal.ts +31 -12
  150. package/src/input/session-picker-modal.ts +21 -4
  151. package/src/input/settings-modal-behavior.ts +0 -3
  152. package/src/input/settings-modal-subscriptions.ts +3 -3
  153. package/src/input/settings-modal-types.ts +2 -13
  154. package/src/input/settings-modal.ts +6 -52
  155. package/src/input/submission-intent.ts +0 -1
  156. package/src/input/submission-router.ts +3 -3
  157. package/src/main.ts +18 -8
  158. package/src/panels/approval-panel.ts +8 -8
  159. package/src/panels/automation-control-panel.ts +2 -2
  160. package/src/panels/base-panel.ts +1 -1
  161. package/src/panels/builtin/agent.ts +1 -1
  162. package/src/panels/builtin/operations.ts +1 -10
  163. package/src/panels/builtin/session.ts +9 -9
  164. package/src/panels/builtin/shared.ts +2 -2
  165. package/src/panels/cost-tracker-panel.ts +1 -1
  166. package/src/panels/docs-panel.ts +5 -3
  167. package/src/panels/index.ts +0 -1
  168. package/src/panels/knowledge-panel.ts +6 -5
  169. package/src/panels/memory-panel.ts +7 -6
  170. package/src/panels/panel-list-panel.ts +36 -80
  171. package/src/panels/project-planning-panel.ts +18 -11
  172. package/src/panels/provider-account-snapshot.ts +51 -25
  173. package/src/panels/provider-accounts-panel.ts +33 -18
  174. package/src/panels/provider-health-domains.ts +45 -4
  175. package/src/panels/provider-health-panel.ts +5 -4
  176. package/src/panels/qr-panel.ts +1 -1
  177. package/src/panels/schedule-panel.ts +9 -17
  178. package/src/panels/security-panel.ts +8 -8
  179. package/src/panels/session-browser-panel.ts +10 -10
  180. package/src/panels/subscription-panel.ts +3 -3
  181. package/src/panels/system-messages-panel.ts +1 -1
  182. package/src/panels/tasks-panel.ts +20 -13
  183. package/src/panels/tool-inspector-panel.ts +19 -12
  184. package/src/panels/work-plan-panel.ts +5 -5
  185. package/src/planning/project-planning-coordinator.ts +1 -1
  186. package/src/provider-auth-route-display.ts +9 -0
  187. package/src/renderer/agent-workspace-style.ts +34 -0
  188. package/src/renderer/agent-workspace.ts +228 -52
  189. package/src/renderer/autocomplete-overlay.ts +25 -6
  190. package/src/renderer/bookmark-modal.ts +19 -4
  191. package/src/renderer/buffer.ts +4 -2
  192. package/src/renderer/context-inspector.ts +50 -13
  193. package/src/renderer/diff.ts +1 -1
  194. package/src/renderer/file-picker-overlay.ts +19 -6
  195. package/src/renderer/help-overlay.ts +106 -33
  196. package/src/renderer/history-search-overlay.ts +19 -4
  197. package/src/renderer/live-tail-modal.ts +27 -5
  198. package/src/renderer/mcp-workspace.ts +164 -50
  199. package/src/renderer/model-picker-overlay.ts +58 -2
  200. package/src/renderer/model-workspace.ts +104 -20
  201. package/src/renderer/process-modal.ts +27 -6
  202. package/src/renderer/profile-picker-modal.ts +20 -4
  203. package/src/renderer/search-overlay.ts +25 -5
  204. package/src/renderer/selection-modal-overlay.ts +46 -14
  205. package/src/renderer/session-picker-modal.ts +18 -1
  206. package/src/renderer/settings-modal-helpers.ts +2 -40
  207. package/src/renderer/settings-modal.ts +83 -50
  208. package/src/renderer/tool-call.ts +20 -11
  209. package/src/runtime/agent-runtime-events.ts +6 -6
  210. package/src/runtime/bootstrap-command-context.ts +7 -1
  211. package/src/runtime/bootstrap-command-parts.ts +9 -7
  212. package/src/runtime/bootstrap-core.ts +19 -1
  213. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  214. package/src/runtime/bootstrap-shell.ts +4 -4
  215. package/src/runtime/bootstrap.ts +31 -22
  216. package/src/runtime/connected-host-auth.ts +7 -2
  217. package/src/runtime/diagnostics/panels/index.ts +2 -2
  218. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  219. package/src/runtime/index.ts +2 -1
  220. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  221. package/src/runtime/onboarding/apply.ts +80 -79
  222. package/src/runtime/onboarding/derivation.ts +5 -8
  223. package/src/runtime/onboarding/snapshot.ts +0 -15
  224. package/src/runtime/onboarding/types.ts +8 -19
  225. package/src/runtime/onboarding/verify.ts +32 -10
  226. package/src/runtime/operator-token-cleanup.ts +1 -1
  227. package/src/runtime/services.ts +91 -22
  228. package/src/runtime/store/selectors/index.ts +3 -3
  229. package/src/runtime/store/state.ts +1 -4
  230. package/src/runtime/surface-feature-flags.ts +41 -6
  231. package/src/runtime/ui-read-models.ts +3 -4
  232. package/src/runtime/ui-services.ts +6 -6
  233. package/src/shell/blocking-input.ts +2 -1
  234. package/src/shell/ui-openers.ts +3 -13
  235. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  236. package/src/tools/agent-channel-send-tool.ts +133 -0
  237. package/src/tools/agent-context-policy.ts +1 -1
  238. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  239. package/src/tools/agent-knowledge-tool.ts +170 -0
  240. package/src/tools/agent-local-registry-tool.ts +269 -69
  241. package/src/tools/agent-media-generate-tool.ts +133 -0
  242. package/src/tools/agent-notify-tool.ts +143 -0
  243. package/src/tools/agent-operator-action-tool.ts +137 -0
  244. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  245. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  246. package/src/tools/agent-tool-policy-guard.ts +8 -8
  247. package/src/tools/agent-work-plan-tool.ts +256 -0
  248. package/src/version.ts +1 -1
  249. package/src/input/commands/policy-dispatch.ts +0 -339
  250. package/src/input/commands/policy.ts +0 -13
  251. package/src/panels/panel-picker.ts +0 -105
  252. package/src/panels/policy-panel.ts +0 -308
  253. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -1,339 +0,0 @@
1
- import type { CommandContext } from '../command-registry.ts';
2
- import {
3
- buildPolicyPreflightReview,
4
- createPermissionSimulator,
5
- lintPolicyConfig,
6
- runPolicySimulationScenarios,
7
- } from '@/runtime/index.ts';
8
- import { DivergenceDashboard } from '@/runtime/index.ts';
9
- import type { PolicyRuntimeState } from '@/runtime/index.ts';
10
- import { createUnsignedBundle } from '@/runtime/index.ts';
11
- import type { PolicyBundlePayload } from '@/runtime/index.ts';
12
- import type { PolicyRule, PermissionsConfig, DivergenceStats } from '@/runtime/index.ts';
13
- import { requireShellPaths } from './runtime-services.ts';
14
-
15
- function getPolicyState(ctx?: CommandContext): PolicyRuntimeState {
16
- const policyRuntimeState = ctx?.extensions.policyRuntimeState;
17
- if (!policyRuntimeState) {
18
- throw new Error('Policy state is not available in this Agent session.');
19
- }
20
- return policyRuntimeState;
21
- }
22
-
23
- function getRegistry(ctx?: CommandContext) {
24
- return ctx?.extensions.policyRegistry ?? getPolicyState(ctx).getRegistry();
25
- }
26
-
27
- function fmtRate(rate: number): string {
28
- return `${(rate * 100).toFixed(1)}%`;
29
- }
30
-
31
- function bundleSummary(
32
- label: string,
33
- version: { bundle: { bundleId: string; issuedAt: string; issuer?: string }; state: string; rules: PolicyRule[] },
34
- ): string {
35
- const issuer = version.bundle.issuer ? ` issuer=${version.bundle.issuer}` : '';
36
- const ts = version.bundle.issuedAt.replace('T', ' ').slice(0, 19);
37
- return `${label}: ${version.bundle.bundleId} (${ts}${issuer}) [${version.rules.length} rules, ${version.state}]`;
38
- }
39
-
40
- async function handleLoad(args: string[], context: CommandContext): Promise<void> {
41
- const registry = getRegistry(context);
42
- const policyState = getPolicyState(context);
43
- const bundleId = args[0] ?? `policy-candidate-${Date.now()}`;
44
- const ruleCount = Math.max(0, parseInt(args[1] ?? '0', 10));
45
- const rules: PolicyRule[] = [];
46
- for (let i = 0; i < ruleCount; i++) {
47
- rules.push({
48
- type: 'prefix',
49
- id: `${bundleId}-rule-${i}`,
50
- description: `Demo rule ${i}`,
51
- origin: 'user',
52
- effect: 'allow',
53
- toolPattern: '*',
54
- commandPrefixes: [],
55
- });
56
- }
57
- const payload: PolicyBundlePayload = {
58
- version: 1,
59
- rules,
60
- description: `Loaded via /policy load at ${new Date().toISOString()}`,
61
- };
62
- const bundle = createUnsignedBundle(bundleId, payload);
63
- const result = registry.loadCandidate(bundle);
64
- if (!result.ok) {
65
- context.print(`[policy] Load failed: ${result.error ?? 'unknown error'}`);
66
- return;
67
- }
68
- const candidate = registry.getCandidate();
69
- if (candidate) {
70
- policyState.notify();
71
- context.print(bundleSummary('[policy] Candidate loaded', candidate));
72
- context.print('[policy] Next: run `/policy simulate` to collect divergence evidence before promoting.');
73
- }
74
- }
75
-
76
- async function handleSimulate(args: string[], context: CommandContext): Promise<void> {
77
- const registry = getRegistry(context);
78
- const policyState = getPolicyState(context);
79
- const projectRoot = requireShellPaths(context).workingDirectory;
80
- const candidate = registry.getCandidate();
81
- const current = registry.getCurrent();
82
- if (!candidate) {
83
- context.print('[policy] No candidate bundle loaded. Run `/policy load <bundle-id>` first.');
84
- return;
85
- }
86
- if (!registry.markSimulating()) {
87
- context.print(`[policy] Cannot start simulation: candidate is in "${candidate.state}" state. Only a freshly loaded ("loaded") candidate can be simulated.`);
88
- return;
89
- }
90
- const currentConfig: PermissionsConfig = {
91
- mode: 'default',
92
- projectRoot,
93
- rules: current?.rules ?? [],
94
- defaultEffect: 'allow',
95
- };
96
- const candidateForSim = registry.getCandidate();
97
- if (!candidateForSim) {
98
- throw new Error('Invariant: candidate disappeared after markSimulating()');
99
- }
100
- const candidateConfig: PermissionsConfig = {
101
- mode: 'default',
102
- projectRoot,
103
- rules: candidateForSim.rules,
104
- defaultEffect: 'allow',
105
- };
106
- const modeArg = args[0];
107
- const simulationMode = modeArg === 'enforce' ? 'enforce' : modeArg === 'silent' ? 'simulation-only' : 'warn-on-divergence';
108
- const simulator = createPermissionSimulator(currentConfig, candidateConfig, simulationMode, {});
109
- const dashboard = new DivergenceDashboard(simulator, simulationMode, { threshold: 0.05 });
110
- policyState.setDashboard(dashboard);
111
- context.print(`[policy] Simulation started in "${simulationMode}" mode. Evaluations will be tracked against candidate bundle "${candidate.bundle.bundleId}".`);
112
- context.print('[policy] Use `/policy diff` to compare rules. When ready, run `/policy promote` to enforce (requires gate passing).');
113
- const report = simulator.getDivergenceReport();
114
- const gateResult = dashboard.checkEnforceGate();
115
- const scenarioSummary = runPolicySimulationScenarios(simulator);
116
- registry.attachSimulationReport(report, gateResult);
117
- policyState.recordSimulationSummary(scenarioSummary);
118
- const candidate2 = registry.getCandidate();
119
- policyState.notify();
120
- context.print(`[policy] Baseline divergence: ${fmtRate(report.overall.divergenceRate)} (${report.overall.total} divergences / ${report.overall.totalEvaluations} evaluations). Gate: ${gateResult.status}.`);
121
- context.print(`[policy] Scenario run: ${scenarioSummary.totalScenarios} samples, ${scenarioSummary.divergentScenarios} diverged, ${scenarioSummary.allowedByActual}/${scenarioSummary.allowedBySimulated} allowed (actual/candidate).`);
122
- if (candidate2) context.print(bundleSummary('[policy] Candidate state', candidate2));
123
- }
124
-
125
- async function handleDiff(_args: string[], context: CommandContext): Promise<void> {
126
- const registry = getRegistry(context);
127
- const diff = registry.diff();
128
- if (!diff) {
129
- const current = registry.getCurrent();
130
- const candidate = registry.getCandidate();
131
- if (!current) context.print('[policy] No active bundle. Load a bundle with `/policy load`.');
132
- else if (!candidate) context.print('[policy] No candidate bundle. Load a candidate with `/policy load`.');
133
- else context.print('[policy] Diff unavailable.');
134
- return;
135
- }
136
- context.print(`[policy] Diff: ${diff.fromBundleId} → ${diff.toBundleId} (${diff.totalChanges} change${diff.totalChanges !== 1 ? 's' : ''})`);
137
- context.print(` Added: ${diff.added.length} rule${diff.added.length !== 1 ? 's' : ''}`);
138
- context.print(` Removed: ${diff.removed.length} rule${diff.removed.length !== 1 ? 's' : ''}`);
139
- context.print(` Changed: ${diff.changed.length} rule${diff.changed.length !== 1 ? 's' : ''}`);
140
- context.print(` Unchanged: ${diff.unchanged.length} rule${diff.unchanged.length !== 1 ? 's' : ''}`);
141
- if (diff.added.length > 0) {
142
- context.print(' [+] Added rules:');
143
- for (const r of diff.added) context.print(` + ${r.id} (${r.type}, effect=${r.effect})`);
144
- }
145
- if (diff.removed.length > 0) {
146
- context.print(' [-] Removed rules:');
147
- for (const r of diff.removed) context.print(` - ${r.id} (${r.type}, effect=${r.effect})`);
148
- }
149
- if (diff.changed.length > 0) {
150
- context.print(' [~] Changed rules:');
151
- for (const c of diff.changed) context.print(` ~ ${c.ruleId}`);
152
- }
153
- const dashboard = getPolicyState(context).getDashboard();
154
- if (dashboard) {
155
- const snap = dashboard.getSnapshot();
156
- const report = snap.report;
157
- const prefixEntries = Object.entries(report.byCommandPrefix);
158
- if (prefixEntries.length > 0) {
159
- context.print(' [divergence by command prefix]');
160
- for (const [prefix, stats] of prefixEntries) {
161
- context.print(` ${prefix}: ${fmtRate(stats.divergenceRate)} (${stats.total}/${stats.totalEvaluations})`);
162
- }
163
- }
164
- const classEntries = Object.entries(report.byToolClass) as Array<[string, DivergenceStats]>;
165
- if (classEntries.length > 0) {
166
- context.print(' [divergence by tool class]');
167
- for (const [cls, stats] of classEntries) {
168
- if (!stats) continue;
169
- context.print(` ${cls}: ${fmtRate(stats.divergenceRate)} (${stats.total}/${stats.totalEvaluations})`);
170
- }
171
- }
172
- }
173
- }
174
-
175
- async function handleLint(_args: string[], context: CommandContext): Promise<void> {
176
- const registry = getRegistry(context);
177
- const current = registry.getCurrent();
178
- const candidate = registry.getCandidate();
179
- if (!current && !candidate) {
180
- context.print('[policy] No policy bundles loaded. Use `/policy load` to begin.');
181
- return;
182
- }
183
- const findings = [
184
- ...(current ? lintPolicyConfig({ mode: 'custom', rules: current.rules }).map((finding) => ({ scope: 'current', ...finding })) : []),
185
- ...(candidate ? lintPolicyConfig({ mode: 'custom', rules: candidate.rules }).map((finding) => ({ scope: 'candidate', ...finding })) : []),
186
- ];
187
- if (findings.length === 0) {
188
- context.print('[policy] No lint findings for the active or candidate bundles.');
189
- return;
190
- }
191
- context.print(`[policy] Lint findings (${findings.length}):`);
192
- for (const finding of findings) {
193
- context.print(` [${finding.scope}] ${finding.severity.toUpperCase()} ${finding.ruleId ? `${finding.ruleId}: ` : ''}${finding.message}`);
194
- }
195
- }
196
-
197
- async function handlePreflight(_args: string[], context: CommandContext): Promise<void> {
198
- const registry = getRegistry(context);
199
- const policyState = getPolicyState(context);
200
- const current = registry.getCurrent();
201
- const candidate = registry.getCandidate();
202
- const lintFindings = [
203
- ...(current ? lintPolicyConfig({ mode: 'custom', rules: current.rules }) : []),
204
- ...(candidate ? lintPolicyConfig({ mode: 'custom', rules: candidate.rules }) : []),
205
- ];
206
- const review = buildPolicyPreflightReview({
207
- config: context.platform.config,
208
- lintFindings,
209
- mcpServers: context.extensions.mcpRegistry.listServerSecurity().map((server) => ({
210
- serverName: server.name,
211
- trustMode: server.trustMode,
212
- role: server.role,
213
- allowedPaths: server.allowedPaths,
214
- allowedHosts: server.allowedHosts,
215
- })),
216
- });
217
- policyState.recordPreflightReview(review);
218
- context.print(`[policy] Preflight review: ${review.status.toUpperCase()} (${review.issueCount} issue${review.issueCount === 1 ? '' : 's'})`);
219
- context.print(`[policy] ${review.summary}`);
220
- for (const issue of review.issues.slice(0, 8)) {
221
- const subject = issue.serverName ? ` ${issue.serverName}` : '';
222
- const detail = issue.detail ? ` — ${issue.detail}` : '';
223
- context.print(` [${issue.severity.toUpperCase()}] ${issue.source}${subject}: ${issue.message}${detail}`);
224
- }
225
- }
226
-
227
- async function handlePromote(args: string[], context: CommandContext): Promise<void> {
228
- const registry = getRegistry(context);
229
- const policyState = getPolicyState(context);
230
- const force = args.includes('--force');
231
- if (force) {
232
- context.print('[policy] WARNING: --force bypasses the divergence gate. This is not safe for production use.');
233
- }
234
- const result = registry.promote(force);
235
- if (!result.ok) {
236
- context.print(`[policy] Promotion blocked: ${result.error}`);
237
- if (result.gate) {
238
- context.print(`[policy] Gate: ${result.gate.status} — divergence rate ${result.gate.divergenceRate !== undefined ? fmtRate(result.gate.divergenceRate) : 'unknown'} (threshold ${fmtRate(result.gate.threshold)}, ${result.gate.totalEvaluations} evaluations).`);
239
- }
240
- return;
241
- }
242
- const current = registry.getCurrent();
243
- context.print(`[policy] Promoted: "${result.bundleId}" is now the active enforcement policy.`);
244
- if (result.gate) {
245
- context.print(`[policy] Gate at promotion: ${result.gate.status} — divergence rate ${result.gate.divergenceRate !== undefined ? fmtRate(result.gate.divergenceRate) : 'unknown'} (threshold ${fmtRate(result.gate.threshold)}).`);
246
- }
247
- if (current) context.print(bundleSummary('[policy] Active bundle', current));
248
- policyState.notify();
249
- }
250
-
251
- async function handleRollback(_args: string[], context: CommandContext): Promise<void> {
252
- const registry = getRegistry(context);
253
- const policyState = getPolicyState(context);
254
- const result = registry.rollback();
255
- if (!result.ok) {
256
- context.print(`[policy] Rollback failed: ${result.error}`);
257
- return;
258
- }
259
- const current = registry.getCurrent();
260
- context.print(`[policy] Rolled back to bundle "${result.restoredBundleId}".`);
261
- if (current) context.print(bundleSummary('[policy] Active bundle', current));
262
- policyState.setDashboard(null);
263
- policyState.notify();
264
- context.print('[policy] Simulation dashboard cleared. Run `/policy simulate` for the restored bundle.');
265
- }
266
-
267
- async function handleStatus(_args: string[], context: CommandContext): Promise<void> {
268
- const registry = getRegistry(context);
269
- const policyState = getPolicyState(context);
270
- const current = registry.getCurrent();
271
- const candidate = registry.getCandidate();
272
- const history = registry.getHistory();
273
- if (!current && !candidate) {
274
- context.print('[policy] No policy bundles loaded. Use `/policy load` to begin.');
275
- return;
276
- }
277
- if (current) context.print(bundleSummary('[policy] Current (enforced)', current));
278
- if (candidate) context.print(bundleSummary('[policy] Candidate', candidate));
279
- context.print(`[policy] History: ${history.length} previous bundle(s).`);
280
- const dashboard = policyState.getDashboard();
281
- if (dashboard) {
282
- const snap = dashboard.getSnapshot();
283
- context.print(`[policy] Divergence gate: ${snap.gate.status} — ${snap.gate.divergenceRate !== undefined ? fmtRate(snap.gate.divergenceRate) : fmtRate(snap.report.overall.divergenceRate)} (${snap.report.overall.totalEvaluations} evaluations).`);
284
- } else {
285
- context.print('[policy] No active simulation dashboard.');
286
- }
287
- }
288
-
289
- export function renderPolicyUsage(): string {
290
- return [
291
- 'Usage: /policy <subcommand>',
292
- ' /policy status — show current policy state',
293
- ' load <bundle-id> [rule-count] — Load a candidate bundle',
294
- ' simulate [mode] — Run simulation (silent|warn|enforce)',
295
- ' diff — Show rule diff (current vs candidate)',
296
- ' lint — Lint active and candidate bundles',
297
- ' preflight — Review proactive policy and MCP risk state',
298
- ' promote [--force] — Promote candidate to enforcement',
299
- ' rollback — Restore the previous active bundle',
300
- ' status — Show current policy state',
301
- ].join('\n');
302
- }
303
-
304
- export async function dispatchPolicyCommand(args: string[], context: CommandContext): Promise<void> {
305
- const [sub, ...rest] = args;
306
- switch (sub) {
307
- case 'load':
308
- await handleLoad(rest, context);
309
- break;
310
- case 'simulate':
311
- case 'sim':
312
- await handleSimulate(rest, context);
313
- break;
314
- case 'diff':
315
- await handleDiff(rest, context);
316
- break;
317
- case 'lint':
318
- await handleLint(rest, context);
319
- break;
320
- case 'preflight':
321
- case 'pf':
322
- await handlePreflight(rest, context);
323
- break;
324
- case 'promote':
325
- await handlePromote(rest, context);
326
- break;
327
- case 'rollback':
328
- case 'rb':
329
- await handleRollback(rest, context);
330
- break;
331
- case 'status':
332
- case 'st':
333
- await handleStatus(rest, context);
334
- break;
335
- default:
336
- context.print(renderPolicyUsage());
337
- break;
338
- }
339
- }
@@ -1,13 +0,0 @@
1
- import type { SlashCommand, CommandContext } from '../command-registry.ts';
2
- import { dispatchPolicyCommand } from './policy-dispatch.ts';
3
-
4
- export const policyCommand: SlashCommand = {
5
- name: 'policy',
6
- aliases: ['pol'],
7
- description: 'Review or manage versioned policy bundles (load, simulate, diff, promote, rollback).',
8
- usage: '<subcommand> [args]',
9
- argsHint: 'load|simulate|diff|lint|preflight|promote|rollback|status',
10
- handler: async (args: string[], context: CommandContext): Promise<void> => {
11
- await dispatchPolicyCommand(args, context);
12
- },
13
- };
@@ -1,105 +0,0 @@
1
- import type { PanelRegistration, PanelCategory } from './types.ts';
2
-
3
- /** Display order for panel categories. */
4
- const CATEGORY_ORDER: PanelCategory[] = ['agent', 'monitoring', 'session', 'ai'];
5
-
6
- /** Human-readable labels for each category. */
7
- const CATEGORY_LABELS: Record<PanelCategory, string> = {
8
- agent: 'Agent',
9
- monitoring: 'Monitoring',
10
- session: 'Session',
11
- ai: 'AI',
12
- };
13
-
14
- /**
15
- * Modal state for browsing and selecting panel registrations.
16
- * The filtered list is a flat list of PanelRegistration items matching the
17
- * current search query. Category headers are rendered by the overlay, not
18
- * stored here — keeping this class pure data/logic.
19
- */
20
- export class PanelPicker {
21
- public active: boolean = false;
22
- public selectedIndex: number = 0;
23
- public searchQuery: string = '';
24
- private items: PanelRegistration[] = [];
25
- private filtered: PanelRegistration[] = [];
26
-
27
- /** Open the picker with the given set of registered panels. */
28
- open(registrations: PanelRegistration[]): void {
29
- this.items = registrations;
30
- this.searchQuery = '';
31
- this.filtered = this._applyFilter('');
32
- this.selectedIndex = 0;
33
- this.active = true;
34
- }
35
-
36
- /** Close and reset the picker. */
37
- close(): void {
38
- this.active = false;
39
- this.searchQuery = '';
40
- this.selectedIndex = 0;
41
- }
42
-
43
- /**
44
- * Filter the list by name or description (case-insensitive).
45
- * Resets selectedIndex to 0.
46
- */
47
- search(query: string): void {
48
- this.searchQuery = query;
49
- this.filtered = this._applyFilter(query);
50
- this.selectedIndex = 0;
51
- }
52
-
53
- /** Move selection up by one, wrapping at top. */
54
- moveUp(): void {
55
- if (this.filtered.length === 0) return;
56
- this.selectedIndex = this.selectedIndex <= 0
57
- ? this.filtered.length - 1
58
- : this.selectedIndex - 1;
59
- }
60
-
61
- /** Move selection down by one, wrapping at bottom. */
62
- moveDown(): void {
63
- if (this.filtered.length === 0) return;
64
- this.selectedIndex = this.selectedIndex >= this.filtered.length - 1
65
- ? 0
66
- : this.selectedIndex + 1;
67
- }
68
-
69
- /**
70
- * Returns the filtered list, sorted by category order then name.
71
- * The overlay is responsible for inserting category headers between groups.
72
- */
73
- getVisible(): PanelRegistration[] {
74
- return this.filtered;
75
- }
76
-
77
- /** Returns the currently selected registration, or null if the list is empty. */
78
- getSelected(): PanelRegistration | null {
79
- if (this.filtered.length === 0) return null;
80
- return this.filtered[this.selectedIndex] ?? null;
81
- }
82
-
83
- // ── Private ──────────────────────────────────────────────────────────────
84
-
85
- private _applyFilter(query: string): PanelRegistration[] {
86
- const q = query.trim().toLowerCase();
87
- const source = q
88
- ? this.items.filter(
89
- r =>
90
- r.name.toLowerCase().includes(q) ||
91
- r.description.toLowerCase().includes(q),
92
- )
93
- : [...this.items];
94
-
95
- // Sort by category order, then alphabetically within each category
96
- return source.sort((a, b) => {
97
- const ai = CATEGORY_ORDER.indexOf(a.category);
98
- const bi = CATEGORY_ORDER.indexOf(b.category);
99
- if (ai !== bi) return ai - bi;
100
- return a.name.localeCompare(b.name);
101
- });
102
- }
103
- }
104
-
105
- export { CATEGORY_LABELS, CATEGORY_ORDER };