@pellux/goodvibes-agent 1.1.6 → 1.2.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 (256) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/README.md +46 -30
  3. package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
  4. package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
  5. package/dist/package/main.js +132662 -91399
  6. package/docs/README.md +14 -7
  7. package/docs/channels-remote-and-api.md +8 -5
  8. package/docs/connected-host.md +14 -12
  9. package/docs/getting-started.md +56 -30
  10. package/docs/knowledge-artifacts-and-multimodal.md +7 -5
  11. package/docs/project-planning.md +2 -2
  12. package/docs/providers-and-routing.md +11 -3
  13. package/docs/tools-and-commands.md +114 -45
  14. package/docs/voice-and-live-tts.md +3 -1
  15. package/package.json +3 -2
  16. package/release/release-notes.md +130 -5
  17. package/release/release-readiness.json +120 -90
  18. package/src/agent/assistant-cockpit.ts +247 -0
  19. package/src/agent/autonomy-schedule-format.ts +96 -0
  20. package/src/agent/autonomy-schedule.ts +514 -0
  21. package/src/agent/channel-delivery-receipts.ts +292 -0
  22. package/src/agent/competitive-feature-inventory.ts +296 -0
  23. package/src/agent/document-registry-types.ts +106 -0
  24. package/src/agent/document-registry.ts +734 -0
  25. package/src/agent/harness-control.ts +2 -2
  26. package/src/agent/memory-prompt.ts +2 -2
  27. package/src/agent/operator-actions.ts +50 -6
  28. package/src/agent/persona-registry.ts +8 -0
  29. package/src/agent/project-context-files.ts +273 -0
  30. package/src/agent/prompt-context-receipts.ts +502 -0
  31. package/src/agent/reminder-schedule-format.ts +16 -2
  32. package/src/agent/research-run-registry.ts +582 -0
  33. package/src/agent/research-source-registry.ts +441 -0
  34. package/src/agent/routine-registry.ts +19 -3
  35. package/src/agent/routine-schedule-format.ts +27 -2
  36. package/src/agent/runtime-profile-starters.ts +7 -0
  37. package/src/agent/runtime-profile.ts +106 -4
  38. package/src/agent/schedule-edit-format.ts +129 -0
  39. package/src/agent/schedule-edit.ts +496 -0
  40. package/src/agent/schedule-next-routes.ts +42 -0
  41. package/src/agent/setup-wizard-checkpoint.ts +140 -0
  42. package/src/agent/setup-wizard.ts +447 -0
  43. package/src/agent/skill-registry-types.ts +102 -0
  44. package/src/agent/skill-registry.ts +81 -107
  45. package/src/agent/vibe-confirmation-routes.ts +62 -0
  46. package/src/agent/vibe-file.ts +285 -0
  47. package/src/cli/agent-knowledge-command.ts +45 -2
  48. package/src/cli/help.ts +3 -0
  49. package/src/cli/profiles-command.ts +25 -11
  50. package/src/config/agent-settings-policy.ts +1 -18
  51. package/src/input/agent-workspace-activation.ts +53 -0
  52. package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
  53. package/src/input/agent-workspace-artifact-metadata.ts +17 -0
  54. package/src/input/agent-workspace-basic-command-editors.ts +5 -1
  55. package/src/input/agent-workspace-categories.ts +153 -155
  56. package/src/input/agent-workspace-channel-triage.ts +481 -0
  57. package/src/input/agent-workspace-channels.ts +263 -0
  58. package/src/input/agent-workspace-command-editor.ts +152 -0
  59. package/src/input/agent-workspace-context-snapshot.ts +349 -0
  60. package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
  61. package/src/input/agent-workspace-document-editor.ts +502 -0
  62. package/src/input/agent-workspace-document-ops-editor.ts +523 -0
  63. package/src/input/agent-workspace-host-category.ts +1 -1
  64. package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
  65. package/src/input/agent-workspace-model-compare-editor.ts +376 -0
  66. package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
  67. package/src/input/agent-workspace-model-compare-types.ts +82 -0
  68. package/src/input/agent-workspace-model-compare-utils.ts +35 -0
  69. package/src/input/agent-workspace-onboarding-categories.ts +141 -0
  70. package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
  71. package/src/input/agent-workspace-operations-command-editors.ts +22 -0
  72. package/src/input/agent-workspace-research-report-editor.ts +212 -0
  73. package/src/input/agent-workspace-research-run-editor.ts +144 -0
  74. package/src/input/agent-workspace-research-source-editor.ts +167 -0
  75. package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
  76. package/src/input/agent-workspace-review-packet-utils.ts +265 -0
  77. package/src/input/agent-workspace-settings.ts +262 -30
  78. package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
  79. package/src/input/agent-workspace-setup-snapshot.ts +198 -0
  80. package/src/input/agent-workspace-setup.ts +34 -1
  81. package/src/input/agent-workspace-snapshot.ts +162 -147
  82. package/src/input/agent-workspace-types.ts +287 -2
  83. package/src/input/agent-workspace.ts +18 -0
  84. package/src/input/command-registry.ts +9 -2
  85. package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
  86. package/src/input/commands/channels-runtime.ts +102 -114
  87. package/src/input/commands/delegation-runtime.ts +87 -24
  88. package/src/input/commands/knowledge-browser-flags.ts +12 -0
  89. package/src/input/commands/knowledge.ts +28 -14
  90. package/src/input/commands/operator-actions-runtime.ts +6 -3
  91. package/src/input/commands/schedule-runtime.ts +71 -14
  92. package/src/input/commands/session-content.ts +2 -2
  93. package/src/input/commands/shell-core.ts +2 -0
  94. package/src/input/commands/vibe-runtime.ts +140 -0
  95. package/src/input/commands.ts +2 -0
  96. package/src/input/connected-host-routes.ts +123 -0
  97. package/src/input/settings-modal-types.ts +14 -0
  98. package/src/main.ts +15 -15
  99. package/src/panels/qr-panel.ts +2 -2
  100. package/src/renderer/agent-workspace-context-lines.ts +686 -0
  101. package/src/renderer/agent-workspace.ts +30 -419
  102. package/src/renderer/settings-modal-helpers.ts +12 -0
  103. package/src/renderer/settings-modal.ts +12 -0
  104. package/src/runtime/bootstrap-command-context.ts +11 -1
  105. package/src/runtime/bootstrap-command-parts.ts +14 -3
  106. package/src/runtime/bootstrap-core.ts +43 -1
  107. package/src/runtime/bootstrap-shell.ts +6 -0
  108. package/src/runtime/bootstrap.ts +113 -30
  109. package/src/runtime/connected-host-auth.ts +3 -2
  110. package/src/runtime/execution-ledger.ts +231 -0
  111. package/src/runtime/services.ts +88 -10
  112. package/src/runtime/tool-permission-safety.ts +287 -0
  113. package/src/shell/session-continuity-hints.ts +39 -0
  114. package/src/tools/agent-artifacts-tool.ts +606 -0
  115. package/src/tools/agent-audit-tool.ts +155 -0
  116. package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
  117. package/src/tools/agent-autonomy-tool.ts +140 -0
  118. package/src/tools/agent-channel-send-tool.ts +24 -2
  119. package/src/tools/agent-channels-tool.ts +140 -0
  120. package/src/tools/agent-computer-tool.ts +168 -0
  121. package/src/tools/agent-context-policy.ts +267 -10
  122. package/src/tools/agent-context-tool.ts +143 -0
  123. package/src/tools/agent-delegation-tool.ts +128 -0
  124. package/src/tools/agent-device-tool.ts +240 -0
  125. package/src/tools/agent-documents-tool.ts +684 -0
  126. package/src/tools/agent-execution-tool.ts +230 -0
  127. package/src/tools/agent-harness-agent-orchestration.ts +703 -0
  128. package/src/tools/agent-harness-autonomy-intake.ts +785 -0
  129. package/src/tools/agent-harness-autonomy-live-records.ts +588 -0
  130. package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
  131. package/src/tools/agent-harness-autonomy-queue.ts +560 -0
  132. package/src/tools/agent-harness-background-processes-types.ts +35 -0
  133. package/src/tools/agent-harness-background-processes.ts +794 -0
  134. package/src/tools/agent-harness-browser-cockpit-route.ts +219 -0
  135. package/src/tools/agent-harness-browser-control.ts +338 -0
  136. package/src/tools/agent-harness-channel-metadata.ts +168 -10
  137. package/src/tools/agent-harness-cli-command-policy.ts +110 -0
  138. package/src/tools/agent-harness-command-catalog.ts +4 -2
  139. package/src/tools/agent-harness-command-runner.ts +42 -0
  140. package/src/tools/agent-harness-connected-host-status.ts +7 -4
  141. package/src/tools/agent-harness-delegation-posture.ts +234 -19
  142. package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
  143. package/src/tools/agent-harness-document-ops-types.ts +72 -0
  144. package/src/tools/agent-harness-document-ops.ts +671 -0
  145. package/src/tools/agent-harness-execution-history.ts +489 -0
  146. package/src/tools/agent-harness-execution-posture.ts +382 -0
  147. package/src/tools/agent-harness-file-recovery.ts +135 -0
  148. package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
  149. package/src/tools/agent-harness-learning-curator-common.ts +102 -0
  150. package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
  151. package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
  152. package/src/tools/agent-harness-learning-curator-types.ts +151 -0
  153. package/src/tools/agent-harness-learning-curator.ts +417 -0
  154. package/src/tools/agent-harness-local-model-benchmarks.ts +199 -0
  155. package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
  156. package/src/tools/agent-harness-local-model-endpoints.ts +673 -0
  157. package/src/tools/agent-harness-media-posture.ts +208 -2
  158. package/src/tools/agent-harness-memory-posture.ts +556 -0
  159. package/src/tools/agent-harness-metadata.ts +100 -168
  160. package/src/tools/agent-harness-mode-catalog.ts +84 -33
  161. package/src/tools/agent-harness-model-catalog.ts +162 -0
  162. package/src/tools/agent-harness-model-provider-health.ts +186 -0
  163. package/src/tools/agent-harness-model-readiness.ts +406 -0
  164. package/src/tools/agent-harness-model-routing-types.ts +266 -0
  165. package/src/tools/agent-harness-model-routing-utils.ts +30 -0
  166. package/src/tools/agent-harness-model-routing.ts +137 -190
  167. package/src/tools/agent-harness-operator-methods.ts +115 -133
  168. package/src/tools/agent-harness-pairing-posture.ts +431 -18
  169. package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
  170. package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
  171. package/src/tools/agent-harness-personal-ops-lanes.ts +204 -0
  172. package/src/tools/agent-harness-personal-ops-records.ts +732 -0
  173. package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
  174. package/src/tools/agent-harness-personal-ops-types.ts +222 -0
  175. package/src/tools/agent-harness-personal-ops.ts +606 -0
  176. package/src/tools/agent-harness-project-context.ts +144 -0
  177. package/src/tools/agent-harness-prompt-context.ts +589 -0
  178. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  179. package/src/tools/agent-harness-release-evidence.ts +9 -7
  180. package/src/tools/agent-harness-release-readiness.ts +9 -7
  181. package/src/tools/agent-harness-research-briefing.ts +427 -0
  182. package/src/tools/agent-harness-research-queue.ts +250 -0
  183. package/src/tools/agent-harness-research-runs.ts +295 -0
  184. package/src/tools/agent-harness-research-workflow.ts +322 -0
  185. package/src/tools/agent-harness-security-posture.ts +2 -2
  186. package/src/tools/agent-harness-service-posture.ts +9 -9
  187. package/src/tools/agent-harness-session-metadata.ts +3 -3
  188. package/src/tools/agent-harness-setup-connected-host.ts +501 -0
  189. package/src/tools/agent-harness-setup-describe.ts +557 -0
  190. package/src/tools/agent-harness-setup-handoffs.ts +502 -0
  191. package/src/tools/agent-harness-setup-model-helpers.ts +101 -0
  192. package/src/tools/agent-harness-setup-plan.ts +253 -0
  193. package/src/tools/agent-harness-setup-posture-types.ts +218 -0
  194. package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
  195. package/src/tools/agent-harness-setup-posture.ts +449 -221
  196. package/src/tools/agent-harness-setup-smoke.ts +508 -0
  197. package/src/tools/agent-harness-sudo-posture.ts +114 -0
  198. package/src/tools/agent-harness-tool-schema.ts +120 -7
  199. package/src/tools/agent-harness-tool-types.ts +78 -0
  200. package/src/tools/agent-harness-tool-utils.ts +43 -0
  201. package/src/tools/agent-harness-tool.ts +311 -370
  202. package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
  203. package/src/tools/agent-harness-ui-surface-types.ts +46 -0
  204. package/src/tools/agent-harness-vibe-health.ts +105 -0
  205. package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
  206. package/src/tools/agent-harness-workspace-actions.ts +114 -8
  207. package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
  208. package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
  209. package/src/tools/agent-host-tool.ts +159 -0
  210. package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
  211. package/src/tools/agent-learning-consolidation-core.ts +327 -0
  212. package/src/tools/agent-learning-consolidation-tool.ts +758 -0
  213. package/src/tools/agent-memory-tool.ts +214 -0
  214. package/src/tools/agent-model-compare-export.ts +315 -0
  215. package/src/tools/agent-model-compare-handoff.ts +592 -0
  216. package/src/tools/agent-model-compare-judgment.ts +633 -0
  217. package/src/tools/agent-model-compare-run.ts +722 -0
  218. package/src/tools/agent-model-compare-tool.ts +698 -0
  219. package/src/tools/agent-model-compare-types.ts +191 -0
  220. package/src/tools/agent-model-compare-utils.ts +93 -0
  221. package/src/tools/agent-models-tool.ts +208 -0
  222. package/src/tools/agent-operator-action-tool.ts +1 -1
  223. package/src/tools/agent-operator-method-tool.ts +643 -0
  224. package/src/tools/agent-personal-ops-tool.ts +197 -0
  225. package/src/tools/agent-policy-explanation.ts +415 -0
  226. package/src/tools/agent-research-report-tool.ts +608 -0
  227. package/src/tools/agent-research-runs-tool.ts +434 -0
  228. package/src/tools/agent-research-sources-tool.ts +443 -0
  229. package/src/tools/agent-research-tool.ts +687 -0
  230. package/src/tools/agent-review-packet-presets-core.ts +757 -0
  231. package/src/tools/agent-review-packet-presets-tool.ts +466 -0
  232. package/src/tools/agent-review-packet-share-tool.ts +305 -0
  233. package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
  234. package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
  235. package/src/tools/agent-route-planner-candidates-work.ts +251 -0
  236. package/src/tools/agent-route-planner-helpers.ts +667 -0
  237. package/src/tools/agent-route-planner.ts +185 -0
  238. package/src/tools/agent-route-tool.ts +105 -0
  239. package/src/tools/agent-schedule-edit-tool.ts +210 -0
  240. package/src/tools/agent-schedule-tool.ts +282 -0
  241. package/src/tools/agent-security-tool.ts +145 -0
  242. package/src/tools/agent-sessions-tool.ts +122 -0
  243. package/src/tools/agent-settings-import-tool.ts +104 -0
  244. package/src/tools/agent-settings-tool.ts +176 -0
  245. package/src/tools/agent-setup-tool.ts +226 -0
  246. package/src/tools/agent-support-tool.ts +122 -0
  247. package/src/tools/agent-terminal-process-tools.ts +167 -0
  248. package/src/tools/agent-tool-policy-guard-types.ts +77 -0
  249. package/src/tools/agent-tool-policy-guard.ts +110 -89
  250. package/src/tools/agent-vibe-tool.ts +297 -0
  251. package/src/tools/agent-work-plan-tool.ts +265 -6
  252. package/src/tools/agent-workspace-tool.ts +305 -0
  253. package/src/tools/artifact-archive.ts +169 -0
  254. package/src/tools/tool-definition-compaction.ts +36 -0
  255. package/src/tools/tool-execution-safety.ts +41 -0
  256. package/src/version.ts +1 -1
