@pellux/goodvibes-agent 1.1.7 → 1.3.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 (274) hide show
  1. package/CHANGELOG.md +329 -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 +138903 -91477
  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 +58 -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 +116 -45
  14. package/docs/voice-and-live-tts.md +3 -1
  15. package/package.json +3 -2
  16. package/release/release-notes.md +142 -5
  17. package/release/release-readiness.json +124 -94
  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 +294 -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-artifact-receipts.ts +366 -0
  42. package/src/agent/setup-wizard-checkpoint.ts +140 -0
  43. package/src/agent/setup-wizard.ts +683 -0
  44. package/src/agent/skill-registry-types.ts +102 -0
  45. package/src/agent/skill-registry.ts +81 -107
  46. package/src/agent/vibe-confirmation-routes.ts +62 -0
  47. package/src/agent/vibe-file.ts +285 -0
  48. package/src/cli/agent-knowledge-command.ts +45 -2
  49. package/src/cli/help.ts +3 -0
  50. package/src/cli/profiles-command.ts +25 -11
  51. package/src/config/agent-settings-policy.ts +1 -18
  52. package/src/input/agent-workspace-activation.ts +53 -0
  53. package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
  54. package/src/input/agent-workspace-artifact-metadata.ts +17 -0
  55. package/src/input/agent-workspace-basic-command-editors.ts +5 -1
  56. package/src/input/agent-workspace-categories.ts +153 -155
  57. package/src/input/agent-workspace-channel-triage.ts +481 -0
  58. package/src/input/agent-workspace-channels.ts +263 -0
  59. package/src/input/agent-workspace-command-editor.ts +152 -0
  60. package/src/input/agent-workspace-context-snapshot.ts +349 -0
  61. package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
  62. package/src/input/agent-workspace-document-editor.ts +502 -0
  63. package/src/input/agent-workspace-document-ops-editor.ts +523 -0
  64. package/src/input/agent-workspace-host-category.ts +1 -1
  65. package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
  66. package/src/input/agent-workspace-model-compare-editor.ts +376 -0
  67. package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
  68. package/src/input/agent-workspace-model-compare-types.ts +82 -0
  69. package/src/input/agent-workspace-model-compare-utils.ts +35 -0
  70. package/src/input/agent-workspace-onboarding-categories.ts +141 -0
  71. package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
  72. package/src/input/agent-workspace-operations-command-editors.ts +22 -0
  73. package/src/input/agent-workspace-research-report-editor.ts +212 -0
  74. package/src/input/agent-workspace-research-run-editor.ts +144 -0
  75. package/src/input/agent-workspace-research-source-editor.ts +167 -0
  76. package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
  77. package/src/input/agent-workspace-review-packet-utils.ts +265 -0
  78. package/src/input/agent-workspace-settings.ts +313 -30
  79. package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
  80. package/src/input/agent-workspace-setup-snapshot.ts +217 -0
  81. package/src/input/agent-workspace-setup.ts +61 -1
  82. package/src/input/agent-workspace-snapshot.ts +184 -149
  83. package/src/input/agent-workspace-types.ts +287 -2
  84. package/src/input/agent-workspace.ts +23 -1
  85. package/src/input/command-registry.ts +9 -2
  86. package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
  87. package/src/input/commands/channels-runtime.ts +102 -114
  88. package/src/input/commands/delegation-runtime.ts +87 -24
  89. package/src/input/commands/knowledge-browser-flags.ts +12 -0
  90. package/src/input/commands/knowledge.ts +28 -14
  91. package/src/input/commands/operator-actions-runtime.ts +6 -3
  92. package/src/input/commands/schedule-runtime.ts +71 -14
  93. package/src/input/commands/session-content.ts +2 -2
  94. package/src/input/commands/shell-core.ts +2 -0
  95. package/src/input/commands/vibe-runtime.ts +140 -0
  96. package/src/input/commands.ts +2 -0
  97. package/src/input/connected-host-routes.ts +123 -0
  98. package/src/input/settings-modal-types.ts +14 -0
  99. package/src/input/setup-wizard-live-receipts.ts +76 -0
  100. package/src/main.ts +15 -15
  101. package/src/panels/qr-panel.ts +2 -2
  102. package/src/renderer/agent-workspace-context-lines.ts +701 -0
  103. package/src/renderer/agent-workspace.ts +68 -421
  104. package/src/renderer/settings-modal-helpers.ts +12 -0
  105. package/src/renderer/settings-modal.ts +12 -0
  106. package/src/runtime/bootstrap-command-context.ts +11 -1
  107. package/src/runtime/bootstrap-command-parts.ts +14 -3
  108. package/src/runtime/bootstrap-core.ts +39 -1
  109. package/src/runtime/bootstrap-shell.ts +6 -0
  110. package/src/runtime/bootstrap.ts +113 -30
  111. package/src/runtime/connected-host-auth.ts +3 -2
  112. package/src/runtime/execution-ledger.ts +231 -0
  113. package/src/runtime/services.ts +88 -10
  114. package/src/runtime/tool-permission-safety.ts +199 -5
  115. package/src/shell/session-continuity-hints.ts +39 -0
  116. package/src/tools/agent-artifacts-tool.ts +606 -0
  117. package/src/tools/agent-audit-tool.ts +155 -0
  118. package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
  119. package/src/tools/agent-autonomy-tool.ts +140 -0
  120. package/src/tools/agent-channel-send-tool.ts +24 -2
  121. package/src/tools/agent-channels-tool.ts +140 -0
  122. package/src/tools/agent-computer-tool.ts +168 -0
  123. package/src/tools/agent-context-policy.ts +137 -7
  124. package/src/tools/agent-context-tool.ts +143 -0
  125. package/src/tools/agent-delegation-tool.ts +128 -0
  126. package/src/tools/agent-device-tool.ts +240 -0
  127. package/src/tools/agent-documents-tool.ts +684 -0
  128. package/src/tools/agent-execution-tool.ts +230 -0
  129. package/src/tools/agent-harness-agent-orchestration-policy.ts +75 -0
  130. package/src/tools/agent-harness-agent-orchestration.ts +791 -0
  131. package/src/tools/agent-harness-autonomy-intake.ts +785 -0
  132. package/src/tools/agent-harness-autonomy-live-records.ts +742 -0
  133. package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
  134. package/src/tools/agent-harness-autonomy-queue.ts +571 -0
  135. package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
  136. package/src/tools/agent-harness-background-processes-types.ts +35 -0
  137. package/src/tools/agent-harness-background-processes.ts +798 -0
  138. package/src/tools/agent-harness-browser-cockpit-route.ts +373 -0
  139. package/src/tools/agent-harness-browser-control.ts +346 -0
  140. package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -0
  141. package/src/tools/agent-harness-channel-metadata.ts +168 -10
  142. package/src/tools/agent-harness-cli-command-policy.ts +110 -0
  143. package/src/tools/agent-harness-command-catalog.ts +4 -2
  144. package/src/tools/agent-harness-command-runner.ts +42 -0
  145. package/src/tools/agent-harness-connected-host-status.ts +7 -4
  146. package/src/tools/agent-harness-delegation-posture.ts +234 -19
  147. package/src/tools/agent-harness-device-live-read-models.ts +366 -0
  148. package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
  149. package/src/tools/agent-harness-document-ops-types.ts +72 -0
  150. package/src/tools/agent-harness-document-ops.ts +671 -0
  151. package/src/tools/agent-harness-execution-history.ts +489 -0
  152. package/src/tools/agent-harness-execution-posture.ts +385 -0
  153. package/src/tools/agent-harness-file-recovery.ts +135 -0
  154. package/src/tools/agent-harness-interactive-runtime-records.ts +421 -0
  155. package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
  156. package/src/tools/agent-harness-learning-curator-common.ts +102 -0
  157. package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
  158. package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
  159. package/src/tools/agent-harness-learning-curator-types.ts +151 -0
  160. package/src/tools/agent-harness-learning-curator.ts +417 -0
  161. package/src/tools/agent-harness-local-model-benchmarks.ts +269 -0
  162. package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
  163. package/src/tools/agent-harness-local-model-endpoints.ts +788 -0
  164. package/src/tools/agent-harness-local-model-smoke.ts +277 -0
  165. package/src/tools/agent-harness-local-model-url.ts +78 -0
  166. package/src/tools/agent-harness-media-posture.ts +223 -2
  167. package/src/tools/agent-harness-memory-external-providers.ts +796 -0
  168. package/src/tools/agent-harness-memory-posture.ts +672 -0
  169. package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
  170. package/src/tools/agent-harness-memory-refinement.ts +340 -0
  171. package/src/tools/agent-harness-metadata.ts +100 -168
  172. package/src/tools/agent-harness-mode-catalog.ts +86 -33
  173. package/src/tools/agent-harness-model-catalog.ts +162 -0
  174. package/src/tools/agent-harness-model-provider-health.ts +283 -0
  175. package/src/tools/agent-harness-model-readiness.ts +432 -0
  176. package/src/tools/agent-harness-model-routing-types.ts +327 -0
  177. package/src/tools/agent-harness-model-routing-utils.ts +30 -0
  178. package/src/tools/agent-harness-model-routing.ts +165 -193
  179. package/src/tools/agent-harness-operator-methods.ts +115 -133
  180. package/src/tools/agent-harness-pairing-posture.ts +452 -18
  181. package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
  182. package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
  183. package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
  184. package/src/tools/agent-harness-personal-ops-lanes.ts +270 -0
  185. package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
  186. package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
  187. package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
  188. package/src/tools/agent-harness-personal-ops-records.ts +684 -0
  189. package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
  190. package/src/tools/agent-harness-personal-ops-types.ts +240 -0
  191. package/src/tools/agent-harness-personal-ops.ts +613 -0
  192. package/src/tools/agent-harness-project-context.ts +144 -0
  193. package/src/tools/agent-harness-prompt-context.ts +589 -0
  194. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  195. package/src/tools/agent-harness-release-evidence.ts +9 -7
  196. package/src/tools/agent-harness-release-readiness.ts +9 -7
  197. package/src/tools/agent-harness-remote-read-models.ts +541 -0
  198. package/src/tools/agent-harness-research-briefing.ts +446 -0
  199. package/src/tools/agent-harness-research-live-read-models.ts +500 -0
  200. package/src/tools/agent-harness-research-queue.ts +250 -0
  201. package/src/tools/agent-harness-research-runs.ts +307 -0
  202. package/src/tools/agent-harness-research-workflow.ts +395 -0
  203. package/src/tools/agent-harness-security-posture.ts +2 -2
  204. package/src/tools/agent-harness-service-posture.ts +9 -9
  205. package/src/tools/agent-harness-session-metadata.ts +3 -3
  206. package/src/tools/agent-harness-setup-connected-host.ts +501 -0
  207. package/src/tools/agent-harness-setup-describe.ts +557 -0
  208. package/src/tools/agent-harness-setup-handoffs.ts +502 -0
  209. package/src/tools/agent-harness-setup-model-helpers.ts +102 -0
  210. package/src/tools/agent-harness-setup-plan.ts +253 -0
  211. package/src/tools/agent-harness-setup-posture-types.ts +218 -0
  212. package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
  213. package/src/tools/agent-harness-setup-posture.ts +449 -221
  214. package/src/tools/agent-harness-setup-smoke.ts +533 -0
  215. package/src/tools/agent-harness-sudo-posture.ts +114 -0
  216. package/src/tools/agent-harness-tool-schema.ts +142 -7
  217. package/src/tools/agent-harness-tool-types.ts +83 -0
  218. package/src/tools/agent-harness-tool-utils.ts +43 -0
  219. package/src/tools/agent-harness-tool.ts +319 -370
  220. package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
  221. package/src/tools/agent-harness-ui-surface-types.ts +46 -0
  222. package/src/tools/agent-harness-vibe-health.ts +105 -0
  223. package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
  224. package/src/tools/agent-harness-workspace-actions.ts +114 -8
  225. package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
  226. package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
  227. package/src/tools/agent-host-tool.ts +159 -0
  228. package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
  229. package/src/tools/agent-learning-consolidation-core.ts +327 -0
  230. package/src/tools/agent-learning-consolidation-tool.ts +758 -0
  231. package/src/tools/agent-memory-tool.ts +253 -0
  232. package/src/tools/agent-model-compare-export.ts +315 -0
  233. package/src/tools/agent-model-compare-handoff.ts +592 -0
  234. package/src/tools/agent-model-compare-judgment.ts +633 -0
  235. package/src/tools/agent-model-compare-run.ts +735 -0
  236. package/src/tools/agent-model-compare-tool.ts +698 -0
  237. package/src/tools/agent-model-compare-types.ts +191 -0
  238. package/src/tools/agent-model-compare-utils.ts +93 -0
  239. package/src/tools/agent-models-tool.ts +208 -0
  240. package/src/tools/agent-operator-action-tool.ts +1 -1
  241. package/src/tools/agent-operator-method-tool.ts +643 -0
  242. package/src/tools/agent-personal-ops-tool.ts +197 -0
  243. package/src/tools/agent-policy-explanation.ts +415 -0
  244. package/src/tools/agent-research-report-tool.ts +608 -0
  245. package/src/tools/agent-research-runner.ts +367 -0
  246. package/src/tools/agent-research-runs-tool.ts +434 -0
  247. package/src/tools/agent-research-sources-tool.ts +443 -0
  248. package/src/tools/agent-research-tool.ts +515 -0
  249. package/src/tools/agent-review-packet-presets-core.ts +757 -0
  250. package/src/tools/agent-review-packet-presets-tool.ts +466 -0
  251. package/src/tools/agent-review-packet-share-tool.ts +305 -0
  252. package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
  253. package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
  254. package/src/tools/agent-route-planner-candidates-work.ts +251 -0
  255. package/src/tools/agent-route-planner-helpers.ts +667 -0
  256. package/src/tools/agent-route-planner.ts +185 -0
  257. package/src/tools/agent-route-tool.ts +105 -0
  258. package/src/tools/agent-schedule-edit-tool.ts +210 -0
  259. package/src/tools/agent-schedule-tool.ts +282 -0
  260. package/src/tools/agent-security-tool.ts +145 -0
  261. package/src/tools/agent-sessions-tool.ts +122 -0
  262. package/src/tools/agent-settings-import-tool.ts +104 -0
  263. package/src/tools/agent-settings-tool.ts +176 -0
  264. package/src/tools/agent-setup-tool.ts +226 -0
  265. package/src/tools/agent-support-tool.ts +122 -0
  266. package/src/tools/agent-terminal-process-tools.ts +167 -0
  267. package/src/tools/agent-tool-policy-guard-types.ts +77 -0
  268. package/src/tools/agent-tool-policy-guard.ts +106 -85
  269. package/src/tools/agent-vibe-tool.ts +297 -0
  270. package/src/tools/agent-work-plan-tool.ts +265 -6
  271. package/src/tools/agent-workspace-tool.ts +305 -0
  272. package/src/tools/artifact-archive.ts +169 -0
  273. package/src/tools/tool-definition-compaction.ts +36 -0
  274. package/src/version.ts +1 -1
