@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
@@ -27,6 +27,40 @@ export function buildAgentWorkspaceChannelCommandEditorSubmission(
27
27
  editor: AgentWorkspaceLocalEditor,
28
28
  readField: AgentWorkspaceFieldReader,
29
29
  ): AgentWorkspaceChannelCommandEditorSubmission {
30
+ if (editor.kind === 'channel-send') {
31
+ if (!/^(y|yes|true)$/i.test(readField('confirm').trim())) {
32
+ return {
33
+ kind: 'editor',
34
+ editor: { ...editor, message: 'Channel delivery not confirmed. Type yes, then press Enter.' },
35
+ status: 'Channel delivery not confirmed.',
36
+ };
37
+ }
38
+ const parts = ['/channels', 'send'];
39
+ const title = readField('title');
40
+ const channel = readField('channel');
41
+ const route = readField('route');
42
+ const webhook = readField('webhook');
43
+ const link = readField('link');
44
+ if (title) parts.push('--title', quoteSlashCommandArg(title));
45
+ if (channel) parts.push('--channel', quoteSlashCommandArg(channel));
46
+ if (route) parts.push('--route', quoteSlashCommandArg(route));
47
+ if (webhook) parts.push('--webhook', quoteSlashCommandArg(webhook));
48
+ if (link) parts.push('--link', quoteSlashCommandArg(link));
49
+ parts.push('--message', quoteSlashCommandArg(readField('message')), '--yes');
50
+ const command = parts.join(' ');
51
+ return {
52
+ kind: 'dispatch',
53
+ command,
54
+ status: 'Opening channel delivery.',
55
+ actionResult: {
56
+ kind: 'dispatched',
57
+ title: 'Opening channel delivery',
58
+ detail: 'The workspace handed a confirmed channel delivery command to the shell-owned command router.',
59
+ command,
60
+ safety: 'safe',
61
+ },
62
+ };
63
+ }
30
64
  const subcommand = editor.kind === 'channel-doctor' ? 'doctor' : editor.kind === 'channel-setup' ? 'setup' : 'show';
31
65
  const command = `/channels ${subcommand} ${quoteSlashCommandArg(readField('channel'))}`;
32
66
  const title = editor.kind === 'channel-doctor'
@@ -2,14 +2,32 @@ import type { AgentWorkspaceEditorKind, AgentWorkspaceLocalEditor } from './agen
2
2
 
3
3
  export type AgentWorkspaceChannelCommandEditorKind = Extract<
4
4
  AgentWorkspaceEditorKind,
5
- 'channel-show' | 'channel-doctor' | 'channel-setup'
5
+ 'channel-show' | 'channel-doctor' | 'channel-setup' | 'channel-send'
6
6
  >;
7
7
 
8
8
  export function isAgentWorkspaceChannelCommandEditorKind(kind: AgentWorkspaceEditorKind): kind is AgentWorkspaceChannelCommandEditorKind {
9
- return kind === 'channel-show' || kind === 'channel-doctor' || kind === 'channel-setup';
9
+ return kind === 'channel-show' || kind === 'channel-doctor' || kind === 'channel-setup' || kind === 'channel-send';
10
10
  }
11
11
 
12
12
  export function createAgentWorkspaceChannelCommandEditor(kind: AgentWorkspaceChannelCommandEditorKind): AgentWorkspaceLocalEditor {
13
+ if (kind === 'channel-send') {
14
+ return {
15
+ kind,
16
+ mode: 'create',
17
+ title: 'Send Channel Message',
18
+ selectedFieldIndex: 0,
19
+ message: 'Send one message through a configured delivery target. Fill exactly one target field and type yes to confirm.',
20
+ fields: [
21
+ { id: 'message', label: 'Message', value: '', required: true, multiline: true, hint: 'Plain-text message. Ctrl-J inserts a new line.' },
22
+ { id: 'title', label: 'Title', value: '', required: false, multiline: false, hint: 'Optional delivery title. Blank uses the Agent default.' },
23
+ { id: 'channel', label: 'Channel target', value: '', required: false, multiline: false, hint: 'Optional surface[:route[:label]], such as slack:ops:Ops or telephony:+15551234567.' },
24
+ { id: 'route', label: 'Route target', value: '', required: false, multiline: false, hint: 'Optional route id or route:label.' },
25
+ { id: 'webhook', label: 'Webhook URL', value: '', required: false, multiline: false, hint: 'Optional http(s) webhook target.' },
26
+ { id: 'link', label: 'Link target', value: '', required: false, multiline: false, hint: 'Optional link delivery target.' },
27
+ { id: 'confirm', label: 'Confirm', value: '', required: true, multiline: false, hint: 'Type yes to run /channels send with --yes.' },
28
+ ],
29
+ };
30
+ }
13
31
  if (kind === 'channel-doctor') {
14
32
  return {
15
33
  kind,
@@ -18,7 +36,7 @@ export function createAgentWorkspaceChannelCommandEditor(kind: AgentWorkspaceCha
18
36
  selectedFieldIndex: 0,
19
37
  message: 'Inspect one connected channel route without sending messages or changing delivery state.',
20
38
  fields: [
21
- { id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, or whatsapp.' },
39
+ { id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, whatsapp, or telephony.' },
22
40
  ],
23
41
  };
24
42
  }
@@ -30,7 +48,7 @@ export function createAgentWorkspaceChannelCommandEditor(kind: AgentWorkspaceCha
30
48
  selectedFieldIndex: 0,
31
49
  message: 'Show read-only setup guidance for one channel. This does not pair, enable, or send through the channel.',
32
50
  fields: [
33
- { id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, or whatsapp.' },
51
+ { id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, whatsapp, or telephony.' },
34
52
  ],
35
53
  };
36
54
  }
@@ -41,7 +59,7 @@ export function createAgentWorkspaceChannelCommandEditor(kind: AgentWorkspaceCha
41
59
  selectedFieldIndex: 0,
42
60
  message: 'Show one channel readiness record from the Agent workspace without changing channel state.',
43
61
  fields: [
44
- { id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, or whatsapp.' },
62
+ { id: 'channel', label: 'Channel id', value: '', required: true, multiline: false, hint: 'Channel id, such as slack, telegram, discord, ntfy, signal, whatsapp, or telephony.' },
45
63
  ],
46
64
  };
47
65
  }
@@ -29,6 +29,7 @@ interface AgentWorkspaceChannelSpec {
29
29
  readonly label: string;
30
30
  readonly enabledKey: string;
31
31
  readonly requiredKeys: readonly string[];
32
+ readonly requiredKeyGroups?: readonly (readonly string[])[];
32
33
  readonly defaultTargetKeys: readonly string[];
33
34
  readonly risk: AgentWorkspaceChannelRisk;
34
35
  readonly riskLabel: string;
@@ -98,6 +99,19 @@ const AGENT_WORKSPACE_CHANNEL_SPECS: readonly AgentWorkspaceChannelSpec[] = [
98
99
  risk: 'dm',
99
100
  riskLabel: 'phone-number delivery',
100
101
  },
102
+ {
103
+ id: 'telephony',
104
+ label: 'Telephony',
105
+ enabledKey: 'surfaces.telephony.enabled',
106
+ requiredKeys: [],
107
+ requiredKeyGroups: [
108
+ ['surfaces.telephony.bridgeUrl'],
109
+ ['surfaces.telephony.accountSid', 'surfaces.telephony.authToken', 'surfaces.telephony.fromNumber'],
110
+ ],
111
+ defaultTargetKeys: ['surfaces.telephony.defaultRecipient'],
112
+ risk: 'dm',
113
+ riskLabel: 'SMS/voice phone-number delivery',
114
+ },
101
115
  {
102
116
  id: 'imessage',
103
117
  label: 'iMessage',
@@ -186,9 +200,28 @@ function hasConfigValue(context: CommandContext, key: string): boolean {
186
200
  }
187
201
  }
188
202
 
203
+ function uniqueStrings(values: readonly string[]): readonly string[] {
204
+ return [...new Set(values)];
205
+ }
206
+
207
+ function missingKeysForBestRequiredGroup(context: CommandContext, groups: readonly (readonly string[])[]): readonly string[] {
208
+ if (groups.length === 0) return [];
209
+ const missingByGroup = groups.map((group) => group.filter((key) => !hasConfigValue(context, key)));
210
+ if (missingByGroup.some((missing) => missing.length === 0)) return [];
211
+ return missingByGroup.reduce((best, missing) => missing.length < best.length ? missing : best, missingByGroup[0] ?? []);
212
+ }
213
+
189
214
  function buildChannelStatus(context: CommandContext, spec: AgentWorkspaceChannelSpec): AgentWorkspaceChannelStatus {
190
215
  const enabled = readConfigBoolean(context, spec.enabledKey, false);
191
- const missingRequiredKeys = spec.requiredKeys.filter((key) => !hasConfigValue(context, key));
216
+ const requiredKeyGroups = spec.requiredKeyGroups ?? [];
217
+ const requiredKeys = uniqueStrings([
218
+ ...spec.requiredKeys,
219
+ ...requiredKeyGroups.flat(),
220
+ ]);
221
+ const missingRequiredKeys = [
222
+ ...spec.requiredKeys.filter((key) => !hasConfigValue(context, key)),
223
+ ...missingKeysForBestRequiredGroup(context, requiredKeyGroups),
224
+ ];
192
225
  const configuredDefaultTargetKeys = spec.defaultTargetKeys.filter((key) => hasConfigValue(context, key));
193
226
  const missingConfigCount = missingRequiredKeys.length;
194
227
  const defaultTarget = spec.defaultTargetKeys.length === 0
@@ -223,7 +256,7 @@ function buildChannelStatus(context: CommandContext, spec: AgentWorkspaceChannel
223
256
  label: spec.label,
224
257
  enabled,
225
258
  ready,
226
- requiredKeys: spec.requiredKeys,
259
+ requiredKeys,
227
260
  missingRequiredKeys,
228
261
  missingConfigCount,
229
262
  defaultTargetKeys: spec.defaultTargetKeys,
@@ -4,12 +4,15 @@ import { buildAgentKnowledgeUrlEditorSubmission } from './agent-workspace-knowle
4
4
  import { buildAgentKnowledgeQueryEditorSubmission } from './agent-workspace-knowledge-query-editor.ts';
5
5
  import { buildAgentReminderScheduleEditorSubmission } from './agent-workspace-reminder-schedule-editor.ts';
6
6
  import { buildAgentRoutineScheduleEditorSubmission } from './agent-workspace-routine-schedule-editor.ts';
7
+ import { buildAgentWorkspaceWebResearchSubmission } from './agent-workspace-web-research-editor.ts';
7
8
  import type { AgentWorkspaceActionResult, AgentWorkspaceEditorKind, AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
8
9
 
9
10
  type AgentWorkspaceFieldReader = (fieldId: string) => string;
10
11
  type AgentWorkspaceCommandEditorKind = AgentWorkspaceBasicCommandEditorKind | Extract<
11
12
  AgentWorkspaceEditorKind,
12
- 'knowledge-url'
13
+ 'web-research'
14
+ | 'web-fetch'
15
+ | 'knowledge-url'
13
16
  | 'knowledge-urls'
14
17
  | 'knowledge-file'
15
18
  | 'knowledge-browser-history'
@@ -22,6 +25,7 @@ type AgentWorkspaceCommandEditorKind = AgentWorkspaceBasicCommandEditorKind | Ex
22
25
  | 'notify-webhook-remove'
23
26
  | 'notify-webhook-clear'
24
27
  | 'notify-webhook-test'
28
+ | 'notify-send'
25
29
  | 'secret-set'
26
30
  | 'secret-link'
27
31
  | 'secret-test'
@@ -67,10 +71,18 @@ type AgentWorkspaceCommandEditorSubmission =
67
71
  readonly command: string;
68
72
  readonly status: string;
69
73
  readonly actionResult: AgentWorkspaceActionResult;
74
+ }
75
+ | {
76
+ readonly kind: 'prompt';
77
+ readonly prompt: string;
78
+ readonly status: string;
79
+ readonly actionResult: AgentWorkspaceActionResult;
70
80
  };
71
81
 
72
82
  export function isAgentWorkspaceCommandEditorKind(kind: AgentWorkspaceEditorKind): kind is AgentWorkspaceCommandEditorKind {
73
- return kind === 'knowledge-url'
83
+ return kind === 'web-research'
84
+ || kind === 'web-fetch'
85
+ || kind === 'knowledge-url'
74
86
  || kind === 'knowledge-search'
75
87
  || kind === 'knowledge-ask'
76
88
  || kind === 'routine-schedule'
@@ -82,7 +94,11 @@ export function buildAgentWorkspaceCommandEditorSubmission(
82
94
  editor: AgentWorkspaceLocalEditor,
83
95
  readField: AgentWorkspaceFieldReader,
84
96
  commandDispatchAvailable: boolean,
97
+ promptDispatchAvailable: boolean,
85
98
  ): AgentWorkspaceCommandEditorSubmission {
99
+ if (editor.kind === 'web-research' || editor.kind === 'web-fetch') {
100
+ return buildAgentWorkspaceWebResearchSubmission(editor, readField, promptDispatchAvailable);
101
+ }
86
102
  if (editor.kind === 'knowledge-url') return buildAgentKnowledgeUrlEditorSubmission(editor, readField, commandDispatchAvailable);
87
103
  if (editor.kind === 'knowledge-search' || editor.kind === 'knowledge-ask') {
88
104
  return buildAgentKnowledgeQueryEditorSubmission(editor, readField, commandDispatchAvailable);
@@ -0,0 +1,16 @@
1
+ import type { CommandContext } from './command-registry.ts';
2
+
3
+ export interface AgentWorkspaceConfigReader {
4
+ get(key: string): unknown;
5
+ }
6
+
7
+ export function getAgentWorkspaceConfigReader(context: CommandContext): AgentWorkspaceConfigReader | null {
8
+ const configManager: object = context.platform.configManager;
9
+ const get = Reflect.get(configManager, 'get');
10
+ if (typeof get !== 'function') return null;
11
+ return {
12
+ get(key: string): unknown {
13
+ return Reflect.apply(get, configManager, [key]);
14
+ },
15
+ };
16
+ }
@@ -37,7 +37,7 @@ export function buildAgentWorkspaceDelegationEditorSubmission(
37
37
  };
38
38
  }
39
39
  const parts = ['/delegate'];
40
- if (isAffirmative(readField('wrfc'))) parts.push('--wrfc');
40
+ if (isAffirmative(readField('review')) || isAffirmative(readField('wrfc'))) parts.push('--review');
41
41
  parts.push(quoteSlashCommandArg(readField('task')));
42
42
  const command = parts.join(' ');
43
43
  return {
@@ -1,6 +1,7 @@
1
1
  import type { AgentPersonaRecord } from '../agent/persona-registry.ts';
2
2
  import type { AgentRoutineRecord } from '../agent/routine-registry.ts';
3
3
  import type { AgentSkillRecord } from '../agent/skill-registry.ts';
4
+ import type { AgentNoteRecord } from '../agent/note-registry.ts';
4
5
  import type { MemoryRecord } from '@pellux/goodvibes-sdk/platform/state';
5
6
  import type {
6
7
  AgentWorkspaceLocalEditor,
@@ -36,7 +37,7 @@ export function createLearnedBehaviorEditor(): AgentWorkspaceLocalEditor {
36
37
  mode: 'create',
37
38
  title: 'Capture Learned Behavior',
38
39
  selectedFieldIndex: 0,
39
- message: 'Turn a reviewed workflow or lesson into local Agent behavior. This writes only to local personas, skills, or routines.',
40
+ message: 'Turn a reviewed workflow or lesson into Agent-local behavior. This writes only to Agent-local personas, skills, or routines.',
40
41
  fields: [
41
42
  { id: 'target', label: 'Behavior type', value: 'skill', required: true, multiline: false, hint: 'skill, routine, or persona.' },
42
43
  { id: 'name', label: 'Name', value: '', required: true, multiline: false, hint: 'Short name for the learned behavior.' },
@@ -72,7 +73,7 @@ export function createLocalEditor(kind: AgentWorkspaceLocalEditorKind | 'knowled
72
73
  mode: 'create',
73
74
  title: 'Create Memory',
74
75
  selectedFieldIndex: 0,
75
- message: 'Record a durable, non-secret Agent memory. This stays in the Agent-owned memory store and never writes to default Knowledge/Wiki.',
76
+ message: 'Record a durable, non-secret Agent memory. This stays in the Agent-owned memory store and never writes to default knowledge.',
76
77
  fields: [
77
78
  { id: 'cls', label: 'Class', value: 'fact', required: true, multiline: false, hint: 'fact, decision, constraint, incident, pattern, risk, runbook, architecture, or ownership.' },
78
79
  { id: 'scope', label: 'Scope', value: 'project', required: true, multiline: false, hint: 'session, project, or team.' },
@@ -83,6 +84,21 @@ export function createLocalEditor(kind: AgentWorkspaceLocalEditorKind | 'knowled
83
84
  ],
84
85
  };
85
86
  }
87
+ if (kind === 'note') {
88
+ return {
89
+ kind,
90
+ mode: 'create',
91
+ title: 'Create Note',
92
+ selectedFieldIndex: 0,
93
+ message: 'Capture a local working note or source-triage note. Notes stay in the Agent-local scratchpad and are not memory or Agent Knowledge.',
94
+ fields: [
95
+ { id: 'title', label: 'Title', value: '', required: true, multiline: false, hint: 'Short note title.' },
96
+ { id: 'body', label: 'Note', value: '', required: true, multiline: true, hint: 'Working note, source triage, or temporary decision. Ctrl-J inserts a new line.' },
97
+ { id: 'sourceUrl', label: 'Source URL', value: '', required: false, multiline: false, hint: 'Optional reviewed URL. This does not ingest the URL into Agent Knowledge.' },
98
+ { id: 'tags', label: 'Tags', value: 'scratchpad', required: false, multiline: false, hint: 'Comma-separated optional tags.' },
99
+ ],
100
+ };
101
+ }
86
102
  if (kind === 'persona') {
87
103
  return {
88
104
  kind,
@@ -155,6 +171,127 @@ export function createMemoryUpdateEditor(record: MemoryRecord): AgentWorkspaceLo
155
171
  };
156
172
  }
157
173
 
174
+ function noteDescription(note: AgentNoteRecord): string {
175
+ const compact = note.body.replace(/\s+/g, ' ').trim();
176
+ const firstSentence = compact.split(/(?<=[.!?])\s+/)[0]?.trim() ?? compact;
177
+ const base = firstSentence.length > 160 ? `${firstSentence.slice(0, 157)}...` : firstSentence;
178
+ return base || note.title;
179
+ }
180
+
181
+ function noteTags(note: AgentNoteRecord, extraTag: string): string {
182
+ return [...new Set([...note.tags, extraTag])].join(', ');
183
+ }
184
+
185
+ export function createMemoryEditorFromNote(note: AgentNoteRecord): AgentWorkspaceLocalEditor {
186
+ const sourceLine = note.sourceUrl ? `\n\nOrigin URL: ${note.sourceUrl}` : '';
187
+ return {
188
+ kind: 'memory',
189
+ mode: 'create',
190
+ title: 'Create Memory From Note',
191
+ selectedFieldIndex: 0,
192
+ message: `Promote ${note.title} into durable Agent memory. Saving writes memory only; the note remains in the scratchpad.`,
193
+ fields: [
194
+ { id: 'cls', label: 'Class', value: 'fact', required: true, multiline: false, hint: 'fact, decision, constraint, incident, pattern, risk, runbook, architecture, or ownership.' },
195
+ { id: 'scope', label: 'Scope', value: 'project', required: true, multiline: false, hint: 'session, project, or team.' },
196
+ { id: 'summary', label: 'Summary', value: note.title, required: true, multiline: false, hint: 'One durable sentence. Do not store secrets.' },
197
+ { id: 'detail', label: 'Detail', value: `${note.body}${sourceLine}`, required: false, multiline: true, hint: 'Optional supporting detail. Ctrl-J inserts a new line.' },
198
+ { id: 'tags', label: 'Tags', value: noteTags(note, 'from-note'), required: false, multiline: false, hint: 'Comma-separated optional tags.' },
199
+ { id: 'confidence', label: 'Confidence', value: note.reviewState === 'reviewed' ? '85' : '65', required: false, multiline: false, hint: '0-100 confidence score.' },
200
+ ],
201
+ };
202
+ }
203
+
204
+ export function createPersonaEditorFromNote(note: AgentNoteRecord): AgentWorkspaceLocalEditor {
205
+ return {
206
+ kind: 'persona',
207
+ mode: 'create',
208
+ title: 'Create Persona From Note',
209
+ selectedFieldIndex: 0,
210
+ message: `Use ${note.title} as the starting point for a local persona. Saving creates a persona only; the note remains in the scratchpad.`,
211
+ fields: [
212
+ { id: 'name', label: 'Name', value: note.title, required: true, multiline: false, hint: 'Short persona name.' },
213
+ { id: 'description', label: 'Description', value: noteDescription(note), required: true, multiline: false, hint: 'One-line summary of when to use it.' },
214
+ { id: 'body', label: 'Instructions', value: note.body, required: true, multiline: true, hint: 'Operating guidance. Ctrl-J inserts a new line.' },
215
+ { id: 'tags', label: 'Tags', value: noteTags(note, 'from-note'), required: false, multiline: false, hint: 'Comma-separated optional tags.' },
216
+ { id: 'triggers', label: 'Triggers', value: '', required: false, multiline: false, hint: 'Comma-separated words that suggest this persona.' },
217
+ { id: 'activate', label: 'Activate now', value: 'no', required: false, multiline: false, hint: 'yes/no.' },
218
+ ],
219
+ };
220
+ }
221
+
222
+ export function createSkillEditorFromNote(note: AgentNoteRecord): AgentWorkspaceLocalEditor {
223
+ return {
224
+ kind: 'skill',
225
+ mode: 'create',
226
+ title: 'Create Skill From Note',
227
+ selectedFieldIndex: 0,
228
+ message: `Use ${note.title} as the starting point for a reusable local skill. Saving creates a skill only; the note remains in the scratchpad.`,
229
+ fields: [
230
+ { id: 'name', label: 'Name', value: note.title, required: true, multiline: false, hint: 'Short skill name.' },
231
+ { id: 'description', label: 'Description', value: noteDescription(note), required: true, multiline: false, hint: 'One-line summary of the procedure.' },
232
+ { id: 'procedure', label: 'Procedure', value: note.body, required: true, multiline: true, hint: 'Reusable steps. Ctrl-J inserts a new line.' },
233
+ { id: 'triggers', label: 'Triggers', value: '', required: false, multiline: false, hint: 'Comma-separated words that suggest this skill.' },
234
+ { id: 'tags', label: 'Tags', value: noteTags(note, 'from-note'), required: false, multiline: false, hint: 'Comma-separated optional tags.' },
235
+ { id: 'requiresEnv', label: 'Required env vars', value: '', required: false, multiline: false, hint: 'Comma-separated env var names. Values are never stored.' },
236
+ { id: 'requiresCommands', label: 'Required commands', value: '', required: false, multiline: false, hint: 'Comma-separated binaries that must be on PATH.' },
237
+ { id: 'enabled', label: 'Enable now', value: 'yes', required: false, multiline: false, hint: 'yes/no.' },
238
+ ],
239
+ };
240
+ }
241
+
242
+ export function createRoutineEditorFromNote(note: AgentNoteRecord): AgentWorkspaceLocalEditor {
243
+ return {
244
+ kind: 'routine',
245
+ mode: 'create',
246
+ title: 'Create Routine From Note',
247
+ selectedFieldIndex: 0,
248
+ message: `Use ${note.title} as the starting point for a repeatable local routine. Saving creates a routine only; the note remains in the scratchpad.`,
249
+ fields: [
250
+ { id: 'name', label: 'Name', value: note.title, required: true, multiline: false, hint: 'Short routine name.' },
251
+ { id: 'description', label: 'Description', value: noteDescription(note), required: true, multiline: false, hint: 'One-line summary of the workflow.' },
252
+ { id: 'steps', label: 'Steps', value: note.body, required: true, multiline: true, hint: 'Workflow steps. Ctrl-J inserts a new line.' },
253
+ { id: 'triggers', label: 'Triggers', value: '', required: false, multiline: false, hint: 'Comma-separated words that suggest this routine.' },
254
+ { id: 'tags', label: 'Tags', value: noteTags(note, 'from-note'), required: false, multiline: false, hint: 'Comma-separated optional tags.' },
255
+ { id: 'requiresEnv', label: 'Required env vars', value: '', required: false, multiline: false, hint: 'Comma-separated env var names. Values are never stored.' },
256
+ { id: 'requiresCommands', label: 'Required commands', value: '', required: false, multiline: false, hint: 'Comma-separated binaries that must be on PATH.' },
257
+ { id: 'enabled', label: 'Enable now', value: 'yes', required: false, multiline: false, hint: 'yes/no.' },
258
+ ],
259
+ };
260
+ }
261
+
262
+ export function createKnowledgeUrlEditorFromNote(note: AgentNoteRecord): AgentWorkspaceLocalEditor {
263
+ return {
264
+ kind: 'knowledge-url',
265
+ mode: 'create',
266
+ title: 'Ingest Note Source Into Agent Knowledge',
267
+ selectedFieldIndex: 3,
268
+ message: `Review the source URL from ${note.title}, then type yes to ingest it into isolated Agent Knowledge. The scratchpad note remains unchanged.`,
269
+ fields: [
270
+ { id: 'url', label: 'URL', value: note.sourceUrl ?? '', required: true, multiline: false, hint: 'HTTP or HTTPS URL to ingest into Agent Knowledge only.' },
271
+ { id: 'tags', label: 'Tags', value: noteTags(note, 'from-note'), required: false, multiline: false, hint: 'Comma-separated optional tags. Spaces are not needed.' },
272
+ { id: 'folder', label: 'Folder', value: 'notes', required: false, multiline: false, hint: 'Optional Agent Knowledge folder path.' },
273
+ { id: 'confirm', label: 'Confirm', value: '', required: true, multiline: false, hint: 'Type yes to run /knowledge ingest-url with --yes.' },
274
+ ],
275
+ };
276
+ }
277
+
278
+ export function createNoteUpdateEditor(record: AgentNoteRecord): AgentWorkspaceLocalEditor {
279
+ return {
280
+ kind: 'note',
281
+ mode: 'update',
282
+ recordId: record.id,
283
+ title: 'Edit Note',
284
+ selectedFieldIndex: 0,
285
+ message: `Editing ${record.title}. Saving keeps it in the Agent-local scratchpad only.`,
286
+ fields: [
287
+ { id: 'title', label: 'Title', value: record.title, required: true, multiline: false, hint: 'Short note title.' },
288
+ { id: 'body', label: 'Note', value: record.body, required: true, multiline: true, hint: 'Working note, source triage, or temporary decision. Ctrl-J inserts a new line.' },
289
+ { id: 'sourceUrl', label: 'Source URL', value: record.sourceUrl ?? '', required: false, multiline: false, hint: 'Optional reviewed URL. This does not ingest the URL into Agent Knowledge.' },
290
+ { id: 'tags', label: 'Tags', value: record.tags.join(', '), required: false, multiline: false, hint: 'Comma-separated optional tags.' },
291
+ ],
292
+ };
293
+ }
294
+
158
295
  export function createPersonaUpdateEditor(record: AgentPersonaRecord, active: boolean): AgentWorkspaceLocalEditor {
159
296
  return {
160
297
  kind: 'persona',
@@ -242,6 +379,7 @@ export function isAffirmative(value: string): boolean {
242
379
 
243
380
  export function editorCategoryId(kind: AgentWorkspaceLocalEditorKind): string {
244
381
  if (kind === 'memory') return 'memory';
382
+ if (kind === 'note') return 'notes';
245
383
  if (kind === 'profile') return 'profiles';
246
384
  if (kind === 'persona') return 'personas';
247
385
  if (kind === 'skill') return 'skills';
@@ -25,7 +25,7 @@ export function createAgentKnowledgeQueryEditor(mode: AgentKnowledgeQueryMode):
25
25
  title: mode === 'ask' ? 'Ask Agent Knowledge' : 'Search Agent Knowledge',
26
26
  selectedFieldIndex: 0,
27
27
  message: mode === 'ask'
28
- ? 'Ask the isolated Agent Knowledge segment. If it has no answer, it fails closed instead of using another wiki.'
28
+ ? 'Ask the isolated Agent Knowledge segment. If it has no answer, it fails closed instead of using another knowledge segment.'
29
29
  : 'Search the isolated Agent Knowledge segment. Results come from Agent-owned sources only.',
30
30
  fields: [
31
31
  {
@@ -5,7 +5,7 @@ import { AgentSkillRegistry } from '../agent/skill-registry.ts';
5
5
  import type { AgentWorkspaceLocalEditorKind } from './agent-workspace-types.ts';
6
6
 
7
7
  export interface AgentWorkspaceLearnedBehaviorInput {
8
- readonly target: Exclude<AgentWorkspaceLocalEditorKind, 'memory' | 'profile'>;
8
+ readonly target: Exclude<AgentWorkspaceLocalEditorKind, 'memory' | 'note' | 'profile'>;
9
9
  readonly name: string;
10
10
  readonly description: string;
11
11
  readonly notes: string;
@@ -15,7 +15,7 @@ export interface AgentWorkspaceLearnedBehaviorInput {
15
15
  }
16
16
 
17
17
  export interface AgentWorkspaceLearnedBehaviorResult {
18
- readonly kind: Exclude<AgentWorkspaceLocalEditorKind, 'memory' | 'profile'>;
18
+ readonly kind: Exclude<AgentWorkspaceLocalEditorKind, 'memory' | 'note' | 'profile'>;
19
19
  readonly id: string;
20
20
  readonly name: string;
21
21
  }
@@ -28,7 +28,7 @@ export function buildAgentWorkspaceLibraryCommandEditorSubmission(
28
28
  readField: AgentWorkspaceFieldReader,
29
29
  ): AgentWorkspaceLibraryCommandEditorSubmission {
30
30
  const target = editor.kind.startsWith('persona') ? 'persona' : editor.kind.startsWith('skill') ? 'skill' : 'routine';
31
- const root = target === 'persona' ? '/personas' : target === 'skill' ? '/agent-skills' : '/routines';
31
+ const root = target === 'persona' ? '/personas' : target === 'skill' ? '/skills' : '/routines';
32
32
  const search = editor.kind.endsWith('search');
33
33
  const command = search
34
34
  ? `${root} search ${quoteSlashCommandArg(readField('query'))}`
@@ -24,8 +24,8 @@ export function createAgentWorkspaceLibraryCommandEditor(kind: AgentWorkspaceLib
24
24
  title: search ? `Search ${label}s` : `Show ${label}`,
25
25
  selectedFieldIndex: 0,
26
26
  message: search
27
- ? `Search local Agent ${target}s by name, description, tags, triggers, or body.`
28
- : `Show one local Agent ${target} by id.`,
27
+ ? `Search Agent-local ${target}s by name, description, tags, triggers, or body.`
28
+ : `Show one Agent-local ${target} by id.`,
29
29
  fields: [
30
30
  search
31
31
  ? { id: 'query', label: 'Search query', value: '', required: false, multiline: false, hint: `Optional text query. Blank lists every local ${target}.` }