@@ -0,0 +1,606 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, stat, writeFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import type { ArtifactDescriptor, ArtifactRecord, ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts';
5
+ import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
6
+ import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
7
+ import { resolveAndValidatePath } from '@pellux/goodvibes-sdk/platform/utils';
8
+ import {
9
+ createZipArchive,
10
+ packageArtifactFilename,
11
+ safeArtifactExportFilename,
12
+ sanitizeArtifactMetadata,
13
+ sanitizeArtifactSourceUri,
14
+ type ArtifactPackageEntry,
15
+ } from './artifact-archive.ts';
16
+
17
+ export interface AgentArtifactsToolArgs {
18
+ readonly mode?: unknown;
19
+ readonly artifactId?: unknown;
20
+ readonly artifactIds?: unknown;
21
+ readonly destinationPath?: unknown;
22
+ readonly overwrite?: unknown;
23
+ readonly query?: unknown;
24
+ readonly kind?: unknown;
25
+ readonly mimeType?: unknown;
26
+ readonly purpose?: unknown;
27
+ readonly source?: unknown;
28
+ readonly limit?: unknown;
29
+ readonly includeContent?: unknown;
30
+ readonly previewBytes?: unknown;
31
+ readonly confirm?: unknown;
32
+ readonly explicitUserRequest?: unknown;
33
+ }
34
+
35
+ type AgentArtifactBrowserStore = Partial<Pick<ArtifactStore, 'get' | 'list' | 'readContent'>>;
36
+
37
+ export interface AgentArtifactsToolOptions {
38
+ readonly projectRoot?: string;
39
+ }
40
+
41
+ interface LoadedArtifact {
42
+ readonly descriptor: ArtifactDescriptor;
43
+ readonly record?: ArtifactRecord;
44
+ readonly buffer?: Buffer;
45
+ }
46
+
47
+ interface ArtifactPackagePayload {
48
+ readonly artifactCount: number;
49
+ readonly totalBytes: number;
50
+ readonly entries: readonly ArtifactPackageEntry[];
51
+ }
52
+
53
+ const DEFAULT_LIST_LIMIT = 25;
54
+ const DEFAULT_PREVIEW_BYTES = 2_048;
55
+ const MAX_PREVIEW_BYTES = 20_000;
56
+ const MAX_PACKAGE_ARTIFACTS = 100;
57
+
58
+ function readString(value: unknown): string {
59
+ return typeof value === 'string' ? value.trim() : '';
60
+ }
61
+
62
+ function readBoolean(value: unknown): boolean {
63
+ return value === true || value === 'true' || value === 'yes';
64
+ }
65
+
66
+ function readNumber(value: unknown, fallback: number): number {
67
+ const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
68
+ if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
69
+ return Math.trunc(parsed);
70
+ }
71
+
72
+ function readStringList(value: unknown): readonly string[] {
73
+ const values = Array.isArray(value)
74
+ ? value
75
+ : readString(value)
76
+ .split(/[,\n]/)
77
+ .map((entry) => entry.trim());
78
+ const seen = new Set<string>();
79
+ const result: string[] = [];
80
+ for (const entry of values) {
81
+ const text = readString(entry);
82
+ if (!text || seen.has(text)) continue;
83
+ seen.add(text);
84
+ result.push(text);
85
+ }
86
+ return result;
87
+ }
88
+
89
+ function clamp(value: number, min: number, max: number): number {
90
+ return Math.max(min, Math.min(max, value));
91
+ }
92
+
93
+ function output(text: string): { readonly success: true; readonly output: string } {
94
+ return { success: true, output: text };
95
+ }
96
+
97
+ function failure(error: string): { readonly success: false; readonly error: string } {
98
+ return { success: false, error };
99
+ }
100
+
101
+ function isoTime(value: number): string {
102
+ if (!Number.isFinite(value)) return '(unknown)';
103
+ return new Date(value).toISOString();
104
+ }
105
+
106
+ function formatBytes(value: number): string {
107
+ if (!Number.isFinite(value) || value < 0) return '(unknown)';
108
+ if (value < 1024) return `${value} B`;
109
+ if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
110
+ return `${(value / (1024 * 1024)).toFixed(1)} MB`;
111
+ }
112
+
113
+ function metadataText(metadata: Record<string, unknown>): string {
114
+ return JSON.stringify(sanitizeArtifactMetadata(metadata));
115
+ }
116
+
117
+ function metadataValue(metadata: Record<string, unknown>, key: string): string {
118
+ const value = metadata[key];
119
+ if (typeof value === 'string') return value;
120
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
121
+ return '';
122
+ }
123
+
124
+ function artifactSearchText(artifact: ArtifactDescriptor): string {
125
+ return [
126
+ artifact.id,
127
+ artifact.kind,
128
+ artifact.mimeType,
129
+ artifact.filename ?? '',
130
+ artifact.sourceUri ?? '',
131
+ artifact.sha256,
132
+ metadataText(artifact.metadata),
133
+ ].join('\n').toLowerCase();
134
+ }
135
+
136
+ function matchesFilter(artifact: ArtifactDescriptor, args: AgentArtifactsToolArgs): boolean {
137
+ const query = readString(args.query).toLowerCase();
138
+ const kind = readString(args.kind).toLowerCase();
139
+ const mimeType = readString(args.mimeType).toLowerCase();
140
+ const purpose = readString(args.purpose).toLowerCase();
141
+ const source = readString(args.source).toLowerCase();
142
+ if (query && !artifactSearchText(artifact).includes(query)) return false;
143
+ if (kind && artifact.kind.toLowerCase() !== kind) return false;
144
+ if (mimeType && !artifact.mimeType.toLowerCase().includes(mimeType)) return false;
145
+ if (purpose && !metadataValue(artifact.metadata, 'purpose').toLowerCase().includes(purpose)) return false;
146
+ if (source) {
147
+ const sourceText = [
148
+ artifact.sourceUri ?? '',
149
+ metadataValue(artifact.metadata, 'source'),
150
+ metadataValue(artifact.metadata, 'sourceKind'),
151
+ metadataValue(artifact.metadata, 'purpose'),
152
+ ].join('\n').toLowerCase();
153
+ if (!sourceText.includes(source)) return false;
154
+ }
155
+ return true;
156
+ }
157
+
158
+ function isTextLike(mimeType: string): boolean {
159
+ const normalized = mimeType.toLowerCase();
160
+ return normalized.startsWith('text/')
161
+ || normalized.includes('json')
162
+ || normalized.includes('xml')
163
+ || normalized.includes('yaml')
164
+ || normalized.includes('csv')
165
+ || normalized.includes('javascript')
166
+ || normalized.includes('typescript');
167
+ }
168
+
169
+ function contentPreview(artifact: ArtifactDescriptor, buffer: Buffer | undefined, previewBytes: number): string {
170
+ if (!buffer) return 'Content preview not loaded. Pass includeContent:true to preview text-like artifacts.';
171
+ if (!isTextLike(artifact.mimeType)) {
172
+ return `Content preview omitted for non-text MIME type ${artifact.mimeType}.`;
173
+ }
174
+ const sliced = buffer.subarray(0, Math.min(buffer.byteLength, previewBytes));
175
+ const text = sliced.toString('utf-8').replace(/\0/g, '').trimEnd();
176
+ if (!text) return '(empty text artifact)';
177
+ return buffer.byteLength > sliced.byteLength ? `${text}\n... (${formatBytes(buffer.byteLength - sliced.byteLength)} more)` : text;
178
+ }
179
+
180
+ function describeArtifact(artifact: ArtifactDescriptor, options: { readonly includeRoute: boolean }): readonly string[] {
181
+ const purpose = metadataValue(artifact.metadata, 'purpose');
182
+ const source = metadataValue(artifact.metadata, 'source') || metadataValue(artifact.metadata, 'sourceKind');
183
+ const exportPath = `exports/${safeArtifactExportFilename(artifact)}`;
184
+ return [
185
+ `${artifact.id} ${artifact.kind} ${artifact.mimeType} ${formatBytes(artifact.sizeBytes)}`,
186
+ ` filename ${artifact.filename ?? '(none)'}`,
187
+ ` created ${isoTime(artifact.createdAt)}`,
188
+ ` purpose ${purpose || '(none)'}`,
189
+ ` source ${source || artifact.sourceUri || '(none)'}`,
190
+ ` sha256 ${artifact.sha256}`,
191
+ ...(options.includeRoute ? [` inspect agent_artifacts mode:"show" artifactId:"${artifact.id}" includeContent:true`] : []),
192
+ ...(options.includeRoute ? [` export agent_artifacts mode:"export" artifactId:"${artifact.id}" destinationPath:"${exportPath}" confirm:true explicitUserRequest:"..."`] : []),
193
+ ];
194
+ }
195
+
196
+ function formatList(artifacts: readonly ArtifactDescriptor[], total: number, args: AgentArtifactsToolArgs): string {
197
+ if (artifacts.length === 0) {
198
+ return [
199
+ 'Agent artifact browser',
200
+ 'No artifacts matched the requested filters.',
201
+ 'Try agent_artifacts mode:"list" without filters, or create an upload/export/media/compare artifact first.',
202
+ ].join('\n');
203
+ }
204
+ const lines = [
205
+ 'Agent artifact browser',
206
+ ` returned ${artifacts.length}/${total}`,
207
+ ` query ${readString(args.query) || '(none)'}`,
208
+ ` kind ${readString(args.kind) || '(any)'}`,
209
+ ` mime ${readString(args.mimeType) || '(any)'}`,
210
+ ` purpose ${readString(args.purpose) || '(any)'}`,
211
+ '',
212
+ ];
213
+ for (const artifact of artifacts) {
214
+ lines.push(...describeArtifact(artifact, { includeRoute: true }), '');
215
+ }
216
+ if (artifacts.length > 1) {
217
+ lines.push(
218
+ 'Package selected artifacts',
219
+ ` package agent_artifacts mode:"package" artifactIds:${JSON.stringify(artifacts.slice(0, 5).map((artifact) => artifact.id))} destinationPath:"exports/artifact-package" confirm:true explicitUserRequest:"..."`,
220
+ ` archive agent_artifacts mode:"archive" artifactIds:${JSON.stringify(artifacts.slice(0, 5).map((artifact) => artifact.id))} destinationPath:"exports/artifact-package.zip" confirm:true explicitUserRequest:"..."`,
221
+ );
222
+ }
223
+ return lines.join('\n').trimEnd();
224
+ }
225
+
226
+ async function loadArtifact(store: AgentArtifactBrowserStore, artifactId: string, includeContent: boolean): Promise<LoadedArtifact | null> {
227
+ if (includeContent && store.readContent) {
228
+ const { record, buffer } = await store.readContent(artifactId);
229
+ return { descriptor: record, record, buffer };
230
+ }
231
+ const descriptor = store.get?.(artifactId)
232
+ ?? store.list?.(500).find((artifact) => artifact.id === artifactId)
233
+ ?? null;
234
+ return descriptor ? { descriptor } : null;
235
+ }
236
+
237
+ function formatShow(loaded: LoadedArtifact, args: AgentArtifactsToolArgs): string {
238
+ const previewBytes = clamp(readNumber(args.previewBytes, DEFAULT_PREVIEW_BYTES), 1, MAX_PREVIEW_BYTES);
239
+ const lines = [
240
+ 'Agent artifact',
241
+ ...describeArtifact(loaded.descriptor, { includeRoute: false }),
242
+ ` expires ${loaded.descriptor.expiresAt ? isoTime(loaded.descriptor.expiresAt) : '(none)'}`,
243
+ ` acquisition ${loaded.descriptor.acquisitionMode}`,
244
+ ` fetch ${loaded.descriptor.fetchMode}`,
245
+ ` sourceUri ${loaded.descriptor.sourceUri ?? '(none)'}`,
246
+ ' metadata',
247
+ JSON.stringify(sanitizeArtifactMetadata(loaded.descriptor.metadata), null, 2).split('\n').map((line) => ` ${line}`).join('\n'),
248
+ '',
249
+ 'Content',
250
+ contentPreview(loaded.descriptor, loaded.buffer, previewBytes).split('\n').map((line) => ` ${line}`).join('\n'),
251
+ ];
252
+ if (readBoolean(args.includeContent) && !loaded.buffer) {
253
+ lines.push('', 'Warning: artifact content could not be read in this runtime.');
254
+ }
255
+ return lines.join('\n');
256
+ }
257
+
258
+ function requireConfirmed(args: AgentArtifactsToolArgs, action: string): void {
259
+ const explicitUserRequest = readString(args.explicitUserRequest);
260
+ if (!explicitUserRequest) throw new Error(`${action} requires explicitUserRequest with the user's exact request or a short faithful summary.`);
261
+ if (args.confirm !== true) throw new Error(`${action} requires confirm:true after an explicit user request.`);
262
+ }
263
+
264
+ async function exportArtifactToFile(
265
+ store: AgentArtifactBrowserStore,
266
+ args: AgentArtifactsToolArgs,
267
+ options: AgentArtifactsToolOptions,
268
+ ): Promise<string> {
269
+ requireConfirmed(args, 'Agent artifact export');
270
+ if (!store.readContent) throw new Error('Artifact export requires an artifact store with readContent support.');
271
+ if (!options.projectRoot) throw new Error('Artifact export requires a projectRoot for path validation.');
272
+ const artifactId = readString(args.artifactId);
273
+ if (!artifactId) throw new Error('artifactId is required for mode:"export".');
274
+ const destinationPath = readString(args.destinationPath);
275
+ if (!destinationPath) throw new Error('destinationPath is required for mode:"export".');
276
+ const resolvedPath = resolveAndValidatePath(destinationPath, options.projectRoot);
277
+ const { record, buffer } = await store.readContent(artifactId);
278
+ const overwrite = readBoolean(args.overwrite);
279
+ if (existsSync(resolvedPath) && !overwrite) {
280
+ throw new Error(`Export target already exists: ${resolvedPath}. Pass overwrite:true only after the user confirms replacement.`);
281
+ }
282
+ await mkdir(dirname(resolvedPath), { recursive: true });
283
+ await writeFile(resolvedPath, buffer);
284
+ return [
285
+ 'Exported Agent artifact',
286
+ ` artifact ${record.id}`,
287
+ ` path ${resolvedPath}`,
288
+ ` bytes ${record.sizeBytes}`,
289
+ ` mime ${record.mimeType}`,
290
+ ` filename ${record.filename ?? '(none)'}`,
291
+ ` sha256 ${record.sha256}`,
292
+ ` overwrite ${overwrite ? 'yes' : 'no'}`,
293
+ ' policy exact artifact bytes copied; artifact retained; content not printed',
294
+ ].join('\n');
295
+ }
296
+
297
+ async function loadArtifactsForPackage(
298
+ store: AgentArtifactBrowserStore,
299
+ args: AgentArtifactsToolArgs,
300
+ mode: 'package' | 'archive',
301
+ ): Promise<readonly { readonly record: ArtifactRecord; readonly buffer: Buffer }[]> {
302
+ if (!store.readContent) throw new Error('Artifact package export requires an artifact store with readContent support.');
303
+ const artifactIds = readStringList(args.artifactIds);
304
+ if (artifactIds.length === 0) throw new Error(`artifactIds is required for mode:"${mode}". Provide a comma-separated string or string array.`);
305
+ if (artifactIds.length > MAX_PACKAGE_ARTIFACTS) throw new Error(`Artifact package export supports at most ${MAX_PACKAGE_ARTIFACTS} artifacts per package.`);
306
+ const loaded: Array<{ readonly record: ArtifactRecord; readonly buffer: Buffer }> = [];
307
+ for (const artifactId of artifactIds) {
308
+ loaded.push(await store.readContent(artifactId));
309
+ }
310
+ return loaded;
311
+ }
312
+
313
+ function buildArtifactPackagePayload(
314
+ loaded: readonly { readonly record: ArtifactRecord; readonly buffer: Buffer }[],
315
+ ): ArtifactPackagePayload {
316
+ const usedFilenames = new Set<string>();
317
+ const manifestArtifacts: Array<Record<string, unknown>> = [];
318
+ const entries: ArtifactPackageEntry[] = [];
319
+ let totalBytes = 0;
320
+ const fileLines: string[] = [];
321
+
322
+ for (let index = 0; index < loaded.length; index += 1) {
323
+ const { record, buffer } = loaded[index];
324
+ const filename = packageArtifactFilename(record, index, usedFilenames);
325
+ const relativePath = `artifacts/${filename}`;
326
+ entries.push({ path: relativePath, buffer });
327
+ totalBytes += buffer.byteLength;
328
+ fileLines.push(`- ${record.id}: ${relativePath} (${formatBytes(buffer.byteLength)}, ${record.mimeType})`);
329
+ manifestArtifacts.push({
330
+ id: record.id,
331
+ file: relativePath,
332
+ originalFilename: record.filename ?? null,
333
+ kind: record.kind,
334
+ mimeType: record.mimeType,
335
+ sizeBytes: record.sizeBytes,
336
+ copiedBytes: buffer.byteLength,
337
+ sha256: record.sha256,
338
+ createdAt: isoTime(record.createdAt),
339
+ expiresAt: record.expiresAt ? isoTime(record.expiresAt) : null,
340
+ acquisitionMode: record.acquisitionMode,
341
+ fetchMode: record.fetchMode,
342
+ sourceUri: sanitizeArtifactSourceUri(record.sourceUri) ?? null,
343
+ metadata: sanitizeArtifactMetadata(record.metadata),
344
+ });
345
+ }
346
+
347
+ const createdAt = new Date().toISOString();
348
+ const manifest = {
349
+ version: 1,
350
+ product: 'goodvibes-agent',
351
+ createdAt,
352
+ artifactCount: loaded.length,
353
+ totalBytes,
354
+ policy: {
355
+ content: 'Exact saved artifact bytes copied into artifacts/.',
356
+ transcript: 'Artifact contents are not printed by the export tool.',
357
+ metadata: 'Secret-like metadata keys and URL query parameters are redacted in this manifest.',
358
+ retention: 'Original saved artifacts are retained in the Agent artifact store.',
359
+ },
360
+ artifacts: manifestArtifacts,
361
+ };
362
+ entries.push({
363
+ path: 'manifest.json',
364
+ buffer: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, 'utf-8'),
365
+ });
366
+ entries.push({
367
+ path: 'README.md',
368
+ buffer: Buffer.from(
369
+ [
370
+ '# GoodVibes Agent Artifact Package',
371
+ '',
372
+ `Generated: ${createdAt}`,
373
+ `Artifacts: ${loaded.length}`,
374
+ `Total bytes: ${totalBytes}`,
375
+ '',
376
+ 'Files',
377
+ ...fileLines,
378
+ '',
379
+ 'Manifest',
380
+ '- `manifest.json` contains redacted artifact metadata and file paths.',
381
+ '- Artifact bytes live under `artifacts/` and are copied exactly from the saved Agent artifact store.',
382
+ '- Original artifacts remain saved in Agent; this package is a user-visible export only.',
383
+ '',
384
+ ].join('\n'),
385
+ 'utf-8',
386
+ ),
387
+ });
388
+
389
+ return {
390
+ artifactCount: loaded.length,
391
+ totalBytes,
392
+ entries,
393
+ };
394
+ }
395
+
396
+ async function writeArtifactPackageDirectory(resolvedPath: string, payload: ArtifactPackagePayload): Promise<void> {
397
+ for (const entry of payload.entries) {
398
+ const target = join(resolvedPath, ...entry.path.split('/'));
399
+ await mkdir(dirname(target), { recursive: true });
400
+ await writeFile(target, entry.buffer);
401
+ }
402
+ }
403
+
404
+ async function exportArtifactPackage(
405
+ store: AgentArtifactBrowserStore,
406
+ args: AgentArtifactsToolArgs,
407
+ options: AgentArtifactsToolOptions,
408
+ ): Promise<string> {
409
+ requireConfirmed(args, 'Agent artifact package export');
410
+ if (!options.projectRoot) throw new Error('Artifact package export requires a projectRoot for path validation.');
411
+ const destinationPath = readString(args.destinationPath);
412
+ if (!destinationPath) throw new Error('destinationPath is required for mode:"package".');
413
+ const resolvedPath = resolveAndValidatePath(destinationPath, options.projectRoot);
414
+ const overwrite = readBoolean(args.overwrite);
415
+ if (existsSync(resolvedPath)) {
416
+ const stats = await stat(resolvedPath);
417
+ if (!stats.isDirectory()) {
418
+ throw new Error(`Package target already exists and is not a directory: ${resolvedPath}. Choose a directory path.`);
419
+ }
420
+ if (!overwrite) {
421
+ throw new Error(`Package target already exists: ${resolvedPath}. Pass overwrite:true only after the user confirms replacement.`);
422
+ }
423
+ }
424
+
425
+ const loaded = await loadArtifactsForPackage(store, args, 'package');
426
+ const payload = buildArtifactPackagePayload(loaded);
427
+ await writeArtifactPackageDirectory(resolvedPath, payload);
428
+ const artifactDir = join(resolvedPath, 'artifacts');
429
+
430
+ return [
431
+ 'Exported Agent artifact package',
432
+ ` path ${resolvedPath}`,
433
+ ` artifacts ${payload.artifactCount}`,
434
+ ` bytes ${payload.totalBytes}`,
435
+ ` manifest ${join(resolvedPath, 'manifest.json')}`,
436
+ ` files ${artifactDir}`,
437
+ ` overwrite ${overwrite ? 'yes' : 'no'}`,
438
+ ' policy exact artifact bytes copied; metadata redacted; artifacts retained; content not printed',
439
+ ].join('\n');
440
+ }
441
+
442
+ async function exportArtifactArchive(
443
+ store: AgentArtifactBrowserStore,
444
+ args: AgentArtifactsToolArgs,
445
+ options: AgentArtifactsToolOptions,
446
+ ): Promise<string> {
447
+ requireConfirmed(args, 'Agent artifact archive export');
448
+ if (!options.projectRoot) throw new Error('Artifact archive export requires a projectRoot for path validation.');
449
+ const destinationPath = readString(args.destinationPath);
450
+ if (!destinationPath) throw new Error('destinationPath is required for mode:"archive".');
451
+ const resolvedPath = resolveAndValidatePath(destinationPath, options.projectRoot);
452
+ const overwrite = readBoolean(args.overwrite);
453
+ if (existsSync(resolvedPath)) {
454
+ const stats = await stat(resolvedPath);
455
+ if (stats.isDirectory()) {
456
+ throw new Error(`Archive target already exists and is a directory: ${resolvedPath}. Choose a .zip file path.`);
457
+ }
458
+ if (!overwrite) {
459
+ throw new Error(`Archive target already exists: ${resolvedPath}. Pass overwrite:true only after the user confirms replacement.`);
460
+ }
461
+ }
462
+
463
+ const loaded = await loadArtifactsForPackage(store, args, 'archive');
464
+ const payload = buildArtifactPackagePayload(loaded);
465
+ const archive = createZipArchive(payload.entries);
466
+ await mkdir(dirname(resolvedPath), { recursive: true });
467
+ await writeFile(resolvedPath, archive);
468
+
469
+ return [
470
+ 'Exported Agent artifact archive',
471
+ ` path ${resolvedPath}`,
472
+ ` artifacts ${payload.artifactCount}`,
473
+ ` sourceBytes ${payload.totalBytes}`,
474
+ ` archiveBytes ${archive.byteLength}`,
475
+ ' archive zip',
476
+ ' manifest manifest.json',
477
+ ` overwrite ${overwrite ? 'yes' : 'no'}`,
478
+ ' policy exact artifact bytes packaged; metadata redacted; artifacts retained; content not printed',
479
+ ].join('\n');
480
+ }
481
+
482
+ export function createAgentArtifactsTool(
483
+ artifactStore?: AgentArtifactBrowserStore,
484
+ options: AgentArtifactsToolOptions = {},
485
+ ): Tool {
486
+ return {
487
+ definition: {
488
+ name: 'agent_artifacts',
489
+ description: 'Browse, preview, export, package, and archive saved Agent artifacts.',
490
+ parameters: {
491
+ type: 'object',
492
+ properties: {
493
+ mode: {
494
+ type: 'string',
495
+ enum: ['list', 'show', 'export', 'package', 'archive'],
496
+ description: 'List, show, export one artifact, or package/archive selected ids.',
497
+ },
498
+ artifactId: {
499
+ type: 'string',
500
+ description: 'Artifact id for show or export.',
501
+ },
502
+ artifactIds: {
503
+ type: 'array',
504
+ items: { type: 'string' },
505
+ description: 'Artifact ids for mode:"package" or mode:"archive".',
506
+ },
507
+ destinationPath: {
508
+ type: 'string',
509
+ description: 'File path for export/archive; directory path for package.',
510
+ },
511
+ overwrite: {
512
+ type: 'boolean',
513
+ description: 'Allow export to replace an existing file.',
514
+ },
515
+ query: {
516
+ type: 'string',
517
+ description: 'Search id, filename, MIME, source URI, sha256, and metadata.',
518
+ },
519
+ kind: {
520
+ type: 'string',
521
+ description: 'Exact kind filter: file, image, audio, video, document, archive, data.',
522
+ },
523
+ mimeType: {
524
+ type: 'string',
525
+ description: 'Optional MIME type substring filter.',
526
+ },
527
+ purpose: {
528
+ type: 'string',
529
+ description: 'Optional metadata.purpose filter.',
530
+ },
531
+ source: {
532
+ type: 'string',
533
+ description: 'Optional source URI or metadata source filter.',
534
+ },
535
+ limit: {
536
+ type: 'number',
537
+ description: 'Maximum artifacts to return in list mode.',
538
+ },
539
+ includeContent: {
540
+ type: 'boolean',
541
+ description: 'For mode:"show", include a bounded text preview when possible.',
542
+ },
543
+ previewBytes: {
544
+ type: 'number',
545
+ description: 'Maximum text bytes to preview for mode:"show".',
546
+ },
547
+ confirm: {
548
+ type: 'boolean',
549
+ description: 'Required true for export, package, and archive writes.',
550
+ },
551
+ explicitUserRequest: {
552
+ type: 'string',
553
+ description: 'User request authorizing export.',
554
+ },
555
+ },
556
+ required: ['mode'],
557
+ additionalProperties: false,
558
+ },
559
+ sideEffects: ['write_fs'],
560
+ concurrency: 'serial',
561
+ },
562
+ execute: async (rawArgs: Record<string, unknown>) => {
563
+ const args = rawArgs as AgentArtifactsToolArgs;
564
+ const mode = readString(args.mode);
565
+ if (!artifactStore?.list && !artifactStore?.get && !artifactStore?.readContent) {
566
+ return failure('Artifact browsing is unavailable because this runtime did not provide the SDK artifact store.');
567
+ }
568
+ try {
569
+ if (mode === 'list') {
570
+ if (!artifactStore.list) return failure('Artifact list is unavailable in this runtime.');
571
+ const limit = clamp(readNumber(args.limit, DEFAULT_LIST_LIMIT), 1, 100);
572
+ const source = artifactStore.list(Math.max(limit * 4, limit));
573
+ const filtered = source.filter((artifact) => matchesFilter(artifact, args));
574
+ return output(formatList(filtered.slice(0, limit), filtered.length, args));
575
+ }
576
+ if (mode === 'show') {
577
+ const artifactId = readString(args.artifactId);
578
+ if (!artifactId) return failure('artifactId is required for mode:"show".');
579
+ const loaded = await loadArtifact(artifactStore, artifactId, readBoolean(args.includeContent));
580
+ if (!loaded) return failure(`Unknown artifact ${artifactId}. Use agent_artifacts mode:"list" to inspect available artifacts.`);
581
+ return output(formatShow(loaded, args));
582
+ }
583
+ if (mode === 'export') {
584
+ return output(await exportArtifactToFile(artifactStore, args, options));
585
+ }
586
+ if (mode === 'package') {
587
+ return output(await exportArtifactPackage(artifactStore, args, options));
588
+ }
589
+ if (mode === 'archive') {
590
+ return output(await exportArtifactArchive(artifactStore, args, options));
591
+ }
592
+ return failure(`Unknown agent_artifacts mode: ${mode || '<missing>'}. Use mode:"list", mode:"show", mode:"export", mode:"package", or mode:"archive".`);
593
+ } catch (error) {
594
+ return failure(error instanceof Error ? error.message : String(error));
595
+ }
596
+ },
597
+ };
598
+ }
599
+
600
+ export function registerAgentArtifactsTool(
601
+ registry: ToolRegistry,
602
+ artifactStore?: AgentArtifactBrowserStore,
603
+ options: AgentArtifactsToolOptions = {},
604
+ ): void {
605
+ registry.register(createAgentArtifactsTool(artifactStore, options));
606
+ }