@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
@@ -38,6 +38,7 @@ import { registerChannelsRuntimeCommands } from './commands/channels-runtime.ts'
38
38
  import { registerBriefRuntimeCommands } from './commands/brief-runtime.ts';
39
39
  import { registerSupportBundleRuntimeCommands } from './commands/support-bundle-runtime.ts';
40
40
  import { registerCompatRuntimeCommands } from './commands/compat-runtime.ts';
41
+ import { registerOperatorActionRuntimeCommands } from './commands/operator-actions-runtime.ts';
41
42
 
42
43
  function registerAgentMemoryCommand(registry: CommandRegistry): void {
43
44
  registry.register({
@@ -62,6 +63,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
62
63
  registerBriefRuntimeCommands(registry);
63
64
  registerSupportBundleRuntimeCommands(registry);
64
65
  registerCompatRuntimeCommands(registry);
66
+ registerOperatorActionRuntimeCommands(registry);
65
67
  registerAgentRuntimeProfileRuntimeCommands(registry);
66
68
  registerPersonasRuntimeCommands(registry);
67
69
  registerAgentSkillsRuntimeCommands(registry);
@@ -70,7 +72,6 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
70
72
  registerDelegationRuntimeCommands(registry);
71
73
  registerConfigCommand(registry);
72
74
  registerOperatorRuntimeCommands(registry);
73
- registry.unregister('profiles');
74
75
  registerNotifyRuntimeCommands(registry);
75
76
  registerProductRuntimeCommands(registry);
76
77
  registerPlatformAccessRuntimeCommands(registry);
@@ -93,7 +94,6 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
93
94
  registerPlanningRuntimeCommands(registry);
94
95
  registerScheduleRuntimeCommands(registry);
95
96
  registerSessionContentCommands(registry);
96
- registry.unregister('session-memory');
97
97
  registerAgentMemoryCommand(registry);
98
98
 
99
99
  // ── /session ─────────────────────────────────────────────────────────────
@@ -147,7 +147,8 @@ export interface FeedContextClosures {
147
147
  ensureInputCursorVisible: (contentWidth?: number) => void;
148
148
  registerPaste: (content: string) => string;
149
149
  executeBlockAction: (id: string) => void;
150
- cyclePanelTab: (direction: 'next' | 'prev') => void;
150
+ cycleAgentWorkspaceCategory: (direction: 'next' | 'prev') => void;
151
+ dismissAgentWorkspace: () => boolean;
151
152
  onPanelInputConsumed: (activePanel: Panel | null, key: string) => void;
152
153
  getWrappedPromptInfo: (contentWidth: number) => WrappedPromptInfo;
153
154
  moveCursorVertical: (direction: -1 | 1) => boolean;
@@ -1,3 +1,4 @@
1
+ import type { Dirent } from 'node:fs';
1
2
  import { readdir } from 'node:fs/promises';
2
3
  import { join, relative } from 'node:path';
3
4
  import type { ShellPathService } from '@/runtime/index.ts';
@@ -160,9 +161,9 @@ export class FilePickerModal {
160
161
  if (depth > 8) return; // Limit depth
161
162
  if (files.length > 5000) return; // Limit total files
162
163
 
163
- let entries: import('node:fs').Dirent[];
164
+ let entries: Dirent[];
164
165
  try {
165
- entries = (await readdir(dir, { withFileTypes: true })) as unknown as import('node:fs').Dirent[];
166
+ entries = await readdir(dir, { withFileTypes: true });
166
167
  } catch {
167
168
  return;
168
169
  }
@@ -106,7 +106,7 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
106
106
  if (skillContent) {
107
107
  state.commandContext?.submitInput?.(skillContent);
108
108
  } else {
109
- state.conversationManager?.log(`Unknown command: /${name}. Type /help for available commands.`, { fg: '#ef4444' });
109
+ state.conversationManager?.log(`Unknown command /${name}. Type /help for available commands.`, { fg: '#ef4444' });
110
110
  state.requestRender();
111
111
  }
112
112
  }
@@ -121,27 +121,24 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
121
121
  }
122
122
 
123
123
  function withPanelFocusSync(context: CommandContext, state: CommandModeRouteState): CommandContext {
124
- const panelIsFocusable = (): boolean =>
125
- state.panelManager.isVisible() && state.panelManager.getAllOpen().length > 0;
126
-
127
124
  return {
128
125
  ...context,
129
126
  openPanelPicker: context.openPanelPicker
130
127
  ? () => {
131
128
  context.openPanelPicker?.();
132
- state.panelFocused = panelIsFocusable();
129
+ state.panelFocused = false;
133
130
  }
134
131
  : undefined,
135
132
  showPanel: context.showPanel
136
133
  ? (panelId, pane) => {
137
134
  context.showPanel?.(panelId, pane);
138
- state.panelFocused = true;
135
+ state.panelFocused = false;
139
136
  }
140
137
  : undefined,
141
138
  focusPanels: context.focusPanels
142
139
  ? () => {
143
140
  context.focusPanels?.();
144
- state.panelFocused = panelIsFocusable();
141
+ state.panelFocused = false;
145
142
  }
146
143
  : undefined,
147
144
  focusPrompt: context.focusPrompt
@@ -1,4 +1,5 @@
1
- import { readFileSync, existsSync } from 'node:fs';
1
+ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { basename, relative } from 'node:path';
2
3
  import { copyToClipboard, pasteFromClipboard, pasteImageFromClipboard } from '../utils/clipboard.ts';
3
4
  import type { InfiniteBuffer } from '../core/history.ts';
4
5
  import type { ConversationManager } from '../core/conversation';
@@ -30,6 +31,11 @@ export const BINARY_IMAGE_MAGIC: {
30
31
  ];
31
32
 
32
33
  export const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp', '.gif'];
34
+ const MAX_CONTEXT_FILE_BYTES = 64 * 1024;
35
+ const MAX_CONTEXT_DIRECTORY_ENTRIES = 200;
36
+ const URL_REFERENCE_REGEX = /^https?:\/\/[^\s]+$/i;
37
+ const CONTEXT_REFERENCE_REGEX = /(^|\s)@([^\s]+)/g;
38
+ const CONTEXT_REFERENCE_TRAILING_PUNCTUATION = /[),.;:!?]+$/;
33
39
 
34
40
  export function formatFileSize(bytes: number): string {
35
41
  if (bytes < 1024) return `${bytes}B`;
@@ -46,6 +52,86 @@ export function mediaTypeFromExt(ext: string): string {
46
52
  }
47
53
  }
48
54
 
55
+ function stripTrailingReferencePunctuation(value: string): { readonly core: string; readonly suffix: string } {
56
+ const match = CONTEXT_REFERENCE_TRAILING_PUNCTUATION.exec(value);
57
+ if (!match) return { core: value, suffix: '' };
58
+ return {
59
+ core: value.slice(0, -match[0].length),
60
+ suffix: match[0],
61
+ };
62
+ }
63
+
64
+ function escapeContextAttribute(value: string): string {
65
+ return value
66
+ .replace(/&/g, '&amp;')
67
+ .replace(/"/g, '&quot;')
68
+ .replace(/</g, '&lt;')
69
+ .replace(/>/g, '&gt;');
70
+ }
71
+
72
+ function readContextFileBlock(reference: string, projectRoot: string): string | null {
73
+ let resolvedPath: string;
74
+ try {
75
+ resolvedPath = resolveAndValidatePath(reference, projectRoot);
76
+ } catch (error) {
77
+ logger.debug('expandPrompt: context reference rejected', { reference, error: summarizeError(error) });
78
+ return null;
79
+ }
80
+ if (!existsSync(resolvedPath)) return null;
81
+
82
+ const stat = lstatSync(resolvedPath);
83
+ const label = escapeContextAttribute(relative(projectRoot, resolvedPath) || basename(resolvedPath));
84
+ if (stat.isDirectory()) {
85
+ const entries = readdirSync(resolvedPath, { withFileTypes: true })
86
+ .slice(0, MAX_CONTEXT_DIRECTORY_ENTRIES)
87
+ .map((entry) => `${entry.isDirectory() ? 'dir ' : 'file'} ${entry.name}`)
88
+ .join('\n');
89
+ return [
90
+ `<context-folder path="${label}">`,
91
+ entries || '(empty directory)',
92
+ stat.isDirectory() ? `</context-folder>` : '',
93
+ ].filter(Boolean).join('\n');
94
+ }
95
+
96
+ if (!stat.isFile()) return null;
97
+ if (stat.size > MAX_CONTEXT_FILE_BYTES) {
98
+ return [
99
+ `<context-file path="${label}" truncated="true" bytes="${stat.size}">`,
100
+ readFileSync(resolvedPath, 'utf-8').slice(0, MAX_CONTEXT_FILE_BYTES),
101
+ '</context-file>',
102
+ ].join('\n');
103
+ }
104
+
105
+ return [
106
+ `<context-file path="${label}" bytes="${stat.size}">`,
107
+ readFileSync(resolvedPath, 'utf-8'),
108
+ '</context-file>',
109
+ ].join('\n');
110
+ }
111
+
112
+ function buildContextUrlBlock(reference: string): string {
113
+ const href = escapeContextAttribute(reference);
114
+ return [
115
+ `<context-url href="${href}">`,
116
+ 'The user referenced this URL in the prompt. Use connected tools only if content retrieval is needed; do not ingest it into Agent Knowledge unless the user explicitly asks.',
117
+ '</context-url>',
118
+ ].join('\n');
119
+ }
120
+
121
+ function expandContextReferences(text: string, projectRoot: string): string {
122
+ return text.replace(CONTEXT_REFERENCE_REGEX, (full: string, prefix: string, rawReference: string): string => {
123
+ if (rawReference.startsWith('model:')) return full;
124
+ const { core, suffix } = stripTrailingReferencePunctuation(rawReference);
125
+ if (!core || core.startsWith('@')) return full;
126
+ if (URL_REFERENCE_REGEX.test(core)) {
127
+ return `${prefix}${buildContextUrlBlock(core)}${suffix}`;
128
+ }
129
+ const fileBlock = readContextFileBlock(core, projectRoot);
130
+ if (!fileBlock) return full;
131
+ return `${prefix}${fileBlock}${suffix}`;
132
+ });
133
+ }
134
+
49
135
  export type PasteRegistryState = {
50
136
  pasteRegistry: Map<string, string>;
51
137
  nextPasteId: number;
@@ -171,6 +257,8 @@ export function expandPrompt(
171
257
  }
172
258
  }
173
259
 
260
+ expanded = expandContextReferences(expanded, projectRoot);
261
+
174
262
  const imageMarkerRegex = /\[IMAGE: (img\d+), [^\]]+\]/g;
175
263
  const imageMarkers: { marker: string; index: number; id: string }[] = [];
176
264
  let imgMatch;
@@ -25,7 +25,8 @@ export type PanelFocusRouteState = {
25
25
  autocompleteActive: boolean;
26
26
  requestRender: () => void;
27
27
  handlePathCompletion: () => boolean;
28
- cyclePanelTab: (direction: 'next' | 'prev') => void;
28
+ cycleAgentWorkspaceCategory: (direction: 'next' | 'prev') => void;
29
+ dismissAgentWorkspace: () => boolean;
29
30
  onPanelInputConsumed?: (activePanel: import('../panels/types.ts').Panel | null, key: string) => void;
30
31
  };
31
32
 
@@ -42,16 +43,16 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
42
43
  !state.searchActive &&
43
44
  !state.autocompleteActive
44
45
  ) {
45
- const pm = state.panelManager;
46
- if (pm.isVisible() && pm.getAllOpen().length > 0) {
47
- if (panelFocused) {
48
- panelFocused = false;
49
- } else if (!state.handlePathCompletion()) {
50
- panelFocused = true;
51
- }
46
+ if (panelFocused) {
47
+ panelFocused = false;
48
+ state.requestRender();
49
+ return { handled: true, panelFocused };
50
+ }
51
+ if (state.handlePathCompletion()) {
52
52
  state.requestRender();
53
53
  return { handled: true, panelFocused };
54
54
  }
55
+ return { handled: false, panelFocused };
55
56
  }
56
57
 
57
58
  if (!panelFocused) {
@@ -76,14 +77,17 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
76
77
  }
77
78
  const kb = state.keybindingsManager;
78
79
  if (kb.matches('panel-tab-next', token)) {
79
- state.cyclePanelTab('next');
80
- return { handled: true, panelFocused };
80
+ state.cycleAgentWorkspaceCategory('next');
81
+ return { handled: true, panelFocused: false };
81
82
  }
82
83
  if (kb.matches('panel-tab-prev', token)) {
83
- state.cyclePanelTab('prev');
84
- return { handled: true, panelFocused };
84
+ state.cycleAgentWorkspaceCategory('prev');
85
+ return { handled: true, panelFocused: false };
85
86
  }
86
87
  if (kb.matches('panel-close-all', token)) {
88
+ if (state.dismissAgentWorkspace()) {
89
+ return { handled: true, panelFocused: false };
90
+ }
87
91
  const pm = state.panelManager;
88
92
  for (const p of pm.getAllOpen()) pm.close(p.id);
89
93
  panelFocused = false;
@@ -91,6 +95,9 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
91
95
  return { handled: true, panelFocused };
92
96
  }
93
97
  if (kb.matches('panel-close', token)) {
98
+ if (state.dismissAgentWorkspace()) {
99
+ return { handled: true, panelFocused: false };
100
+ }
94
101
  const pm = state.panelManager;
95
102
  const active = pm.getActivePanel();
96
103
  if (active) {
@@ -148,7 +148,8 @@ export interface InputFeedContext {
148
148
  readonly ensureInputCursorVisible: (contentWidth?: number) => void;
149
149
  readonly registerPaste: (content: string) => string;
150
150
  readonly executeBlockAction: (id: string) => void;
151
- readonly cyclePanelTab: (direction: 'next' | 'prev') => void;
151
+ readonly cycleAgentWorkspaceCategory: (direction: 'next' | 'prev') => void;
152
+ readonly dismissAgentWorkspace: () => boolean;
152
153
  readonly onPanelInputConsumed: (activePanel: Panel | null, key: string) => void;
153
154
  readonly getWrappedPromptInfo: (contentWidth: number) => WrappedPromptInfo;
154
155
  readonly moveCursorVertical: (direction: -1 | 1) => boolean;
@@ -275,7 +276,8 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
275
276
  handleRedo: context.handleRedo,
276
277
  handlePaste: context.handlePaste,
277
278
  handleEscape: context.handleEscape,
278
- cyclePanelTab: context.cyclePanelTab,
279
+ cycleAgentWorkspaceCategory: context.cycleAgentWorkspaceCategory,
280
+ dismissAgentWorkspace: context.dismissAgentWorkspace,
279
281
  panelManager: context.panelManager,
280
282
  keybindingsManager: context.keybindingsManager,
281
283
  };
@@ -295,7 +297,8 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
295
297
  autocompleteActive: !!context.autocomplete?.isActive,
296
298
  requestRender: context.requestRender,
297
299
  handlePathCompletion: context.handlePathCompletion,
298
- cyclePanelTab: context.cyclePanelTab,
300
+ cycleAgentWorkspaceCategory: context.cycleAgentWorkspaceCategory,
301
+ dismissAgentWorkspace: context.dismissAgentWorkspace,
299
302
  panelManager: context.panelManager,
300
303
  keybindingsManager: context.keybindingsManager,
301
304
  onPanelInputConsumed: context.onPanelInputConsumed,
@@ -1,4 +1,4 @@
1
- import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
1
+ import { buildProviderAccountSnapshot } from '../panels/provider-account-snapshot.ts';
2
2
  import type { OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
3
3
  import { collectOnboardingSnapshot } from '../runtime/onboarding/index.ts';
4
4
  import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, handleBlockCopy, handleBlockRerun, handleBlockSave, handleBlockToggle, handleBookmark, handleClipboardPaste, handleCopy, handleCtrlC, registerPaste } from './handler-content-actions.ts';
@@ -37,10 +37,12 @@ export async function hydrateOnboardingWizardFromRuntimeForHandler(handler: Inpu
37
37
  },
38
38
  providerAccounts: {
39
39
  loadSnapshot: () => buildProviderAccountSnapshot({
40
- providerRegistry: handler.uiServices.providers.providerRegistry,
41
- serviceRegistry: handler.uiServices.platform.serviceRegistry,
42
- subscriptionManager: handler.uiServices.platform.subscriptionManager,
43
- secretsManager: handler.uiServices.platform.secretsManager,
40
+ providerModels: handler.uiServices.providers.providerRegistry,
41
+ services: handler.uiServices.platform.serviceRegistry,
42
+ subscriptions: handler.uiServices.platform.subscriptionManager,
43
+ environment: {
44
+ hasEnvironmentVariable: (name: string) => Boolean(process.env[name]),
45
+ },
44
46
  }),
45
47
  },
46
48
  });
@@ -8,7 +8,7 @@ import type { SelectionResult } from './selection-modal.ts';
8
8
 
9
9
  export type ModalStackState = ActiveModalState & {
10
10
  modalStack: string[];
11
- modalReturnFocus?: 'prompt' | 'panel' | 'indicator';
11
+ modalReturnFocus?: 'prompt' | 'indicator';
12
12
  panelFocused: boolean;
13
13
  indicatorFocused: boolean;
14
14
  };
@@ -20,7 +20,7 @@ export function modalOpened(state: ModalStackState, name: string): void {
20
20
  if (state.modalStack[state.modalStack.length - 1] === name) return;
21
21
  if (state.modalStack.includes(name)) return;
22
22
  if (state.modalStack.length === 0) {
23
- state.modalReturnFocus = state.indicatorFocused ? 'indicator' : state.panelFocused ? 'panel' : 'prompt';
23
+ state.modalReturnFocus = state.indicatorFocused ? 'indicator' : 'prompt';
24
24
  }
25
25
  state.modalStack.push(name);
26
26
  }
@@ -86,7 +86,7 @@ export function handleEscape(state: EscapeState): {
86
86
  shortcutsOverlayActive,
87
87
  commandMode,
88
88
  }) !== null) return;
89
- panelFocused = modalReturnFocus === 'panel';
89
+ panelFocused = false;
90
90
  indicatorFocused = modalReturnFocus === 'indicator';
91
91
  modalReturnFocus = 'prompt';
92
92
  state.modalReturnFocus = 'prompt';
@@ -1,8 +1,7 @@
1
- import { createOAuthLocalListener } from '@pellux/goodvibes-sdk/platform/config';
2
1
  import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config';
3
2
  import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils';
4
3
  import { getProviderIdFromModel } from '../config/provider-model.ts';
5
- import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
4
+ import { buildProviderAccountSnapshot } from '../panels/provider-account-snapshot.ts';
6
5
  import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardApplyFeedback } from './onboarding/onboarding-wizard.ts';
7
6
  import { applyOnboardingRequest, collectOnboardingSnapshot, verifyOnboardingRequest, writeOnboardingCheckMarker } from '../runtime/onboarding/index.ts';
8
7
  import type { OnboardingApplyRequest, OnboardingVerificationItem } from '../runtime/onboarding/index.ts';
@@ -156,6 +155,17 @@ export function handleModelPickerCommitForHandler(handler: InputHandler): boolea
156
155
  }
