@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,441 @@
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 AgentResearchSourceStatus = 'candidate' | 'reviewed' | 'rejected' | 'used';
8
+ export type AgentResearchSourceCredibility = 'unreviewed' | 'low' | 'medium' | 'high' | 'mixed';
9
+
10
+ export interface AgentResearchSourceRecord {
11
+ readonly id: string;
12
+ readonly question: string;
13
+ readonly title: string;
14
+ readonly url?: string;
15
+ readonly publisher?: string;
16
+ readonly publishedAt?: string;
17
+ readonly accessedAt?: string;
18
+ readonly summary: string;
19
+ readonly evidence?: string;
20
+ readonly credibility: AgentResearchSourceCredibility;
21
+ readonly score: number;
22
+ readonly status: AgentResearchSourceStatus;
23
+ readonly tags: readonly string[];
24
+ readonly note?: string;
25
+ readonly usedInReportArtifactId?: string;
26
+ readonly provenance: string;
27
+ readonly createdAt: string;
28
+ readonly updatedAt: string;
29
+ readonly reviewedAt?: string;
30
+ readonly rejectedAt?: string;
31
+ readonly usedAt?: string;
32
+ }
33
+
34
+ export interface AgentResearchSourceCreateInput {
35
+ readonly question: string;
36
+ readonly title: string;
37
+ readonly url?: string;
38
+ readonly publisher?: string;
39
+ readonly publishedAt?: string;
40
+ readonly accessedAt?: string;
41
+ readonly summary: string;
42
+ readonly evidence?: string;
43
+ readonly credibility?: AgentResearchSourceCredibility;
44
+ readonly score?: number;
45
+ readonly tags?: readonly string[];
46
+ readonly note?: string;
47
+ readonly provenance?: string;
48
+ }
49
+
50
+ export interface AgentResearchSourceReviewInput {
51
+ readonly credibility?: AgentResearchSourceCredibility;
52
+ readonly score?: number;
53
+ readonly note?: string;
54
+ readonly summary?: string;
55
+ readonly evidence?: string;
56
+ readonly tags?: readonly string[];
57
+ }
58
+
59
+ export interface AgentResearchSourceUseInput {
60
+ readonly reportArtifactId?: string;
61
+ readonly note?: string;
62
+ }
63
+
64
+ export interface AgentResearchSourceSnapshot {
65
+ readonly path: string;
66
+ readonly sources: readonly AgentResearchSourceRecord[];
67
+ readonly candidates: readonly AgentResearchSourceRecord[];
68
+ readonly reviewed: readonly AgentResearchSourceRecord[];
69
+ readonly rejected: readonly AgentResearchSourceRecord[];
70
+ readonly used: readonly AgentResearchSourceRecord[];
71
+ }
72
+
73
+ interface ResearchSourceStoreFile {
74
+ readonly version: 1;
75
+ readonly sources: readonly AgentResearchSourceRecord[];
76
+ }
77
+
78
+ type AgentResearchSourceStorePaths = Pick<ShellPathService, 'resolveProjectPath'>;
79
+
80
+ const STORE_VERSION = 1;
81
+ const SECRETISH = /token|secret|password|authorization|credential|api[-_]?key/i;
82
+
83
+ function isRecord(value: unknown): value is Record<string, unknown> {
84
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
85
+ }
86
+
87
+ function readString(value: unknown, fallback = ''): string {
88
+ return typeof value === 'string' ? value : fallback;
89
+ }
90
+
91
+ function readStringArray(value: unknown): string[] {
92
+ if (!Array.isArray(value)) return [];
93
+ return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
94
+ }
95
+
96
+ function normalizeText(value: string): string {
97
+ return value.trim().replace(/\s+/g, ' ');
98
+ }
99
+
100
+ function normalizeList(values: readonly string[] | undefined): string[] {
101
+ const seen = new Set<string>();
102
+ const result: string[] = [];
103
+ for (const value of values ?? []) {
104
+ const trimmed = value.trim();
105
+ if (!trimmed) continue;
106
+ const key = trimmed.toLowerCase();
107
+ if (seen.has(key)) continue;
108
+ seen.add(key);
109
+ result.push(trimmed);
110
+ }
111
+ return result;
112
+ }
113
+
114
+ function slugify(value: string): string {
115
+ const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
116
+ return slug || 'source';
117
+ }
118
+
119
+ function nowIso(): string {
120
+ return new Date().toISOString();
121
+ }
122
+
123
+ function credibility(value: unknown): AgentResearchSourceCredibility {
124
+ if (value === 'low' || value === 'medium' || value === 'high' || value === 'mixed') return value;
125
+ return 'unreviewed';
126
+ }
127
+
128
+ function status(value: unknown): AgentResearchSourceStatus {
129
+ if (value === 'reviewed' || value === 'rejected' || value === 'used') return value;
130
+ return 'candidate';
131
+ }
132
+
133
+ function boundedScore(value: unknown, fallback = 50): number {
134
+ const parsed = typeof value === 'number' ? value : typeof value === 'string' && value.trim() ? Number(value) : fallback;
135
+ if (!Number.isFinite(parsed)) return fallback;
136
+ return Math.max(0, Math.min(100, Math.round(parsed)));
137
+ }
138
+
139
+ export function sanitizeResearchSourceUrl(value: string): string {
140
+ try {
141
+ const url = new URL(value);
142
+ for (const key of [...url.searchParams.keys()]) {
143
+ if (SECRETISH.test(key)) url.searchParams.set(key, '<redacted>');
144
+ }
145
+ return url.toString();
146
+ } catch {
147
+ return value.replace(/([?&\s](?:token|secret|password|authorization|credential|api[-_]?key)=)[^\s&]+/gi, '$1<redacted>');
148
+ }
149
+ }
150
+
151
+ function parseSource(value: unknown): AgentResearchSourceRecord | null {
152
+ if (!isRecord(value)) return null;
153
+ const id = readString(value.id).trim();
154
+ const question = normalizeText(readString(value.question));
155
+ const title = normalizeText(readString(value.title));
156
+ const summary = readString(value.summary).trim();
157
+ if (!id || !question || !title || !summary) return null;
158
+ const createdAt = readString(value.createdAt, nowIso());
159
+ const url = readString(value.url).trim();
160
+ const publisher = readString(value.publisher).trim();
161
+ const publishedAt = readString(value.publishedAt).trim();
162
+ const accessedAt = readString(value.accessedAt).trim();
163
+ const evidence = readString(value.evidence).trim();
164
+ const note = readString(value.note).trim();
165
+ const reviewedAt = readString(value.reviewedAt).trim();
166
+ const rejectedAt = readString(value.rejectedAt).trim();
167
+ const usedAt = readString(value.usedAt).trim();
168
+ const usedInReportArtifactId = readString(value.usedInReportArtifactId).trim();
169
+ return {
170
+ id,
171
+ question,
172
+ title,
173
+ ...(url ? { url: sanitizeResearchSourceUrl(url) } : {}),
174
+ ...(publisher ? { publisher } : {}),
175
+ ...(publishedAt ? { publishedAt } : {}),
176
+ ...(accessedAt ? { accessedAt } : {}),
177
+ summary,
178
+ ...(evidence ? { evidence } : {}),
179
+ credibility: credibility(value.credibility),
180
+ score: boundedScore(value.score),
181
+ status: status(value.status),
182
+ tags: readStringArray(value.tags),
183
+ ...(note ? { note } : {}),
184
+ ...(usedInReportArtifactId ? { usedInReportArtifactId } : {}),
185
+ provenance: readString(value.provenance, 'agent-research-sources').trim() || 'agent-research-sources',
186
+ createdAt,
187
+ updatedAt: readString(value.updatedAt, createdAt),
188
+ ...(reviewedAt ? { reviewedAt } : {}),
189
+ ...(rejectedAt ? { rejectedAt } : {}),
190
+ ...(usedAt ? { usedAt } : {}),
191
+ };
192
+ }
193
+
194
+ function parseStore(raw: string): ResearchSourceStoreFile {
195
+ const parsed: unknown = JSON.parse(raw);
196
+ if (!isRecord(parsed)) return { version: STORE_VERSION, sources: [] };
197
+ return {
198
+ version: STORE_VERSION,
199
+ sources: Array.isArray(parsed.sources)
200
+ ? parsed.sources.map(parseSource).filter((entry): entry is AgentResearchSourceRecord => entry !== null)
201
+ : [],
202
+ };
203
+ }
204
+
205
+ function formatStore(store: ResearchSourceStoreFile): string {
206
+ return `${JSON.stringify(store, null, 2)}\n`;
207
+ }
208
+
209
+ function assertSourceContentSafe(fields: readonly string[]): void {
210
+ assertNoSecretLikeText(fields, 'Research source queue');
211
+ }
212
+
213
+ export function researchSourceStorePath(shellPaths: AgentResearchSourceStorePaths): string {
214
+ return shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'research', 'sources.json');
215
+ }
216
+
217
+ export function researchSourceReportLine(source: AgentResearchSourceRecord): string {
218
+ const url = source.url ?? '';
219
+ const note = [source.publisher, source.note, source.summary].filter(Boolean).join(' ');
220
+ return [source.title, url, source.credibility, note].filter(Boolean).join(' | ');
221
+ }
222
+
223
+ export class AgentResearchSourceRegistry {
224
+ public constructor(private readonly storePath: string) {}
225
+
226
+ public static fromShellPaths(shellPaths: AgentResearchSourceStorePaths): AgentResearchSourceRegistry {
227
+ return new AgentResearchSourceRegistry(researchSourceStorePath(shellPaths));
228
+ }
229
+
230
+ public snapshot(): AgentResearchSourceSnapshot {
231
+ const sources = [...this.readStore().sources].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
232
+ return {
233
+ path: this.storePath,
234
+ sources,
235
+ candidates: sources.filter((source) => source.status === 'candidate'),
236
+ reviewed: sources.filter((source) => source.status === 'reviewed'),
237
+ rejected: sources.filter((source) => source.status === 'rejected'),
238
+ used: sources.filter((source) => source.status === 'used'),
239
+ };
240
+ }
241
+
242
+ public list(statusFilter?: AgentResearchSourceStatus): readonly AgentResearchSourceRecord[] {
243
+ const sources = this.snapshot().sources;
244
+ return statusFilter ? sources.filter((source) => source.status === statusFilter) : sources;
245
+ }
246
+
247
+ public search(query: string): readonly AgentResearchSourceRecord[] {
248
+ const normalized = query.trim().toLowerCase();
249
+ if (!normalized) return this.list();
250
+ return this.list().filter((source) => [
251
+ source.id,
252
+ source.question,
253
+ source.title,
254
+ source.url ?? '',
255
+ source.publisher ?? '',
256
+ source.summary,
257
+ source.evidence ?? '',
258
+ source.credibility,
259
+ source.status,
260
+ source.note ?? '',
261
+ ...source.tags,
262
+ ].some((field) => field.toLowerCase().includes(normalized)));
263
+ }
264
+
265
+ public get(idOrTitle: string): AgentResearchSourceRecord | null {
266
+ const lookup = idOrTitle.trim().toLowerCase();
267
+ if (!lookup) return null;
268
+ return this.list().find((source) => (
269
+ source.id.toLowerCase() === lookup
270
+ || source.title.toLowerCase() === lookup
271
+ || source.url?.toLowerCase() === lookup
272
+ )) ?? null;
273
+ }
274
+
275
+ public create(input: AgentResearchSourceCreateInput): AgentResearchSourceRecord {
276
+ const store = this.readStore();
277
+ const question = normalizeText(input.question);
278
+ const title = normalizeText(input.title);
279
+ const summary = input.summary.trim();
280
+ if (!question) throw new Error('question is required.');
281
+ if (!title) throw new Error('title is required.');
282
+ if (!summary) throw new Error('summary is required.');
283
+ const url = input.url?.trim() ? sanitizeResearchSourceUrl(input.url.trim()) : undefined;
284
+ const publisher = input.publisher?.trim() || undefined;
285
+ const publishedAt = input.publishedAt?.trim() || undefined;
286
+ const accessedAt = input.accessedAt?.trim() || nowIso();
287
+ const evidence = input.evidence?.trim() || undefined;
288
+ const note = input.note?.trim() || undefined;
289
+ const tags = normalizeList(input.tags);
290
+ assertSourceContentSafe([question, title, publisher ?? '', summary, evidence ?? '', note ?? '', ...tags]);
291
+ const duplicate = store.sources.find((source) => (
292
+ (url && source.url?.toLowerCase() === url.toLowerCase())
293
+ || (!url && source.title.toLowerCase() === title.toLowerCase() && source.question.toLowerCase() === question.toLowerCase())
294
+ ));
295
+ if (duplicate) throw new Error(`Research source already exists ${duplicate.id}.`);
296
+ const timestamp = nowIso();
297
+ const source: AgentResearchSourceRecord = {
298
+ id: this.nextId(title, store.sources),
299
+ question,
300
+ title,
301
+ ...(url ? { url } : {}),
302
+ ...(publisher ? { publisher } : {}),
303
+ ...(publishedAt ? { publishedAt } : {}),
304
+ accessedAt,
305
+ summary,
306
+ ...(evidence ? { evidence } : {}),
307
+ credibility: input.credibility ?? 'unreviewed',
308
+ score: boundedScore(input.score, input.credibility === 'high' ? 80 : input.credibility === 'medium' ? 60 : 50),
309
+ status: input.credibility && input.credibility !== 'unreviewed' ? 'reviewed' : 'candidate',
310
+ tags,
311
+ ...(note ? { note } : {}),
312
+ provenance: input.provenance?.trim() || 'agent-research-sources',
313
+ createdAt: timestamp,
314
+ updatedAt: timestamp,
315
+ ...(input.credibility && input.credibility !== 'unreviewed' ? { reviewedAt: timestamp } : {}),
316
+ };
317
+ this.writeStore({ ...store, sources: [...store.sources, source] });
318
+ return source;
319
+ }
320
+
321
+ public review(idOrTitle: string, input: AgentResearchSourceReviewInput): AgentResearchSourceRecord {
322
+ const store = this.readStore();
323
+ const existing = this.findInStore(store, idOrTitle);
324
+ if (!existing) throw new Error(`Unknown research source ${idOrTitle}.`);
325
+ const tags = input.tags === undefined ? existing.tags : normalizeList(input.tags);
326
+ const note = input.note === undefined ? existing.note : input.note.trim() || undefined;
327
+ const summary = input.summary === undefined ? existing.summary : input.summary.trim();
328
+ if (!summary) throw new Error('summary is required.');
329
+ const evidence = input.evidence === undefined ? existing.evidence : input.evidence.trim() || undefined;
330
+ assertSourceContentSafe([summary, evidence ?? '', note ?? '', ...tags]);
331
+ const credibilityValue = input.credibility ?? (existing.credibility === 'unreviewed' ? 'medium' : existing.credibility);
332
+ const timestamp = nowIso();
333
+ const { evidence: _oldEvidence, note: _oldNote, rejectedAt: _oldRejectedAt, ...base } = existing;
334
+ const reviewed: AgentResearchSourceRecord = {
335
+ ...base,
336
+ summary,
337
+ ...(evidence ? { evidence } : {}),
338
+ credibility: credibilityValue,
339
+ score: boundedScore(input.score, existing.score),
340
+ status: 'reviewed',
341
+ tags,
342
+ ...(note ? { note } : {}),
343
+ reviewedAt: timestamp,
344
+ updatedAt: timestamp,
345
+ };
346
+ this.writeStore({
347
+ ...store,
348
+ sources: store.sources.map((source) => source.id === existing.id ? reviewed : source),
349
+ });
350
+ return reviewed;
351
+ }
352
+
353
+ public reject(idOrTitle: string, reason: string): AgentResearchSourceRecord {
354
+ const store = this.readStore();
355
+ const existing = this.findInStore(store, idOrTitle);
356
+ if (!existing) throw new Error(`Unknown research source ${idOrTitle}.`);
357
+ const note = reason.trim() || 'Rejected during source review.';
358
+ assertSourceContentSafe([note]);
359
+ const timestamp = nowIso();
360
+ const rejected: AgentResearchSourceRecord = {
361
+ ...existing,
362
+ note,
363
+ status: 'rejected',
364
+ rejectedAt: timestamp,
365
+ updatedAt: timestamp,
366
+ };
367
+ this.writeStore({
368
+ ...store,
369
+ sources: store.sources.map((source) => source.id === existing.id ? rejected : source),
370
+ });
371
+ return rejected;
372
+ }
373
+
374
+ public markUsed(idOrTitle: string, input: AgentResearchSourceUseInput = {}): AgentResearchSourceRecord {
375
+ const store = this.readStore();
376
+ const existing = this.findInStore(store, idOrTitle);
377
+ if (!existing) throw new Error(`Unknown research source ${idOrTitle}.`);
378
+ const reportArtifactId = input.reportArtifactId?.trim() || existing.usedInReportArtifactId;
379
+ const note = input.note === undefined ? existing.note : input.note.trim() || undefined;
380
+ assertSourceContentSafe([reportArtifactId ?? '', note ?? '']);
381
+ const timestamp = nowIso();
382
+ const { note: _oldNote, usedInReportArtifactId: _oldArtifactId, ...base } = existing;
383
+ const used: AgentResearchSourceRecord = {
384
+ ...base,
385
+ ...(note ? { note } : {}),
386
+ ...(reportArtifactId ? { usedInReportArtifactId: reportArtifactId } : {}),
387
+ status: 'used',
388
+ usedAt: timestamp,
389
+ updatedAt: timestamp,
390
+ };
391
+ this.writeStore({
392
+ ...store,
393
+ sources: store.sources.map((source) => source.id === existing.id ? used : source),
394
+ });
395
+ return used;
396
+ }
397
+
398
+ public delete(idOrTitle: string): AgentResearchSourceRecord {
399
+ const store = this.readStore();
400
+ const existing = this.findInStore(store, idOrTitle);
401
+ if (!existing) throw new Error(`Unknown research source ${idOrTitle}.`);
402
+ this.writeStore({
403
+ ...store,
404
+ sources: store.sources.filter((source) => source.id !== existing.id),
405
+ });
406
+ return existing;
407
+ }
408
+
409
+ private readStore(): ResearchSourceStoreFile {
410
+ if (!existsSync(this.storePath)) return { version: STORE_VERSION, sources: [] };
411
+ return parseStore(readFileSync(this.storePath, 'utf-8'));
412
+ }
413
+
414
+ private writeStore(store: ResearchSourceStoreFile): void {
415
+ mkdirSync(dirname(this.storePath), { recursive: true });
416
+ const tempPath = `${this.storePath}.tmp`;
417
+ writeFileSync(tempPath, formatStore(store), 'utf-8');
418
+ renameSync(tempPath, this.storePath);
419
+ }
420
+
421
+ private nextId(title: string, sources: readonly AgentResearchSourceRecord[]): string {
422
+ const base = slugify(title);
423
+ const existing = new Set(sources.map((source) => source.id));
424
+ if (!existing.has(base)) return base;
425
+ for (let index = 2; index < 10_000; index += 1) {
426
+ const candidate = `${base}-${index}`;
427
+ if (!existing.has(candidate)) return candidate;
428
+ }
429
+ throw new Error('Unable to allocate a unique research source id.');
430
+ }
431
+
432
+ private findInStore(store: ResearchSourceStoreFile, idOrTitle: string): AgentResearchSourceRecord | null {
433
+ const lookup = idOrTitle.trim().toLowerCase();
434
+ if (!lookup) return null;
435
+ return store.sources.find((source) => (
436
+ source.id.toLowerCase() === lookup
437
+ || source.title.toLowerCase() === lookup
438
+ || source.url?.toLowerCase() === lookup
439
+ )) ?? null;
440
+ }
441
+ }
@@ -410,11 +410,21 @@ export function evaluateAgentRoutineReadiness(
410
410
  }