@@ -0,0 +1,151 @@
1
+ export type LearningCandidateStatus =
2
+ | 'needs-review'
3
+ | 'needs-setup'
4
+ | 'needs-consolidation'
5
+ | 'low-confidence'
6
+ | 'proposal-ready'
7
+ | 'ready-to-promote'
8
+ | 'ready';
9
+ export type LocalLearningCandidateDomain = 'memory' | 'note' | 'persona' | 'skill' | 'skill_bundle' | 'routine';
10
+ export type LearningCandidateDomain = LocalLearningCandidateDomain | 'work_plan' | 'research_run' | 'session' | 'capture' | 'vibe';
11
+ export type LearningProposalTarget = 'memory' | 'skill' | 'routine' | 'persona';
12
+ export type VibeCandidateKind = 'blocked' | 'truncated';
13
+
14
+ export interface SessionInfoLike {
15
+ readonly name: string;
16
+ readonly title?: string;
17
+ readonly model?: string;
18
+ readonly provider?: string;
19
+ readonly timestamp?: number;
20
+ readonly messageCount?: number;
21
+ readonly filePath?: string;
22
+ }
23
+
24
+ export interface SessionManagerLike {
25
+ readonly list?: () => readonly SessionInfoLike[];
26
+ readonly load?: (name: string) => { readonly meta?: { readonly title?: string }; readonly messages?: readonly unknown[] };
27
+ }
28
+
29
+ export interface AgentHarnessLearningCuratorArgs {
30
+ readonly candidateId?: unknown;
31
+ readonly target?: unknown;
32
+ readonly query?: unknown;
33
+ readonly includeParameters?: unknown;
34
+ readonly limit?: unknown;
35
+ }
36
+
37
+ export interface LearningScores {
38
+ readonly usefulness: number;
39
+ readonly freshness: number;
40
+ readonly sourceQuality: number;
41
+ readonly risk: number;
42
+ }
43
+
44
+ export interface LearningConsolidationDiff {
45
+ readonly field: string;
46
+ readonly survivor: string;
47
+ readonly duplicates: readonly string[];
48
+ readonly merged: string;
49
+ }
50
+
51
+ export interface LearningConsolidationFields {
52
+ readonly detail?: string;
53
+ readonly description?: string;
54
+ readonly tags?: readonly string[];
55
+ readonly triggers?: readonly string[];
56
+ }
57
+
58
+ export interface LearningConsolidationPlan {
59
+ readonly survivorId: string;
60
+ readonly duplicateIds: readonly string[];
61
+ readonly sharedKey: string;
62
+ readonly diffs: readonly LearningConsolidationDiff[];
63
+ readonly updateFields?: LearningConsolidationFields;
64
+ readonly rollbackFields: LearningConsolidationFields;
65
+ readonly updateRoute?: string;
66
+ readonly staleRoutes: readonly string[];
67
+ readonly deleteRoutes: readonly string[];
68
+ readonly rollbackRoutes: readonly string[];
69
+ }
70
+
71
+ export interface LearningCandidate {
72
+ readonly id: string;
73
+ readonly label: string;
74
+ readonly domain: LearningCandidateDomain;
75
+ readonly recordId: string | null;
76
+ readonly status: LearningCandidateStatus;
77
+ readonly priority: number;
78
+ readonly reason: string;
79
+ readonly next: string;
80
+ readonly scores: LearningScores;
81
+ readonly reviewState?: string;
82
+ readonly enabled?: boolean;
83
+ readonly active?: boolean;
84
+ readonly confidence?: number;
85
+ readonly proposalTarget?: LearningProposalTarget;
86
+ readonly proposalFields?: Readonly<Record<string, string>>;
87
+ readonly missingRequirements?: readonly string[];
88
+ readonly inspectRoute: string;
89
+ readonly modelRoute: string;
90
+ readonly reviewRoute?: string;
91
+ readonly staleRoute?: string;
92
+ readonly updateRoute?: string;
93
+ readonly createRoute?: string;
94
+ readonly deleteRoute?: string;
95
+ readonly cleanupRoutes?: readonly string[];
96
+ readonly rollbackRoutes?: readonly string[];
97
+ readonly consolidation?: LearningConsolidationPlan;
98
+ }
99
+
100
+ export interface LearningConsolidationBatchCandidate {
101
+ readonly candidateId: string;
102
+ readonly label: string;
103
+ readonly domain: LearningCandidateDomain;
104
+ readonly survivorId: string;
105
+ readonly duplicateCount: number;
106
+ readonly duplicateIds?: readonly string[];
107
+ readonly diffFields: readonly string[];
108
+ readonly detailRoute: string;
109
+ readonly applyRoute: string;
110
+ readonly mergeRoute: string;
111
+ readonly stalePhaseRoute: string;
112
+ readonly deletePhaseRoute: string;
113
+ readonly updateRoute?: string;
114
+ readonly staleRoute?: string;
115
+ readonly deleteRoute?: string;
116
+ readonly staleRoutes?: readonly string[];
117
+ readonly deleteRoutes?: readonly string[];
118
+ readonly rollbackRoutes?: readonly string[];
119
+ }
120
+
121
+ export interface LearningConsolidationBatchPlan {
122
+ readonly status: 'ready';
123
+ readonly candidates: number;
124
+ readonly duplicateRecords: number;
125
+ readonly domains: readonly Record<string, unknown>[];
126
+ readonly routes: Record<string, string>;
127
+ readonly phases: readonly Record<string, unknown>[];
128
+ readonly topCandidates: readonly LearningConsolidationBatchCandidate[];
129
+ readonly policy: string;
130
+ }
131
+
132
+ export interface LearningPromptPlan {
133
+ readonly status: 'ready' | 'attention' | 'empty';
134
+ readonly promptActiveCount: number;
135
+ readonly suppressedCount: number;
136
+ readonly proposalCount: number;
137
+ readonly consolidationCount: number;
138
+ readonly promptActiveRecords: readonly Record<string, unknown>[];
139
+ readonly reviewFirst: readonly Record<string, unknown>[];
140
+ readonly proposalQueue: readonly Record<string, unknown>[];
141
+ readonly consolidationQueue: readonly Record<string, unknown>[];
142
+ readonly suppressed: Record<string, number>;
143
+ readonly orderingRules: readonly string[];
144
+ readonly routes: Record<string, string>;
145
+ readonly policy: string;
146
+ }
147
+
148
+ export type LearningCandidateResolution =
149
+ | { readonly status: 'found'; readonly candidate: Record<string, unknown> }
150
+ | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
151
+ | { readonly status: 'missing_lookup'; readonly usage: string };
@@ -0,0 +1,417 @@
1
+ import type { CommandContext } from '../input/command-registry.ts';
2
+ import { buildAgentWorkspaceRuntimeSnapshot } from '../input/agent-workspace-snapshot.ts';
3
+ import type { AgentWorkspaceLocalLibraryItem } from '../input/agent-workspace-types.ts';
4
+ import { previewHarnessText } from './agent-harness-text.ts';
5
+ import { buildLearningCandidates, candidateSearchText } from './agent-harness-learning-curator-proposals.ts';
6
+ import { clampScore, isReviewed, localRegistryRoute, missingRequirementCount, routeValue, scoresForItem } from './agent-harness-learning-curator-common.ts';
7
+ import type { AgentHarnessLearningCuratorArgs, LearningCandidate, LearningCandidateResolution, LearningConsolidationBatchPlan, LearningPromptPlan, LocalLearningCandidateDomain } from './agent-harness-learning-curator-types.ts';
8
+ export type { LearningCandidate, LearningCandidateResolution, LearningConsolidationDiff, LearningConsolidationFields, LearningConsolidationPlan } from './agent-harness-learning-curator-types.ts';
9
+
10
+ function readString(value: unknown): string {
11
+ return typeof value === 'string' ? value.trim() : '';
12
+ }
13
+
14
+ function readLimit(value: unknown, fallback: number): number {
15
+ const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
16
+ if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
17
+ return Math.max(1, Math.min(200, Math.trunc(parsed)));
18
+ }
19
+
20
+ function describeCandidate(candidate: LearningCandidate, includeParameters: boolean, lookup?: Record<string, unknown>): Record<string, unknown> {
21
+ return {
22
+ candidateId: candidate.id,
23
+ label: candidate.label,
24
+ domain: candidate.domain,
25
+ recordId: candidate.recordId,
26
+ status: candidate.status,
27
+ priority: candidate.priority,
28
+ reason: previewHarnessText(candidate.reason, includeParameters ? 180 : 96),
29
+ next: previewHarnessText(candidate.next, includeParameters ? 180 : 96),
30
+ scores: candidate.scores,
31
+ ...(candidate.reviewState ? { reviewState: candidate.reviewState } : {}),
32
+ ...(candidate.enabled === undefined ? {} : { enabled: candidate.enabled }),
33
+ ...(candidate.active === undefined ? {} : { active: candidate.active }),
34
+ ...(candidate.confidence === undefined ? {} : { confidence: candidate.confidence }),
35
+ ...(candidate.proposalTarget === undefined ? {} : { proposalTarget: candidate.proposalTarget }),
36
+ ...(includeParameters && candidate.proposalFields ? { proposalFields: candidate.proposalFields } : {}),
37
+ ...(candidate.missingRequirements ? { missingRequirements: candidate.missingRequirements } : {}),
38
+ modelRoute: candidate.modelRoute,
39
+ inspectRoute: candidate.inspectRoute,
40
+ ...(candidate.reviewRoute ? { reviewRoute: candidate.reviewRoute } : {}),
41
+ ...(candidate.staleRoute ? { staleRoute: candidate.staleRoute } : {}),
42
+ ...(candidate.updateRoute ? { updateRoute: candidate.updateRoute } : {}),
43
+ ...(candidate.createRoute ? { createRoute: candidate.createRoute } : {}),
44
+ ...(candidate.deleteRoute ? { deleteRoute: candidate.deleteRoute } : {}),
45
+ ...(includeParameters && candidate.cleanupRoutes ? { cleanupRoutes: candidate.cleanupRoutes } : {}),
46
+ ...(includeParameters && candidate.rollbackRoutes ? { rollbackRoutes: candidate.rollbackRoutes } : {}),
47
+ ...(includeParameters && candidate.consolidation ? { consolidation: candidate.consolidation } : {}),
48
+ ...(lookup ? { lookup } : {}),
49
+ ...(includeParameters ? {
50
+ routes: {
51
+ inspect: candidate.inspectRoute,
52
+ model: candidate.modelRoute,
53
+ review: candidate.reviewRoute ?? null,
54
+ stale: candidate.staleRoute ?? null,
55
+ update: candidate.updateRoute ?? null,
56
+ apply: candidate.consolidation ? `agent_learning_consolidation mode=preview candidateId:"${routeValue(candidate.id)}"` : null,
57
+ merge: candidate.consolidation ? `agent_learning_consolidation mode=merge candidateId:"${routeValue(candidate.id)}" confirm:true explicitUserRequest:"..."` : null,
58
+ stalePhase: candidate.consolidation ? `agent_learning_consolidation mode=stale candidateId:"${routeValue(candidate.id)}" confirm:true explicitUserRequest:"..."` : null,
59
+ deletePhase: candidate.consolidation ? `agent_learning_consolidation mode=delete candidateId:"${routeValue(candidate.id)}" confirm:true explicitUserRequest:"..."` : null,
60
+ create: candidate.createRoute ?? null,
61
+ delete: candidate.deleteRoute ?? null,
62
+ },
63
+ policy: 'Learning curator rows are read-only. VIBE.md personality issues route to existing /vibe inspection/init/import commands. Duplicate consolidation phases use agent_learning_consolidation with confirmation, while create, review, promote, enable, schedule, and non-batch local effects stay on existing confirmed routes.',
64
+ } : {}),
65
+ };
66
+ }
67
+
68
+ function learningConsolidationBatchPlan(
69
+ candidates: readonly LearningCandidate[],
70
+ includeParameters: boolean,
71
+ ): LearningConsolidationBatchPlan | undefined {
72
+ const consolidationCandidates = candidates.filter((candidate) => candidate.consolidation !== undefined);
73
+ if (consolidationCandidates.length === 0) return undefined;
74
+ const domainCounts = new Map<string, { candidates: number; duplicateRecords: number }>();
75
+ for (const candidate of consolidationCandidates) {
76
+ const duplicateRecords = candidate.consolidation?.duplicateIds.length ?? 0;
77
+ const current = domainCounts.get(candidate.domain) ?? { candidates: 0, duplicateRecords: 0 };
78
+ current.candidates += 1;
79
+ current.duplicateRecords += duplicateRecords;
80
+ domainCounts.set(candidate.domain, current);
81
+ }
82
+ const domains = [...domainCounts.entries()]
83
+ .sort(([left], [right]) => left.localeCompare(right))
84
+ .map(([domain, counts]) => ({ domain, ...counts }));
85
+ const topCandidates = consolidationCandidates.slice(0, 8).map((candidate) => {
86
+ const consolidation = candidate.consolidation!;
87
+ return {
88
+ candidateId: candidate.id,
89
+ label: candidate.label,
90
+ domain: candidate.domain,
91
+ survivorId: consolidation.survivorId,
92
+ duplicateCount: consolidation.duplicateIds.length,
93
+ ...(includeParameters ? { duplicateIds: consolidation.duplicateIds } : {}),
94
+ diffFields: consolidation.diffs.map((diff) => diff.field),
95
+ detailRoute: `memory action:"candidate" candidateId:"${routeValue(candidate.id)}"`,
96
+ ...(candidate.updateRoute ? { updateRoute: candidate.updateRoute } : {}),
97
+ applyRoute: `agent_learning_consolidation mode=preview candidateId:"${routeValue(candidate.id)}"`,
98
+ mergeRoute: `agent_learning_consolidation mode=merge candidateId:"${routeValue(candidate.id)}" confirm:true explicitUserRequest:"..."`,
99
+ stalePhaseRoute: `agent_learning_consolidation mode=stale candidateId:"${routeValue(candidate.id)}" confirm:true explicitUserRequest:"..."`,
100
+ deletePhaseRoute: `agent_learning_consolidation mode=delete candidateId:"${routeValue(candidate.id)}" confirm:true explicitUserRequest:"..."`,
101
+ ...(candidate.staleRoute ? { staleRoute: candidate.staleRoute } : {}),
102
+ ...(candidate.deleteRoute ? { deleteRoute: candidate.deleteRoute } : {}),
103
+ ...(includeParameters ? {
104
+ staleRoutes: consolidation.staleRoutes,
105
+ deleteRoutes: consolidation.deleteRoutes,
106
+ rollbackRoutes: consolidation.rollbackRoutes,
107
+ } : {}),
108
+ };
109
+ });
110
+ return {
111
+ status: 'ready',
112
+ candidates: consolidationCandidates.length,
113
+ duplicateRecords: consolidationCandidates.reduce((total, candidate) => total + (candidate.consolidation?.duplicateIds.length ?? 0), 0),
114
+ domains,
115
+ routes: {
116
+ reviewQueue: 'memory action:"curator" query:"consolidation" includeParameters:true',
117
+ candidateDetail: 'memory action:"candidate" candidateId:"<candidateId>"',
118
+ survivorRecord: 'agent_local_registry domain:"<domain>" action:"get" id:"<survivorId>"',
119
+ },
120
+ phases: [
121
+ {
122
+ id: 'inspect',
123
+ label: 'Inspect every duplicate group',
124
+ goal: 'Open the candidate detail and survivor/duplicate records before changing durable context.',
125
+ route: 'memory action:"curator" query:"consolidation" includeParameters:true',
126
+ },
127
+ {
128
+ id: 'merge-survivor',
129
+ label: 'Merge visible survivor fields',
130
+ goal: 'Apply the survivor update route only when the visible diffs preserve useful names, descriptions, tags, and triggers.',
131
+ route: 'Use each topCandidates[].mergeRoute after explicit approval.',
132
+ },
133
+ {
134
+ id: 'stale-duplicates',
135
+ label: 'Stage duplicates as stale',
136
+ goal: 'Mark duplicates stale before deleting them so rollback remains one reviewed route away.',
137
+ route: 'Use each topCandidates[].stalePhaseRoute after explicit approval.',
138
+ },
139
+ {
140
+ id: 'verify',
141
+ label: 'Verify prompt impact and rollback',
142
+ goal: 'Re-run the curator, check the survivor, and keep rollback routes visible until the user accepts the result.',
143
+ route: 'memory action:"curator" query:"consolidation" includeParameters:true',
144
+ },
145
+ {
146
+ id: 'delete-after-approval',
147
+ label: 'Delete only after explicit approval',
148
+ goal: 'Use delete routes only after the user confirms the stale duplicates are no longer needed.',
149
+ route: 'Use each topCandidates[].deletePhaseRoute only after the stale phase has already run.',
150
+ },
151
+ ],
152
+ topCandidates,
153
+ policy: 'This is an ordered review plan. Use agent_learning_consolidation to preview or apply one confirmed candidate phase; low-level Agent-local routes remain visible for inspection and recovery.',
154
+ };
155
+ }
156
+
157
+ function nextActions(candidates: readonly LearningCandidate[]): readonly string[] {
158
+ return candidates
159
+ .filter((candidate) => candidate.status !== 'ready')
160
+ .slice(0, 5)
161
+ .map((candidate) => `${candidate.label}: ${candidate.next}`);
162
+ }
163
+
164
+ function promptPlanCandidate(candidate: LearningCandidate, includeParameters: boolean): Record<string, unknown> {
165
+ return {
166
+ candidateId: candidate.id,
167
+ label: candidate.label,
168
+ domain: candidate.domain,
169
+ status: candidate.status,
170
+ priority: candidate.priority,
171
+ scores: candidate.scores,
172
+ reason: previewHarnessText(candidate.reason, includeParameters ? 160 : 96),
173
+ next: previewHarnessText(candidate.next, includeParameters ? 160 : 96),
174
+ inspectRoute: candidate.inspectRoute,
175
+ ...(candidate.reviewRoute ? { reviewRoute: candidate.reviewRoute } : {}),
176
+ ...(candidate.createRoute ? { createRoute: candidate.createRoute } : {}),
177
+ ...(candidate.updateRoute ? { updateRoute: candidate.updateRoute } : {}),
178
+ };
179
+ }
180
+
181
+ function promptRecordPriority(item: AgentWorkspaceLocalLibraryItem): number {
182
+ const scores = scoresForItem(item);
183
+ return clampScore((scores.usefulness * 0.35) + (scores.freshness * 0.25) + (scores.sourceQuality * 0.30) - (scores.risk * 0.25));
184
+ }
185
+
186
+ function isPromptEligibleRecord(domain: LocalLearningCandidateDomain, item: AgentWorkspaceLocalLibraryItem): boolean {
187
+ if (!isReviewed(item)) return false;
188
+ if (domain === 'memory') return (item.confidence ?? 100) >= 70;
189
+ if (domain === 'persona') return item.active === true;
190
+ if (domain === 'skill' || domain === 'skill_bundle' || domain === 'routine') {
191
+ return item.enabled === true && missingRequirementCount(item) === 0;
192
+ }
193
+ return false;
194
+ }
195
+
196
+ function promptRecordEntry(domain: LocalLearningCandidateDomain, item: AgentWorkspaceLocalLibraryItem, includeParameters: boolean): Record<string, unknown> {
197
+ return {
198
+ id: `${domain}:${item.id}`,
199
+ label: item.name,
200
+ domain,
201
+ recordId: item.id,
202
+ priority: promptRecordPriority(item),
203
+ scores: scoresForItem(item),
204
+ reason: domain === 'memory'
205
+ ? 'Reviewed high-confidence memory is eligible for prompt recall.'
206
+ : domain === 'persona'
207
+ ? 'Reviewed active persona is eligible for prompt personality context.'
208
+ : 'Reviewed, enabled, setup-ready behavior is eligible for prompt context.',
209
+ inspectRoute: localRegistryRoute(domain, 'get', item.id),
210
+ ...(includeParameters ? {
211
+ reviewState: item.reviewState,
212
+ confidence: item.confidence ?? null,
213
+ enabled: item.enabled ?? null,
214
+ active: item.active ?? null,
215
+ missingRequirementCount: missingRequirementCount(item),
216
+ tags: item.tags,
217
+ } : {}),
218
+ };
219
+ }
220
+
221
+ function promptEligibleRecords(context: CommandContext, includeParameters: boolean): readonly Record<string, unknown>[] {
222
+ const snapshot = buildAgentWorkspaceRuntimeSnapshot(context);
223
+ const records = [
224
+ ...snapshot.localMemories
225
+ .filter((item) => isPromptEligibleRecord('memory', item))
226
+ .map((item) => promptRecordEntry('memory', item, includeParameters)),
227
+ ...snapshot.localPersonas
228
+ .filter((item) => isPromptEligibleRecord('persona', item))
229
+ .map((item) => promptRecordEntry('persona', item, includeParameters)),
230
+ ...snapshot.localSkills
231
+ .filter((item) => isPromptEligibleRecord('skill', item))
232
+ .map((item) => promptRecordEntry('skill', item, includeParameters)),
233
+ ...snapshot.localSkillBundles
234
+ .filter((item) => isPromptEligibleRecord('skill_bundle', item))
235
+ .map((item) => promptRecordEntry('skill_bundle', item, includeParameters)),
236
+ ...snapshot.localRoutines
237
+ .filter((item) => isPromptEligibleRecord('routine', item))
238
+ .map((item) => promptRecordEntry('routine', item, includeParameters)),
239
+ ];
240
+ return records.sort((left, right) => {
241
+ const leftPriority = typeof left.priority === 'number' ? left.priority : 0;
242
+ const rightPriority = typeof right.priority === 'number' ? right.priority : 0;
243
+ if (rightPriority !== leftPriority) return rightPriority - leftPriority;
244
+ return String(left.label ?? '').localeCompare(String(right.label ?? ''));
245
+ });
246
+ }
247
+
248
+ function learningPromptPlan(context: CommandContext, candidates: readonly LearningCandidate[], includeParameters: boolean): LearningPromptPlan {
249
+ const promptActiveRecords = promptEligibleRecords(context, includeParameters);
250
+ const reviewFirstCandidates = candidates.filter((candidate) => (
251
+ candidate.status === 'needs-review'
252
+ || candidate.status === 'low-confidence'
253
+ || candidate.status === 'needs-setup'
254
+ || candidate.status === 'needs-consolidation'
255
+ ));
256
+ const proposalCandidates = candidates.filter((candidate) => candidate.status === 'proposal-ready' || candidate.status === 'ready-to-promote');
257
+ const consolidationCandidates = candidates.filter((candidate) => candidate.status === 'needs-consolidation');
258
+ const suppressed = {
259
+ needsReview: candidates.filter((candidate) => candidate.status === 'needs-review').length,
260
+ needsSetup: candidates.filter((candidate) => candidate.status === 'needs-setup').length,
261
+ lowConfidence: candidates.filter((candidate) => candidate.status === 'low-confidence').length,
262
+ personalityIssues: candidates.filter((candidate) => candidate.domain === 'vibe').length,
263
+ needsConsolidation: consolidationCandidates.length,
264
+ };
265
+ const suppressedCount = Object.values(suppressed).reduce((total, count) => total + count, 0);
266
+ const status = promptActiveRecords.length === 0 && candidates.length === 0
267
+ ? 'empty'
268
+ : suppressedCount > 0 || proposalCandidates.length > 0
269
+ ? 'attention'
270
+ : 'ready';
271
+ return {
272
+ status,
273
+ promptActiveCount: promptActiveRecords.length,
274
+ suppressedCount,
275
+ proposalCount: proposalCandidates.length,
276
+ consolidationCount: consolidationCandidates.length,
277
+ promptActiveRecords: promptActiveRecords.slice(0, includeParameters ? 12 : 5),
278
+ reviewFirst: reviewFirstCandidates.slice(0, includeParameters ? 10 : 5).map((candidate) => promptPlanCandidate(candidate, includeParameters)),
279
+ proposalQueue: proposalCandidates.slice(0, includeParameters ? 10 : 5).map((candidate) => promptPlanCandidate(candidate, includeParameters)),
280
+ consolidationQueue: consolidationCandidates.slice(0, includeParameters ? 8 : 3).map((candidate) => promptPlanCandidate(candidate, includeParameters)),
281
+ suppressed,
282
+ orderingRules: [
283
+ 'Prompt context stays limited to reviewed high-confidence memory and reviewed setup-ready enabled behavior.',
284
+ 'Usefulness, freshness, source-quality, and risk scores drive review priority before durable context expands.',
285
+ 'Low-confidence, stale, setup-blocked, unreviewed, blocked VIBE.md, and duplicate records stay suppressed until reviewed or repaired.',
286
+ 'Proposals from notes, completed work, completed research, and saved sessions require explicit create or promotion routes before they can guide the assistant.',
287
+ ],
288
+ routes: {
289
+ memoryPosture: 'memory action:"status"',
290
+ curator: 'memory action:"curator" includeParameters:true',
291
+ candidate: 'memory action:"candidate" candidateId:"<candidateId>"',
292
+ consolidation: 'agent_learning_consolidation mode=preview candidateId:"<candidateId>"',
293
+ },
294
+ policy: 'This prompt plan is read-only. It explains what can guide the assistant now, what remains suppressed, and which reviewed route should run before any durable memory, skill, routine, persona, or consolidation change.',
295
+ };
296
+ }
297
+
298
+ export function learningCuratorCatalogStatus(context: CommandContext): Record<string, unknown> {
299
+ const candidates = buildLearningCandidates(context);
300
+ return {
301
+ modes: ['learning_curator', 'learning_candidate'],
302
+ candidates: candidates.length,
303
+ needsReview: candidates.filter((candidate) => candidate.status === 'needs-review').length,
304
+ needsSetup: candidates.filter((candidate) => candidate.status === 'needs-setup').length,
305
+ needsConsolidation: candidates.filter((candidate) => candidate.status === 'needs-consolidation').length,
306
+ lowConfidence: candidates.filter((candidate) => candidate.status === 'low-confidence').length,
307
+ proposedBehavior: candidates.filter((candidate) => candidate.status === 'proposal-ready').length,
308
+ personalityIssues: candidates.filter((candidate) => candidate.domain === 'vibe').length,
309
+ readyToPromote: candidates.filter((candidate) => candidate.status === 'ready-to-promote').length,
310
+ readOnly: true,
311
+ };
312
+ }
313
+
314
+ export function learningCuratorSummary(context: CommandContext, args: AgentHarnessLearningCuratorArgs): Record<string, unknown> {
315
+ const includeParameters = args.includeParameters === true;
316
+ const query = readString(args.query).toLowerCase();
317
+ const limit = readLimit(args.limit, 100);
318
+ const all = buildLearningCandidates(context);
319
+ const filtered = all.filter((candidate) => !query || candidateSearchText(candidate).includes(query));
320
+ const consolidationBatch = learningConsolidationBatchPlan(all, includeParameters);
321
+ return {
322
+ summary: {
323
+ candidates: all.length,
324
+ needsReview: all.filter((candidate) => candidate.status === 'needs-review').length,
325
+ needsSetup: all.filter((candidate) => candidate.status === 'needs-setup').length,
326
+ needsConsolidation: all.filter((candidate) => candidate.status === 'needs-consolidation').length,
327
+ lowConfidence: all.filter((candidate) => candidate.status === 'low-confidence').length,
328
+ proposedBehavior: all.filter((candidate) => candidate.status === 'proposal-ready').length,
329
+ personalityIssues: all.filter((candidate) => candidate.domain === 'vibe').length,
330
+ readyToPromote: all.filter((candidate) => candidate.status === 'ready-to-promote').length,
331
+ ready: all.filter((candidate) => candidate.status === 'ready').length,
332
+ },
333
+ promptPlan: learningPromptPlan(context, all, includeParameters),
334
+ candidates: filtered.slice(0, limit).map((candidate) => describeCandidate(candidate, includeParameters)),
335
+ ...(consolidationBatch ? { consolidationBatch } : {}),
336
+ returned: Math.min(filtered.length, limit),
337
+ total: all.length,
338
+ nextActions: nextActions(all),
339
+ policy: 'Learning curator is read-only. Proposed memory and behavior changes use reviewed notes, completed work-plan items, completed research runs, saved sessions, VIBE.md personality health cards, duplicate consolidation, and existing confirmed capture routes; durable context still requires provenance, review, rollback via stale/delete routes, and explicit user intent for writes or promotion.',
340
+ };
341
+ }
342
+
343
+ export type LearningConsolidationCandidateResolution =
344
+ | { readonly status: 'found'; readonly candidate: LearningCandidate }
345
+ | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Pick<LearningCandidate, 'id' | 'label' | 'status' | 'priority'>[] }
346
+ | { readonly status: 'missing_lookup'; readonly usage: string };
347
+
348
+ export function resolveLearningConsolidationCandidate(context: CommandContext, input: string): LearningConsolidationCandidateResolution {
349
+ const lookup = input.trim();
350
+ if (!lookup) {
351
+ return {
352
+ status: 'missing_lookup',
353
+ usage: 'candidateId or query is required. Use memory action:"curator" query:"consolidation" to inspect candidate ids.',
354
+ };
355
+ }
356
+ const candidates = buildLearningCandidates(context).filter((candidate) => candidate.consolidation !== undefined);
357
+ const normalized = lookup.toLowerCase();
358
+ const exact = candidates.find((candidate) => candidate.id === lookup)
359
+ ?? candidates.find((candidate) => candidate.id.toLowerCase() === normalized);
360
+ if (exact) return { status: 'found', candidate: exact };
361
+ const matches = candidates.filter((candidate) => candidateSearchText(candidate).includes(normalized));
362
+ if (matches.length === 1) return { status: 'found', candidate: matches[0]! };
363
+ if (matches.length > 1) {
364
+ return {
365
+ status: 'ambiguous',
366
+ input: lookup,
367
+ candidates: matches.slice(0, 8).map((candidate) => ({
368
+ id: candidate.id,
369
+ label: candidate.label,
370
+ status: candidate.status,
371
+ priority: candidate.priority,
372
+ })),
373
+ };
374
+ }
375
+ return {
376
+ status: 'missing_lookup',
377
+ usage: `Unknown duplicate-consolidation candidate ${lookup}. Use memory action:"curator" query:"consolidation" to inspect candidate ids.`,
378
+ };
379
+ }
380
+
381
+ export function describeLearningCandidate(context: CommandContext, args: AgentHarnessLearningCuratorArgs): LearningCandidateResolution {
382
+ const candidateId = readString(args.candidateId);
383
+ const target = readString(args.target);
384
+ const query = readString(args.query);
385
+ const input = candidateId || target || query;
386
+ if (!input) {
387
+ return {
388
+ status: 'missing_lookup',
389
+ usage: 'memory action:"candidate" requires candidateId, target, or query. Use memory action:"curator" to inspect candidate ids.',
390
+ };
391
+ }
392
+ const normalized = input.toLowerCase();
393
+ const candidates = buildLearningCandidates(context);
394
+ const exact = candidates.find((candidate) => candidate.id === input);
395
+ if (exact) return { status: 'found', candidate: describeCandidate(exact, true, { source: candidateId ? 'candidateId' : target ? 'target' : 'query', input, resolvedBy: 'id' }) };
396
+ const insensitive = candidates.find((candidate) => candidate.id.toLowerCase() === normalized);
397
+ if (insensitive) return { status: 'found', candidate: describeCandidate(insensitive, true, { source: candidateId ? 'candidateId' : target ? 'target' : 'query', input, resolvedBy: 'case-insensitive-id' }) };
398
+ const matches = candidates.filter((candidate) => candidateSearchText(candidate).includes(normalized));
399
+ if (matches.length === 1) return { status: 'found', candidate: describeCandidate(matches[0]!, true, { source: candidateId ? 'candidateId' : target ? 'target' : 'query', input, resolvedBy: 'search' }) };
400
+ if (matches.length > 1) {
401
+ return {
402
+ status: 'ambiguous',
403
+ input,
404
+ candidates: matches.slice(0, 8).map((candidate) => ({
405
+ candidateId: candidate.id,
406
+ label: candidate.label,
407
+ status: candidate.status,
408
+ priority: candidate.priority,
409
+ modelRoute: candidate.modelRoute,
410
+ })),
411
+ };
412
+ }
413
+ return {
414
+ status: 'missing_lookup',
415
+ usage: `Unknown learning candidate ${input}. Use memory action:"curator" to inspect candidate ids.`,
416
+ };
417
+ }