157
156
 
158
157
  export async function handleOnboardingActionForHandler(handler: InputHandler, action: OnboardingWizardAction): Promise<void> {
158
+ if (action.startsWith('open-agent-workspace:')) {
159
+ const categoryId = action.slice('open-agent-workspace:'.length);
160
+ if (handler.commandContext) handler.openAgentWorkspace(handler.commandContext, categoryId);
161
+ else handler.onboardingWizard.setApplyFeedback({
162
+ severity: 'warning',
163
+ title: 'Workspace unavailable',
164
+ summary: 'The Agent workspace cannot open because the command context is not wired yet.',
165
+ messages: [`Requested workspace: ${categoryId}`],
166
+ });
167
+ return;
168
+ }
159
169
  if (action === 'start-openai-subscription') {
160
170
  await handler.handleOpenAiSubscriptionStart();
161
171
  return;
@@ -256,7 +266,7 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
256
266
  }
257
267
  if (handler.modalStack.length === 0) {
258
268
  const returnFocus = handler.modalReturnFocus;
259
- handler.panelFocused = returnFocus === 'panel';
269
+ handler.panelFocused = false;
260
270
  handler.indicatorFocused = returnFocus === 'indicator';
261
271
  handler.modalReturnFocus = 'prompt';
262
272
  }
@@ -292,10 +302,12 @@ export async function refreshOnboardingHydrationForHandler(handler: InputHandler
292
302
  },
293
303
  providerAccounts: {
294
304
  loadSnapshot: () => buildProviderAccountSnapshot({
295
- providerRegistry: handler.uiServices.providers.providerRegistry,
296
- serviceRegistry: handler.uiServices.platform.serviceRegistry,
297
- subscriptionManager: handler.uiServices.platform.subscriptionManager,
298
- secretsManager: handler.uiServices.platform.secretsManager,
305
+ providerModels: handler.uiServices.providers.providerRegistry,
306
+ services: handler.uiServices.platform.serviceRegistry,
307
+ subscriptions: handler.uiServices.platform.subscriptionManager,
308
+ environment: {
309
+ hasEnvironmentVariable: (name: string) => Boolean(process.env[name]),
310
+ },
299
311
  }),
300
312
  },
301
313
  });