411
411
 
412
412
  export function buildEnabledRoutinesPrompt(shellPaths: ShellPathService): string | null {
413
- const enabled = AgentRoutineRegistry.fromShellPaths(shellPaths).snapshot().enabledRoutines;
414
- if (enabled.length === 0) return null;
413
+ const enabledRecords = AgentRoutineRegistry.fromShellPaths(shellPaths).snapshot().enabledRoutines;
414
+ const enabled = enabledRecords.filter((routine) => routine.reviewState === 'reviewed' && evaluateAgentRoutineReadiness(routine).ready);
415
+ const suppressed = enabledRecords
416
+ .filter((routine) => !enabled.some((activeRoutine) => activeRoutine.id === routine.id))
417
+ .slice(0, 8)
418
+ .map((routine) => {
419
+ const readiness = evaluateAgentRoutineReadiness(routine);
420
+ const review = routine.reviewState === 'reviewed' ? '' : `review=${formatAgentRecordReviewState(routine.reviewState)}`;
421
+ const setup = readiness.ready ? '' : `missing=${readiness.missing.map(formatAgentSkillRequirement).join(', ')}`;
422
+ return `- ${routine.name}: ${[review, setup].filter(Boolean).join('; ')}`;
423
+ });
424
+ if (enabled.length === 0 && suppressed.length === 0) return null;
415
425
  return [
416
426
  '## Enabled GoodVibes Agent Routines',
417
- 'Use these local reusable routines inside the same serial assistant conversation when they fit the user request. Do not start hidden background jobs because a routine matches.',
427
+ 'Use only reviewed, setup-ready local reusable routines inside the same serial assistant conversation when they fit the user request. Do not start hidden background jobs because a routine matches.',
418
428
  '',
419
429
  ...enabled.slice(0, 8).flatMap((routine) => [
420
430
  `### ${routine.name}`,
@@ -425,5 +435,11 @@ export function buildEnabledRoutinesPrompt(shellPaths: ShellPathService): string
425
435
  routine.steps,
426
436
  '',
427
437
  ]),
438
+ ...(suppressed.length > 0 ? [
439
+ '### Suppressed Routines Pending Review Or Setup',
440
+ 'Do not apply these routines until the user reviews them or resolves setup through the learning curator.',
441
+ ...suppressed,
442
+ '',
443
+ ] : []),
428
444
  ].join('\n').trim();
429
445
  }
