@pellux/goodvibes-agent 0.1.116 → 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 (285) hide show
  1. package/CHANGELOG.md +48 -661
  2. package/README.md +64 -57
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +199180 -192185
  5. package/docs/README.md +15 -8
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +59 -0
  8. package/docs/getting-started.md +61 -50
  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 +48 -11
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +3 -6
  16. package/src/agent/behavior-discovery-summary.ts +1 -3
  17. package/src/agent/channel-delivery.ts +201 -0
  18. package/src/agent/media-generation.ts +159 -0
  19. package/src/agent/memory-prompt.ts +0 -1
  20. package/src/agent/note-registry.ts +329 -0
  21. package/src/agent/operator-actions.ts +343 -0
  22. package/src/agent/persona-discovery.ts +1 -3
  23. package/src/agent/persona-registry.ts +19 -16
  24. package/src/agent/record-labels.ts +107 -0
  25. package/src/agent/reminder-schedule-format.ts +33 -24
  26. package/src/agent/reminder-schedule.ts +27 -26
  27. package/src/agent/routine-registry.ts +54 -14
  28. package/src/agent/routine-schedule-args.ts +2 -1
  29. package/src/agent/routine-schedule-format.ts +77 -53
  30. package/src/agent/routine-schedule-promotion.ts +35 -33
  31. package/src/agent/routine-schedule-receipts.ts +29 -27
  32. package/src/agent/runtime-profile-starters.ts +5 -5
  33. package/src/agent/runtime-profile.ts +91 -12
  34. package/src/agent/skill-registry.ts +210 -32
  35. package/src/cli/agent-knowledge-args.ts +5 -1
  36. package/src/cli/agent-knowledge-command.ts +40 -34
  37. package/src/cli/agent-knowledge-format.ts +80 -67
  38. package/src/cli/agent-knowledge-methods.ts +1 -1
  39. package/src/cli/agent-knowledge-runtime.ts +32 -26
  40. package/src/cli/bundle-command.ts +14 -9
  41. package/src/cli/config-overrides.ts +37 -36
  42. package/src/cli/entrypoint.ts +16 -7
  43. package/src/cli/external-runtime.ts +10 -4
  44. package/src/cli/help.ts +54 -29
  45. package/src/cli/local-library-command.ts +203 -83
  46. package/src/cli/management-commands.ts +105 -68
  47. package/src/cli/management.ts +55 -74
  48. package/src/cli/memory-command.ts +66 -32
  49. package/src/cli/parser.ts +37 -3
  50. package/src/cli/profiles-command.ts +166 -31
  51. package/src/cli/provider-auth-routes.ts +2 -1
  52. package/src/cli/routines-command.ts +176 -40
  53. package/src/cli/service-posture.ts +16 -16
  54. package/src/cli/status.ts +56 -135
  55. package/src/core/conversation-message-snapshot.ts +131 -0
  56. package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
  57. package/src/input/agent-workspace-access-command-editors.ts +170 -0
  58. package/src/input/agent-workspace-activation.ts +35 -8
  59. package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
  60. package/src/input/agent-workspace-basic-command-editors.ts +502 -9
  61. package/src/input/agent-workspace-categories.ts +423 -51
  62. package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
  63. package/src/input/agent-workspace-channel-command-editors.ts +65 -0
  64. package/src/input/agent-workspace-channels.ts +35 -2
  65. package/src/input/agent-workspace-command-editor.ts +41 -3
  66. package/src/input/agent-workspace-config-reader.ts +16 -0
  67. package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
  68. package/src/input/agent-workspace-editors.ts +166 -1
  69. package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
  70. package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
  71. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  72. package/src/input/agent-workspace-learned-behavior.ts +67 -0
  73. package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
  74. package/src/input/agent-workspace-library-command-editors.ts +35 -0
  75. package/src/input/agent-workspace-local-operations.ts +218 -0
  76. package/src/input/agent-workspace-local-selection.ts +75 -0
  77. package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
  78. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  79. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  80. package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
  81. package/src/input/agent-workspace-memory-command-editors.ts +180 -0
  82. package/src/input/agent-workspace-memory-editor.ts +2 -2
  83. package/src/input/agent-workspace-navigation.ts +81 -0
  84. package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
  85. package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
  86. package/src/input/agent-workspace-operations-command-editors.ts +196 -0
  87. package/src/input/agent-workspace-panel-route.ts +43 -0
  88. package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
  89. package/src/input/agent-workspace-provider-command-editors.ts +93 -0
  90. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  91. package/src/input/agent-workspace-requirements.ts +11 -0
  92. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  93. package/src/input/agent-workspace-search.ts +169 -0
  94. package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
  95. package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
  96. package/src/input/agent-workspace-session-command-editors.ts +249 -0
  97. package/src/input/agent-workspace-setup.ts +37 -16
  98. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
  99. package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
  100. package/src/input/agent-workspace-snapshot.ts +162 -21
  101. package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
  102. package/src/input/agent-workspace-task-command-editors.ts +47 -0
  103. package/src/input/agent-workspace-token.ts +18 -2
  104. package/src/input/agent-workspace-types.ts +212 -4
  105. package/src/input/agent-workspace-voice-media.ts +4 -7
  106. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  107. package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
  108. package/src/input/agent-workspace.ts +178 -240
  109. package/src/input/command-registry.ts +5 -2
  110. package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
  111. package/src/input/commands/agent-skills-runtime.ts +149 -72
  112. package/src/input/commands/agent-workspace-runtime.ts +23 -6
  113. package/src/input/commands/brief-runtime.ts +55 -25
  114. package/src/input/commands/channels-runtime.ts +147 -33
  115. package/src/input/commands/compat-runtime.ts +32 -0
  116. package/src/input/commands/delegation-runtime.ts +29 -23
  117. package/src/input/commands/experience-runtime.ts +6 -4
  118. package/src/input/commands/guidance-runtime.ts +4 -4
  119. package/src/input/commands/health-runtime.ts +149 -126
  120. package/src/input/commands/knowledge.ts +82 -60
  121. package/src/input/commands/local-provider-runtime.ts +43 -21
  122. package/src/input/commands/local-runtime.ts +138 -16
  123. package/src/input/commands/local-setup-review.ts +10 -20
  124. package/src/input/commands/mcp-runtime.ts +63 -46
  125. package/src/input/commands/notify-runtime.ts +38 -9
  126. package/src/input/commands/operator-actions-runtime.ts +138 -0
  127. package/src/input/commands/operator-runtime.ts +6 -76
  128. package/src/input/commands/personas-runtime.ts +46 -31
  129. package/src/input/commands/planning-runtime.ts +1 -1
  130. package/src/input/commands/platform-access-runtime.ts +35 -98
  131. package/src/input/commands/product-runtime.ts +2 -2
  132. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  133. package/src/input/commands/qrcode-runtime.ts +50 -9
  134. package/src/input/commands/recall-bundle.ts +11 -11
  135. package/src/input/commands/recall-capture.ts +13 -11
  136. package/src/input/commands/recall-query.ts +29 -21
  137. package/src/input/commands/recall-review.ts +2 -1
  138. package/src/input/commands/routines-runtime.ts +99 -45
  139. package/src/input/commands/runtime-services.ts +1 -14
  140. package/src/input/commands/schedule-runtime.ts +33 -20
  141. package/src/input/commands/security-runtime.ts +6 -11
  142. package/src/input/commands/session-content.ts +80 -78
  143. package/src/input/commands/session-workflow.ts +134 -104
  144. package/src/input/commands/session.ts +3 -174
  145. package/src/input/commands/shell-core.ts +38 -17
  146. package/src/input/commands/subscription-runtime.ts +54 -185
  147. package/src/input/commands/support-bundle-runtime.ts +53 -0
  148. package/src/input/commands/tasks-runtime.ts +21 -21
  149. package/src/input/commands/work-plan-runtime.ts +33 -8
  150. package/src/input/commands.ts +6 -2
  151. package/src/input/feed-context-factory.ts +2 -2
  152. package/src/input/file-picker.ts +3 -2
  153. package/src/input/handler-command-route.ts +4 -7
  154. package/src/input/handler-content-actions.ts +89 -26
  155. package/src/input/handler-feed-routes.ts +19 -12
  156. package/src/input/handler-feed.ts +6 -5
  157. package/src/input/handler-interactions.ts +9 -36
  158. package/src/input/handler-modal-stack.ts +3 -3
  159. package/src/input/handler-onboarding.ts +52 -91
  160. package/src/input/handler-shortcuts.ts +16 -7
  161. package/src/input/handler.ts +50 -23
  162. package/src/input/input-history.ts +5 -6
  163. package/src/input/keybindings.ts +27 -16
  164. package/src/input/mcp-workspace.ts +90 -26
  165. package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
  166. package/src/input/onboarding/onboarding-wizard-apply.ts +62 -0
  167. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  168. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  169. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
  170. package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
  171. package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
  172. package/src/input/onboarding/onboarding-wizard.ts +87 -2
  173. package/src/input/profile-picker-modal.ts +31 -12
  174. package/src/input/session-picker-modal.ts +21 -4
  175. package/src/input/settings-modal-agent-policy.ts +6 -6
  176. package/src/input/settings-modal-behavior.ts +0 -3
  177. package/src/input/settings-modal-subscriptions.ts +3 -3
  178. package/src/input/settings-modal-types.ts +2 -13
  179. package/src/input/settings-modal.ts +20 -66
  180. package/src/input/submission-intent.ts +0 -1
  181. package/src/input/submission-router.ts +3 -3
  182. package/src/main.ts +18 -10
  183. package/src/panels/approval-panel.ts +8 -8
  184. package/src/panels/automation-control-panel.ts +4 -4
  185. package/src/panels/base-panel.ts +1 -1
  186. package/src/panels/builtin/agent.ts +1 -1
  187. package/src/panels/builtin/operations.ts +3 -12
  188. package/src/panels/builtin/session.ts +32 -24
  189. package/src/panels/builtin/shared.ts +7 -7
  190. package/src/panels/cost-tracker-panel.ts +1 -1
  191. package/src/panels/docs-panel.ts +5 -3
  192. package/src/panels/index.ts +0 -1
  193. package/src/panels/knowledge-panel.ts +6 -5
  194. package/src/panels/memory-panel.ts +7 -6
  195. package/src/panels/panel-list-panel.ts +36 -80
  196. package/src/panels/project-planning-panel.ts +19 -12
  197. package/src/panels/provider-account-snapshot.ts +51 -25
  198. package/src/panels/provider-accounts-panel.ts +33 -18
  199. package/src/panels/provider-health-domains.ts +48 -7
  200. package/src/panels/provider-health-panel.ts +5 -4
  201. package/src/panels/qr-panel.ts +44 -20
  202. package/src/panels/schedule-panel.ts +9 -17
  203. package/src/panels/security-panel.ts +8 -8
  204. package/src/panels/session-browser-panel.ts +10 -10
  205. package/src/panels/subscription-panel.ts +6 -6
  206. package/src/panels/system-messages-panel.ts +3 -3
  207. package/src/panels/tasks-panel.ts +21 -14
  208. package/src/panels/tool-inspector-panel.ts +19 -12
  209. package/src/panels/work-plan-panel.ts +5 -5
  210. package/src/planning/project-planning-coordinator.ts +3 -3
  211. package/src/provider-auth-route-display.ts +9 -0
  212. package/src/renderer/agent-workspace-style.ts +34 -0
  213. package/src/renderer/agent-workspace.ts +254 -57
  214. package/src/renderer/autocomplete-overlay.ts +25 -6
  215. package/src/renderer/block-actions.ts +1 -3
  216. package/src/renderer/bookmark-modal.ts +19 -4
  217. package/src/renderer/buffer.ts +4 -2
  218. package/src/renderer/context-inspector.ts +50 -13
  219. package/src/renderer/diff.ts +1 -1
  220. package/src/renderer/file-picker-overlay.ts +19 -6
  221. package/src/renderer/help-overlay.ts +112 -34
  222. package/src/renderer/history-search-overlay.ts +19 -4
  223. package/src/renderer/live-tail-modal.ts +27 -5
  224. package/src/renderer/mcp-workspace.ts +176 -43
  225. package/src/renderer/model-picker-overlay.ts +58 -3
  226. package/src/renderer/model-workspace.ts +104 -22
  227. package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
  228. package/src/renderer/process-modal.ts +27 -6
  229. package/src/renderer/profile-picker-modal.ts +21 -5
  230. package/src/renderer/search-overlay.ts +25 -5
  231. package/src/renderer/selection-modal-overlay.ts +46 -14
  232. package/src/renderer/session-picker-modal.ts +18 -1
  233. package/src/renderer/settings-modal-helpers.ts +2 -40
  234. package/src/renderer/settings-modal.ts +88 -55
  235. package/src/renderer/system-message.ts +1 -1
  236. package/src/renderer/tool-call.ts +20 -11
  237. package/src/runtime/agent-runtime-events.ts +129 -0
  238. package/src/runtime/bootstrap-command-context.ts +7 -1
  239. package/src/runtime/bootstrap-command-parts.ts +11 -8
  240. package/src/runtime/bootstrap-core.ts +23 -65
  241. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  242. package/src/runtime/bootstrap-shell.ts +46 -8
  243. package/src/runtime/bootstrap.ts +36 -46
  244. package/src/runtime/connected-host-auth.ts +47 -0
  245. package/src/runtime/diagnostics/panels/index.ts +2 -2
  246. package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
  247. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  248. package/src/runtime/index.ts +2 -5
  249. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  250. package/src/runtime/onboarding/apply.ts +288 -74
  251. package/src/runtime/onboarding/derivation.ts +10 -13
  252. package/src/runtime/onboarding/snapshot.ts +2 -17
  253. package/src/runtime/onboarding/types.ts +34 -20
  254. package/src/runtime/onboarding/verify.ts +105 -4
  255. package/src/runtime/operator-token-cleanup.ts +3 -3
  256. package/src/runtime/services.ts +95 -11
  257. package/src/runtime/store/selectors/index.ts +3 -3
  258. package/src/runtime/store/state.ts +1 -4
  259. package/src/runtime/surface-feature-flags.ts +41 -6
  260. package/src/runtime/ui-read-models.ts +3 -4
  261. package/src/runtime/ui-services.ts +6 -6
  262. package/src/shell/blocking-input.ts +2 -1
  263. package/src/shell/service-settings-sync.ts +7 -7
  264. package/src/shell/ui-openers.ts +17 -38
  265. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  266. package/src/tools/agent-channel-send-tool.ts +133 -0
  267. package/src/tools/agent-context-policy.ts +1 -1
  268. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  269. package/src/tools/agent-knowledge-tool.ts +170 -0
  270. package/src/tools/agent-local-registry-tool.ts +279 -73
  271. package/src/tools/agent-media-generate-tool.ts +133 -0
  272. package/src/tools/agent-notify-tool.ts +143 -0
  273. package/src/tools/agent-operator-action-tool.ts +137 -0
  274. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  275. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  276. package/src/tools/agent-tool-policy-guard.ts +13 -15
  277. package/src/tools/agent-work-plan-tool.ts +256 -0
  278. package/src/version.ts +1 -1
  279. package/docs/connected-services.md +0 -51
  280. package/src/cli/package-verification.ts +0 -274
  281. package/src/input/commands/policy-dispatch.ts +0 -339
  282. package/src/input/commands/policy.ts +0 -17
  283. package/src/panels/panel-picker.ts +0 -105
  284. package/src/panels/policy-panel.ts +0 -308
  285. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -0,0 +1,159 @@