@@ -317,7 +329,6 @@ export async function refreshOnboardingHydrationForHandler(handler: InputHandler
317
329
  export async function handleOpenAiSubscriptionStartForHandler(handler: InputHandler): Promise<void> {
318
330
  if (handler.onboardingApplyPending) return;
319
331
  handler.onboardingApplyPending = true;
320
- let listener: Awaited<ReturnType<typeof createOAuthLocalListener>> | null = null;
321
332
  try {
322
333
  const started = await beginOpenAICodexLogin();
323
334
  handler.uiServices.platform.subscriptionManager.savePending({
@@ -327,12 +338,6 @@ export async function handleOpenAiSubscriptionStartForHandler(handler: InputHand
327
338
  redirectUri: started.redirectUri,
328
339
  createdAt: Date.now(),
329
340
  });
330
- listener = await createOAuthLocalListener({
331
- expectedState: started.state,
332
- host: '127.0.0.1',
333
- port: 1455,
334
- path: '/auth/callback',
335
- }).catch(() => null);
336
341
  const browserOpened = await openExternalUrl(started.authorizationUrl);
337
342
  await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'provider-access' });
338
343
  handler.onboardingWizard.setFieldValue('providers.openai-authorization-url', started.authorizationUrl);
@@ -340,31 +345,14 @@ export async function handleOpenAiSubscriptionStartForHandler(handler: InputHand
340
345
  if (providerIndex >= 0) handler.onboardingWizard.setStep(providerIndex);
341
346
  handler.requestRender();
342
347
 
343
- if (listener && browserOpened) {
344
- const serial = ++handler.onboardingOpenAiListenerSerial;
345
- handler.commandContext?.print?.([
346
- 'OpenAI subscription sign-in started from onboarding.',
347
- ' callback listener: waiting on 127.0.0.1:1455',
348
- ' authorizationUrl: shown in the wizard provider step',
349
- 'You can also paste the callback code or URL into the OpenAI callback field.',
350
- ].join('\n'));
351
- void handler.completeOpenAiSubscriptionFromListener(listener, started.verifier, serial);
352
- listener = null;
353
- handler.requestRender();
354
- return;
355
- }
356
-
357
- listener?.close();
358
348
  handler.commandContext?.print?.([
359
349
  'OpenAI subscription sign-in started from onboarding.',
360
350
  ` browser: ${browserOpened ? 'opened' : 'open failed'}`,
361
- ` callback listener: ${listener ? 'ready' : 'unavailable'}`,
351
+ ' completion: paste callback code or URL into the OpenAI callback field',
362
352
  ' authorizationUrl: shown in the wizard provider step',
363
- 'Paste the callback code or URL into the OpenAI callback field after sign-in.',
364
353
  ].join('\n'));
365
354
  handler.requestRender();
366
355
  } catch (error) {
367
- listener?.close();
368
356
  handler.commandContext?.print?.([
369
357
  'OpenAI subscription sign-in could not start.',
370
358
  ` ${error instanceof Error ? error.message : String(error)}`,
@@ -375,51 +363,6 @@ export async function handleOpenAiSubscriptionStartForHandler(handler: InputHand
375
363
  }
376
364
  }
377
365
 
378
- export async function completeOpenAiSubscriptionFromListenerForHandler(
379
- handler: InputHandler,
380
- listener: Awaited<ReturnType<typeof createOAuthLocalListener>>,
381
- verifier: string,
382
- serial: number,
383
- ): Promise<void> {
384
- try {
385
- const callback = await listener.waitForCode();
386
- const pending = handler.uiServices.platform.subscriptionManager.getPending('openai');
387
- if (!pending || pending.verifier !== verifier || serial !== handler.onboardingOpenAiListenerSerial) return;
388
- const token = await exchangeOpenAICodexCode(callback.code, verifier);
389
- const now = Date.now();
390
- const existing = handler.uiServices.platform.subscriptionManager.get('openai');
391
- handler.uiServices.platform.subscriptionManager.saveSubscription({
392
- provider: 'openai',
393
- accessToken: token.accessToken,
394
- refreshToken: token.refreshToken,
395
- tokenType: token.tokenType,
396
- expiresAt: token.expiresAt,
397
- ...(token.scopes ? { scopes: token.scopes } : {}),
398
- authMode: 'oauth',
399
- overrideAmbientApiKeys: false,
400
- createdAt: existing?.createdAt ?? now,
401
- updatedAt: now,
402
- });
403
- handler.uiServices.platform.subscriptionManager.clearPending('openai');
404
- handler.onboardingOpenAiListenerSerial += 1;
405
- handler.commandContext?.print?.([
406
- 'OpenAI subscription sign-in completed from onboarding.',
407
- ` tokenType: ${token.tokenType}`,
408
- ` expiresAt: ${token.expiresAt ? new Date(token.expiresAt).toISOString() : 'n/a'}`,
409
- ].join('\n'));
410
- await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'provider-access' });
411
- } catch (error) {
412
- handler.commandContext?.print?.([
413
- 'OpenAI subscription listener could not complete automatically.',
414
- ` listener: ${error instanceof Error ? error.message : String(error)}`,
415
- 'Paste the callback code or URL into the OpenAI callback field to finish in onboarding.',
416
- ].join('\n'));
417
- handler.requestRender();
418
- } finally {
419
- listener.close();
420
- }
421
- }
422
-
423
366
  export async function handleOpenAiSubscriptionFinishForHandler(handler: InputHandler): Promise<void> {
424
367
  if (handler.onboardingApplyPending) return;
425
368
  const code = extractAuthorizationCode(handler.onboardingWizard.getTextFieldValue('providers.openai-callback-code'));
@@ -486,12 +429,13 @@ export function syncRuntimeFromOnboardingRequestForHandler(handler: InputHandler
486
429
  }
487
430
 
488
431
  export function getOnboardingConfigValueForHandler(handler: InputHandler, request: OnboardingApplyRequest, key: string): unknown {
489
- const config = handler.uiServices.platform.configManager;
432
+ const configManager: object = handler.uiServices.platform.configManager;
490
433
  for (let index = request.operations.length - 1; index >= 0; index -= 1) {
491
434
  const operation = request.operations[index];
492
435
  if (operation?.kind === 'set-config' && operation.key === key) return operation.value;
493
436
  }
494
- return config.get(key as never);
437
+ const get = Reflect.get(configManager, 'get');
438
+ return typeof get === 'function' ? Reflect.apply(get, configManager, [key]) : undefined;
495
439
  }
496
440
 
497
441
  export function getOnboardingRuntimePostureForHandler(handler: InputHandler, request: OnboardingApplyRequest): OnboardingRuntimePosture {
@@ -504,7 +448,7 @@ export function getOnboardingRuntimePostureForHandler(handler: InputHandler, req
504
448
  export async function restartOnboardingExternalServicesIfNeededForHandler(handler: InputHandler, request: OnboardingApplyRequest): Promise<OnboardingVerificationItem[]> {
505
449
  const externalServices = handler.uiServices.platform.externalServices;
506
450
  const state = externalServices?.inspect();
507
- const hostStatus = state?.daemonStatus?.reason ?? (state?.daemonRunning ? 'connected GoodVibes host appears active' : 'connected GoodVibes host is not verified from this shell');
451
+ const hostStatus = state?.connectedHostStatus?.reason ?? (state?.connectedHostRunning ? 'connected GoodVibes host appears active' : 'connected GoodVibes host is not verified from this shell');
508
452
  return [{
509
453
  id: 'runtime:external-host-owned',
510
454
  status: 'pass',
@@ -41,7 +41,8 @@ export type GlobalShortcutRouteState = {
41
41
  handleRedo: () => void;
42
42
  handlePaste: () => void;
43
43
  handleEscape: () => void;
44
- cyclePanelTab: (direction: 'next' | 'prev') => void;
44
+ cycleAgentWorkspaceCategory: (direction: 'next' | 'prev') => void;
45
+ dismissAgentWorkspace: () => boolean;
45
46
  };
46
47
 
47
48
  export function handleGlobalShortcutToken(
@@ -86,6 +87,10 @@ export function handleGlobalShortcutToken(
86
87
  return true;
87
88
 
88
89
  case 'panel-close-all': {
90
+ if (state.dismissAgentWorkspace()) {
91
+ state.panelFocused = false;
92
+ return true;
93
+ }
89
94
  const pm = state.panelManager;
90
95
  for (const p of pm.getAllOpen()) pm.close(p.id);
91
96
  pm.hide();
@@ -95,6 +100,10 @@ export function handleGlobalShortcutToken(
95
100
  }
96
101
 
97
102
  case 'panel-close': {
103
+ if (state.dismissAgentWorkspace()) {
104
+ state.panelFocused = false;
105
+ return true;
106
+ }
98
107
  const pm = state.panelManager;
99
108
  const active = pm.getActivePanel();
100
109
  if (active) {
@@ -107,16 +116,18 @@ export function handleGlobalShortcutToken(
107
116
 
108
117
  case 'panel-picker':
109
118
  state.commandContext?.openPanelPicker?.();
110
- state.panelFocused = state.panelManager.isVisible() && state.panelManager.getAllOpen().length > 0;
119
+ state.panelFocused = false;
111
120
  state.requestRender();
112
121
  return true;
113
122
 
114
123
  case 'panel-tab-next':
115
- state.cyclePanelTab('next');
124
+ state.cycleAgentWorkspaceCategory('next');
125
+ state.panelFocused = false;
116
126
  return true;
117
127
 
118
128
  case 'panel-tab-prev':
119
- state.cyclePanelTab('prev');
129
+ state.cycleAgentWorkspaceCategory('prev');
130
+ state.panelFocused = false;
120
131
  return true;
121
132
 
122
133
  case 'history-search':