@@ -8,6 +8,7 @@ import {
8
8
  type RoutineScheduleReceipt,
9
9
  type RoutineScheduleReceiptSnapshot,
10
10
  } from './routine-schedule-promotion.ts';
11
+ import { scheduleConfirmationRouteLines, scheduleNextRouteLines, scheduleRouteArg } from './schedule-next-routes.ts';
11
12
 
12
13
  function isRecord(value: unknown): value is Record<string, unknown> {
13
14
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
@@ -47,6 +48,27 @@ function formatRoutineScheduleFailureKind(kind: string): string {
47
48
  return kind.replace(/[_-]+/g, ' ');
48
49
  }
49
50
 
51
+ function routineScheduleSelector(preview: RoutineSchedulePromotionPreview): { readonly flag: string; readonly value: string } {
52
+ if (preview.payload.kind === 'cron') return { flag: '--cron', value: String(preview.payload.cron ?? '') };
53
+ if (preview.payload.kind === 'every') return { flag: '--every', value: String(preview.payload.every ?? '') };
54
+ return { flag: '--at', value: String(preview.payload.at ?? '') };
55
+ }
56
+
57
+ function routineScheduleConfirmCommand(preview: RoutineSchedulePromotionPreview): string {
58
+ const selector = routineScheduleSelector(preview);
59
+ const args = [
60
+ '/schedule promote-routine',
61
+ scheduleRouteArg(preview.routineId),
62
+ selector.flag,
63
+ scheduleRouteArg(selector.value),
64
+ ];
65
+ if (typeof preview.payload.timezone === 'string' && preview.payload.timezone) {
66
+ args.push('--timezone', scheduleRouteArg(preview.payload.timezone));
67
+ }
68
+ args.push('--yes');
69
+ return args.join(' ');
70
+ }
71
+
50
72
  export function formatRoutineSchedulePreview(preview: RoutineSchedulePromotionPreview): string {
51
73
  const schedule = preview.payload.kind === 'cron'
52
74
  ? `${preview.payload.cron}${preview.payload.timezone ? ` [${preview.payload.timezone}]` : ''}`
@@ -65,7 +87,10 @@ export function formatRoutineSchedulePreview(preview: RoutineSchedulePromotionPr
65
87
  ` delivery: ${delivery?.mode ?? 'none'}${deliveryTargetCount > 0 ? ` (${deliveryTargetCount} target${deliveryTargetCount === 1 ? '' : 's'})` : ''}`,
66
88
  ' target connected GoodVibes host/main conversation route',
67
89
  ' policy isolated Agent Knowledge only; no default knowledge/non-Agent fallback; no delegated review unless explicitly requested',
68
- ' next rerun with --yes to create this connected schedule',
90
+ ...scheduleConfirmationRouteLines({
91
+ workspace: 'workspace action:"run" actionId:"schedule-promote-routine" confirm:true explicitUserRequest:"..."',
92
+ cli: routineScheduleConfirmCommand(preview),
93
+ }),
69
94
  ].join('\n');
70
95
  }
71
96
 
@@ -79,7 +104,7 @@ export function formatRoutineScheduleSuccess(result: RoutineSchedulePromotionSuc
79
104
  ` schedule ${id}`,
80
105
  ` status ${status}`,
81
106
  ` route ${result.kind} ${result.route}`,
82
- ' next inspect with /schedule list or schedule observability',
107
+ ...scheduleNextRouteLines(id),
83
108
  ].join('\n');
84
109
  }
85
110
 
@@ -22,6 +22,13 @@ export interface AgentRuntimeProfileStarterTemplate extends AgentRuntimeProfileT
22
22
  readonly triggers: readonly string[];
23
23
  readonly tags: readonly string[];
24
24
  }[];
25
+ readonly vibe?: {
26
+ readonly body: string;
27
+ readonly source: 'discovered' | 'template';
28
+ readonly appliedAs: 'global';
29
+ readonly sourcePaths: readonly string[];
30
+ readonly truncated: number;
31
+ };
25
32
  }
26
33
 
27
34
  export interface AgentRuntimeProfileStarterTemplateFile {