1
+ import type { ArtifactCreateInput, ArtifactDescriptor, ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts';
2
+ import type {
3
+ MediaArtifact,
4
+ MediaGenerationRequest,
5
+ MediaProvider,
6
+ MediaProviderRegistry,
7
+ } from '@pellux/goodvibes-sdk/platform/media';
8
+
9
+ export interface AgentMediaGenerationInput {
10
+ readonly prompt: string;
11
+ readonly providerId?: string;
12
+ readonly modelId?: string;
13
+ readonly outputMimeType?: string;
14
+ readonly options?: Record<string, unknown>;
15
+ }
16
+
17
+ export interface AgentGeneratedMediaArtifact {
18
+ readonly artifactId: string;
19
+ readonly mimeType: string;
20
+ readonly filename?: string;
21
+ readonly sizeBytes: number;
22
+ readonly sourceUri?: string;
23
+ }
24
+
25
+ export interface AgentMediaGenerationResult {
26
+ readonly providerId: string;
27
+ readonly artifacts: readonly AgentGeneratedMediaArtifact[];
28
+ readonly metadata: Record<string, unknown>;
29
+ }
30
+
31
+ function readText(value: string | undefined): string | undefined {
32
+ const trimmed = value?.trim();
33
+ return trimmed ? trimmed : undefined;
34
+ }
35
+
36
+ function ensurePrompt(prompt: string): string {
37
+ const trimmed = prompt.trim();
38
+ if (!trimmed) throw new Error('Media generation prompt is required.');
39
+ return trimmed;
40
+ }
41
+
42
+ function findGenerationProvider(registry: MediaProviderRegistry, providerId: string | undefined): MediaProvider {
43
+ const provider = registry.findProvider('generate', readText(providerId));
44
+ if (!provider?.generate) {
45
+ const available = registry
46
+ .list()
47
+ .filter((entry) => entry.capabilities.includes('generate'))
48
+ .map((entry) => entry.id)
49
+ .join(', ');
50
+ throw new Error(providerId
51
+ ? `Media generation provider is unavailable or not configured for generation: ${providerId}.`
52
+ : `No media generation provider is available${available ? `. Available providers: ${available}` : '.'}`);
53
+ }
54
+ return provider;
55
+ }
56
+
57
+ function filenameForArtifact(artifact: MediaArtifact, index: number): string {
58
+ if (readText(artifact.filename)) return artifact.filename as string;
59
+ if (artifact.mimeType.includes('video')) return `agent-generated-${index + 1}.mp4`;
60
+ if (artifact.mimeType.includes('image')) return `agent-generated-${index + 1}.png`;
61
+ return `agent-generated-${index + 1}`;
62
+ }
63
+
64
+ function artifactCreateInput(
65
+ providerId: string,
66
+ artifact: MediaArtifact,
67
+ index: number,
68
+ ): ArtifactCreateInput {
69
+ const filename = filenameForArtifact(artifact, index);
70
+ const metadata = {
71
+ ...artifact.metadata,
72
+ product: 'goodvibes-agent',
73
+ source: 'agent-media-generation',
74
+ providerId,
75
+ };
76
+ const common = {
77
+ mimeType: artifact.mimeType,
78
+ filename,
79
+ metadata,
80
+ };
81
+ if (readText(artifact.dataBase64)) {
82
+ return {
83
+ ...common,
84
+ dataBase64: artifact.dataBase64,
85
+ sourceUri: artifact.uri,
86
+ acquisitionMode: 'inline-data',
87
+ };
88
+ }
89
+ if (readText(artifact.uri)) {
90
+ return {
91
+ ...common,
92
+ uri: artifact.uri,
93
+ sourceUri: artifact.uri,
94
+ acquisitionMode: 'remote-fetch',
95
+ fetchMode: 'public-only',
96
+ };
97
+ }
98
+ throw new Error(`Generated media artifact ${index + 1} did not include inline data or a fetchable URI.`);
99
+ }
100
+
101
+ function toGeneratedArtifact(descriptor: ArtifactDescriptor): AgentGeneratedMediaArtifact {
102
+ return {
103
+ artifactId: descriptor.id,
104
+ mimeType: descriptor.mimeType,
105
+ ...(descriptor.filename ? { filename: descriptor.filename } : {}),
106
+ sizeBytes: descriptor.sizeBytes,
107
+ ...(descriptor.sourceUri ? { sourceUri: descriptor.sourceUri } : {}),
108
+ };
109
+ }
110
+
111
+ function buildProviderRequest(input: AgentMediaGenerationInput): MediaGenerationRequest {
112
+ return {
113
+ prompt: ensurePrompt(input.prompt),
114
+ ...(readText(input.outputMimeType) ? { outputMimeType: readText(input.outputMimeType) } : {}),
115
+ ...(readText(input.modelId) ? { modelId: readText(input.modelId) } : {}),
116
+ ...(input.options ? { options: input.options } : {}),
117
+ metadata: {
118
+ product: 'goodvibes-agent',
119
+ source: 'agent-media-generation',
120
+ },
121
+ };
122
+ }
123
+
124
+ export async function generateAgentMedia(
125
+ registry: MediaProviderRegistry,
126
+ artifactStore: Pick<ArtifactStore, 'create'>,
127
+ input: AgentMediaGenerationInput,
128
+ ): Promise<AgentMediaGenerationResult> {
129
+ const provider = findGenerationProvider(registry, input.providerId);
130
+ const generate = provider.generate;
131
+ if (!generate) throw new Error(`Media generation provider is unavailable or not configured for generation: ${provider.id}.`);
132
+ const generated = await generate(buildProviderRequest(input));
133
+ const artifacts: AgentGeneratedMediaArtifact[] = [];
134
+ for (const [index, artifact] of generated.artifacts.entries()) {
135
+ const descriptor = await artifactStore.create(artifactCreateInput(generated.providerId, artifact, index));
136
+ artifacts.push(toGeneratedArtifact(descriptor));
137
+ }
138
+ if (artifacts.length === 0) throw new Error(`Media provider ${generated.providerId} did not return artifacts.`);
139
+ return {
140
+ providerId: generated.providerId,
141
+ artifacts,
142
+ metadata: generated.metadata,
143
+ };
144
+ }
145
+
146
+ export function formatAgentMediaGenerationResult(result: AgentMediaGenerationResult): string {
147
+ return [
148
+ 'Agent media generated',
149
+ ` provider ${result.providerId}`,
150
+ ` artifacts ${result.artifacts.length}`,
151
+ ...result.artifacts.map((artifact, index) => [
152
+ ` ${index + 1}. ${artifact.artifactId}`,
153
+ ` type ${artifact.mimeType}`,
154
+ ` size ${artifact.sizeBytes} bytes`,
155
+ ...(artifact.filename ? [` filename ${artifact.filename}`] : []),
156
+ ...(artifact.sourceUri ? [` source ${artifact.sourceUri}`] : []),
157
+ ].join('\n')),
158
+ ].join('\n');
159
+ }
@@ -1,5 +1,4 @@
1
1
  import type { MemoryRecord, MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
2
-
3
2
  const DEFAULT_LIMIT = 10;
4
3
  export const MIN_PROMPT_MEMORY_CONFIDENCE = 50;
5
4
 
@@ -0,0 +1,329 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import type { ShellPathService } from '@/runtime/index.ts';
4
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
5
+ import { assertNoSecretLikeText } from './persona-registry.ts';
6
+
7
+ export type AgentNoteSource = 'user' | 'agent' | 'imported' | 'system';
8
+ export type AgentNoteReviewState = 'fresh' | 'reviewed' | 'stale';
9
+
10
+ export interface AgentNoteRecord {
11
+ readonly id: string;
12
+ readonly title: string;
13
+ readonly body: string;
14
+ readonly tags: readonly string[];
15
+ readonly sourceUrl?: string;
16
+ readonly source: AgentNoteSource;
17
+ readonly provenance: string;
18
+ readonly reviewState: AgentNoteReviewState;
19
+ readonly staleReason?: string;
20
+ readonly createdAt: string;
21
+ readonly updatedAt: string;
22
+ readonly reviewedAt?: string;
23
+ }
24
+
25
+ export interface AgentNoteCreateInput {
26
+ readonly title: string;
27
+ readonly body: string;
28
+ readonly tags?: readonly string[];
29
+ readonly sourceUrl?: string;
30
+ readonly source?: AgentNoteSource;
31
+ readonly provenance?: string;
32
+ }
33
+
34
+ export interface AgentNoteUpdateInput {
35
+ readonly title?: string;
36
+ readonly body?: string;
37
+ readonly tags?: readonly string[];
38
+ readonly sourceUrl?: string;
39
+ readonly provenance?: string;
40
+ }
41
+
42
+ export interface AgentNoteSnapshot {
43
+ readonly path: string;
44
+ readonly notes: readonly AgentNoteRecord[];
45
+ readonly reviewQueue: readonly AgentNoteRecord[];
46
+ }
47
+
48
+ interface NoteStoreFile {
49
+ readonly version: 1;
50
+ readonly notes: readonly AgentNoteRecord[];
51
+ }
52
+
53
+ const STORE_VERSION = 1;
54
+
55
+ function isRecord(value: unknown): value is Record<string, unknown> {
56
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
57
+ }
58
+
59
+ function readString(value: unknown, fallback = ''): string {
60
+ return typeof value === 'string' ? value : fallback;
61
+ }
62
+
63
+ function readStringArray(value: unknown): string[] {
64
+ if (!Array.isArray(value)) return [];
65
+ return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
66
+ }
67
+
68
+ function normalizeTitle(title: string): string {
69
+ return title.trim().replace(/\s+/g, ' ');
70
+ }
71
+
72
+ function normalizeList(values: readonly string[] | undefined): string[] {
73
+ const seen = new Set<string>();
74
+ const result: string[] = [];
75
+ for (const value of values ?? []) {
76
+ const trimmed = value.trim();
77
+ if (!trimmed) continue;
78
+ const key = trimmed.toLowerCase();
79
+ if (seen.has(key)) continue;
80
+ seen.add(key);
81
+ result.push(trimmed);
82
+ }
83
+ return result;
84
+ }
85
+
86
+ function slugify(value: string): string {
87
+ const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
88
+ return slug || 'note';
89
+ }
90
+
91
+ function nowIso(): string {
92
+ return new Date().toISOString();
93
+ }
94
+
95
+ function assertNoNoteSecretLikeText(fields: readonly string[]): void {
96
+ try {
97
+ assertNoSecretLikeText(fields);
98
+ } catch {
99
+ throw new Error('Notes cannot store secret-looking values. Store a secret reference or remove the sensitive text.');
100
+ }
101
+ }
102
+
103
+ function parseNote(value: unknown): AgentNoteRecord | null {
104
+ if (!isRecord(value)) return null;
105
+ const id = readString(value.id).trim();
106
+ const title = normalizeTitle(readString(value.title));
107
+ const body = readString(value.body).trim();
108
+ if (!id || !title || !body) return null;
109
+ const reviewState = value.reviewState === 'reviewed' || value.reviewState === 'stale' ? value.reviewState : 'fresh';
110
+ const source = value.source === 'agent' || value.source === 'imported' || value.source === 'system' ? value.source : 'user';
111
+ const sourceUrl = readString(value.sourceUrl).trim();
112
+ const staleReason = readString(value.staleReason).trim();
113
+ const reviewedAt = readString(value.reviewedAt).trim();
114
+ return {
115
+ id,
116
+ title,
117
+ body,
118
+ tags: readStringArray(value.tags),
119
+ sourceUrl: sourceUrl || undefined,
120
+ source,
121
+ provenance: readString(value.provenance, source).trim() || source,
122
+ reviewState,
123
+ staleReason: staleReason || undefined,
124
+ createdAt: readString(value.createdAt, nowIso()),
125
+ updatedAt: readString(value.updatedAt, nowIso()),
126
+ reviewedAt: reviewedAt || undefined,
127
+ };
128
+ }
129
+
130
+ function parseStore(raw: string): NoteStoreFile {
131
+ const parsed: unknown = JSON.parse(raw);
132
+ if (!isRecord(parsed)) return { version: STORE_VERSION, notes: [] };
133
+ return {
134
+ version: STORE_VERSION,
135
+ notes: Array.isArray(parsed.notes)
136
+ ? parsed.notes.map(parseNote).filter((entry): entry is AgentNoteRecord => entry !== null)
137
+ : [],
138
+ };
139
+ }
140
+
141
+ function formatStore(store: NoteStoreFile): string {
142
+ return `${JSON.stringify(store, null, 2)}\n`;
143
+ }
144
+
145
+ type AgentLocalStorePaths = Pick<ShellPathService, 'resolveUserPath'>;
146
+
147
+ export function noteStorePath(shellPaths: AgentLocalStorePaths): string {
148
+ return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'notes', 'notes.json');
149
+ }
150
+
151
+ export class AgentNoteRegistry {
152
+ public constructor(private readonly storePath: string) {}
153
+
154
+ public static fromShellPaths(shellPaths: AgentLocalStorePaths): AgentNoteRegistry {
155
+ return new AgentNoteRegistry(noteStorePath(shellPaths));
156
+ }
157
+
158
+ public snapshot(): AgentNoteSnapshot {
159
+ const store = this.readStore();
160
+ const notes = [...store.notes].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
161
+ return {
162
+ path: this.storePath,
163
+ notes,
164
+ reviewQueue: notes.filter((note) => note.reviewState !== 'reviewed'),
165
+ };
166
+ }
167
+
168
+ public list(): readonly AgentNoteRecord[] {
169
+ return this.snapshot().notes;
170
+ }
171
+
172
+ public search(query: string): readonly AgentNoteRecord[] {
173
+ const normalized = query.trim().toLowerCase();
174
+ if (!normalized) return this.list();
175
+ return this.list().filter((note) => [
176
+ note.id,
177
+ note.title,
178
+ note.body,
179
+ note.sourceUrl ?? '',
180
+ ...note.tags,
181
+ ].some((field) => field.toLowerCase().includes(normalized)));
182
+ }
183
+
184
+ public get(idOrTitle: string): AgentNoteRecord | null {
185
+ const lookup = idOrTitle.trim().toLowerCase();
186
+ if (!lookup) return null;
187
+ return this.list().find((note) => note.id.toLowerCase() === lookup || note.title.toLowerCase() === lookup) ?? null;
188
+ }
189
+
190
+ public create(input: AgentNoteCreateInput): AgentNoteRecord {
191
+ const store = this.readStore();
192
+ const title = normalizeTitle(input.title);
193
+ const body = input.body.trim();
194
+ this.validateRequired(title, body);
195
+ const sourceUrl = input.sourceUrl?.trim() || undefined;
196
+ assertNoNoteSecretLikeText([title, body, sourceUrl ?? '', ...(input.tags ?? [])]);
197
+ const duplicate = store.notes.find((note) => note.title.toLowerCase() === title.toLowerCase());
198
+ if (duplicate) throw new Error(`Note already exists ${duplicate.id}`);
199
+ const timestamp = nowIso();
200
+ const note: AgentNoteRecord = {
201
+ id: this.nextId(title, store.notes),
202
+ title,
203
+ body,
204
+ tags: normalizeList(input.tags),
205
+ sourceUrl,
206
+ source: input.source ?? 'user',
207
+ provenance: input.provenance?.trim() || input.source || 'user',
208
+ reviewState: 'fresh',
209
+ createdAt: timestamp,
210
+ updatedAt: timestamp,
211
+ };
212
+ this.writeStore({ ...store, notes: [...store.notes, note] });
213
+ return note;
214
+ }
215
+
216
+ public update(idOrTitle: string, input: AgentNoteUpdateInput): AgentNoteRecord {
217
+ const store = this.readStore();
218
+ const existing = this.findInStore(store, idOrTitle);
219
+ if (!existing) throw new Error(`Unknown note ${idOrTitle}`);
220
+ const title = input.title === undefined ? existing.title : normalizeTitle(input.title);
221
+ const body = input.body === undefined ? existing.body : input.body.trim();
222
+ this.validateRequired(title, body);
223
+ const sourceUrl = input.sourceUrl === undefined ? existing.sourceUrl : input.sourceUrl.trim() || undefined;
224
+ assertNoNoteSecretLikeText([title, body, sourceUrl ?? '', ...(input.tags ?? [])]);
225
+ const duplicate = store.notes.find((note) => note.id !== existing.id && note.title.toLowerCase() === title.toLowerCase());
226
+ if (duplicate) throw new Error(`Note already exists ${duplicate.id}`);
227
+ const updated: AgentNoteRecord = {
228
+ ...existing,
229
+ title,
230
+ body,
231
+ tags: input.tags === undefined ? existing.tags : normalizeList(input.tags),
232
+ sourceUrl,
233
+ provenance: input.provenance === undefined ? existing.provenance : input.provenance.trim() || existing.provenance,
234
+ reviewState: 'fresh',
235
+ staleReason: undefined,
236
+ reviewedAt: undefined,
237
+ updatedAt: nowIso(),
238
+ };
239
+ this.writeStore({
240
+ ...store,
241
+ notes: store.notes.map((note) => note.id === existing.id ? updated : note),
242
+ });
243
+ return updated;
244
+ }
245
+
246
+ public markReviewed(idOrTitle: string): AgentNoteRecord {
247
+ const store = this.readStore();
248
+ const existing = this.findInStore(store, idOrTitle);
249
+ if (!existing) throw new Error(`Unknown note ${idOrTitle}`);
250
+ const updated: AgentNoteRecord = {
251
+ ...existing,
252
+ reviewState: 'reviewed',
253
+ staleReason: undefined,
254
+ reviewedAt: nowIso(),
255
+ updatedAt: nowIso(),
256
+ };
257
+ this.writeStore({
258
+ ...store,
259
+ notes: store.notes.map((note) => note.id === existing.id ? updated : note),
260
+ });
261
+ return updated;
262
+ }
263
+
264
+ public markStale(idOrTitle: string, reason: string): AgentNoteRecord {
265
+ const store = this.readStore();
266
+ const existing = this.findInStore(store, idOrTitle);
267
+ if (!existing) throw new Error(`Unknown note ${idOrTitle}`);
268
+ const updated: AgentNoteRecord = {
269
+ ...existing,
270
+ reviewState: 'stale',
271
+ staleReason: reason.trim() || 'Marked stale by user.',
272
+ updatedAt: nowIso(),
273
+ };
274
+ this.writeStore({
275
+ ...store,
276
+ notes: store.notes.map((note) => note.id === existing.id ? updated : note),
277
+ });
278
+ return updated;
279
+ }
280
+
281
+ public deleteNote(idOrTitle: string): AgentNoteRecord {
282
+ const store = this.readStore();
283
+ const existing = this.findInStore(store, idOrTitle);
284
+ if (!existing) throw new Error(`Unknown note ${idOrTitle}`);
285
+ this.writeStore({
286
+ ...store,
287
+ notes: store.notes.filter((note) => note.id !== existing.id),
288
+ });
289
+ return existing;
290
+ }
291
+
292
+ private validateRequired(title: string, body: string): void {
293
+ if (!title) throw new Error('Note title is required.');
294
+ if (!body) throw new Error('Note body is required.');
295
+ }
296
+
297
+ private nextId(title: string, notes: readonly AgentNoteRecord[]): string {
298
+ const base = slugify(title);
299
+ const ids = new Set(notes.map((note) => note.id));
300
+ if (!ids.has(base)) return base;
301
+ for (let index = 2; index < 1000; index += 1) {
302
+ const candidate = `${base}-${index}`;
303
+ if (!ids.has(candidate)) return candidate;
304
+ }
305
+ throw new Error(`Could not allocate note id for ${title}.`);
306
+ }
307
+
308
+ private findInStore(store: NoteStoreFile, idOrTitle: string): AgentNoteRecord | null {
309
+ const lookup = idOrTitle.trim().toLowerCase();
310
+ if (!lookup) return null;
311
+ return store.notes.find((note) => note.id.toLowerCase() === lookup || note.title.toLowerCase() === lookup) ?? null;
312
+ }
313
+
314
+ private readStore(): NoteStoreFile {
315
+ if (!existsSync(this.storePath)) return { version: STORE_VERSION, notes: [] };
316
+ try {
317
+ return parseStore(readFileSync(this.storePath, 'utf-8'));
318
+ } catch (error) {
319
+ throw new Error(`Could not read Agent note store ${error instanceof Error ? error.message : String(error)}`);
320
+ }
321
+ }
322
+
323
+ private writeStore(store: NoteStoreFile): void {
324
+ mkdirSync(dirname(this.storePath), { recursive: true });
325
+ const tmpPath = `${this.storePath}.tmp`;
326
+ writeFileSync(tmpPath, formatStore(store), 'utf-8');
327
+ renameSync(tmpPath, this.storePath);
328
+ }
329
+ }