@pellux/goodvibes-agent 1.1.7 → 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 (255) hide show
  1. package/CHANGELOG.md +170 -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 +132577 -91483
  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 +39 -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 +199 -5
  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 +137 -7
  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 +106 -85
  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/version.ts +1 -1
@@ -0,0 +1,102 @@
1
+ import type { AgentWorkspaceLocalLibraryItem } from '../input/agent-workspace-types.ts';
2
+ import type { LearningCandidate, LearningCandidateStatus, LearningScores, LocalLearningCandidateDomain } from './agent-harness-learning-curator-types.ts';
3
+ export function clampScore(value: number): number {
4
+ return Math.max(0, Math.min(100, Math.round(value)));
5
+ }
6
+
7
+ export function isReviewed(item: AgentWorkspaceLocalLibraryItem): boolean {
8
+ return item.reviewState === 'reviewed';
9
+ }
10
+
11
+ export function missingRequirementCount(item: AgentWorkspaceLocalLibraryItem): number {
12
+ return item.missingRequirementCount ?? 0;
13
+ }
14
+
15
+ export function itemUsefulness(item: AgentWorkspaceLocalLibraryItem): number {
16
+ const triggerSignal = Math.min(20, item.triggers.length * 5);
17
+ const tagSignal = Math.min(10, item.tags.length * 2);
18
+ const usageSignal = (item.enabled ? 15 : 0) + (item.active ? 15 : 0) + Math.min(20, (item.startCount ?? 0) * 4);
19
+ const confidenceSignal = item.confidence === undefined ? 10 : Math.max(0, Math.min(20, item.confidence / 5));
20
+ return clampScore(35 + triggerSignal + tagSignal + usageSignal + confidenceSignal);
21
+ }
22
+
23
+ export function itemFreshness(item: AgentWorkspaceLocalLibraryItem): number {
24
+ if (item.reviewState === 'stale') return 15;
25
+ if (item.reviewState === 'fresh') return 55;
26
+ return missingRequirementCount(item) > 0 ? 70 : 92;
27
+ }
28
+
29
+ export function itemSourceQuality(item: AgentWorkspaceLocalLibraryItem): number {
30
+ const source = item.source.toLowerCase();
31
+ const base = source.includes('workspace') || source.includes('import') ? 72 : source.includes('agent') ? 68 : 60;
32
+ return clampScore(base + (isReviewed(item) ? 12 : 0) + Math.min(12, item.tags.length * 2));
33
+ }
34
+
35
+ export function itemRisk(item: AgentWorkspaceLocalLibraryItem): number {
36
+ const reviewRisk = item.reviewState === 'stale' ? 60 : item.reviewState === 'fresh' ? 35 : 10;
37
+ const injectionRisk = item.enabled || item.active ? 20 : 0;
38
+ const setupRisk = Math.min(30, missingRequirementCount(item) * 10);
39
+ const confidenceRisk = item.confidence === undefined ? 0 : Math.max(0, 70 - item.confidence);
40
+ return clampScore(reviewRisk + injectionRisk + setupRisk + confidenceRisk);
41
+ }
42
+
43
+ export function scoresForItem(item: AgentWorkspaceLocalLibraryItem): LearningScores {
44
+ return {
45
+ usefulness: itemUsefulness(item),
46
+ freshness: itemFreshness(item),
47
+ sourceQuality: itemSourceQuality(item),
48
+ risk: itemRisk(item),
49
+ };
50
+ }
51
+
52
+ export function routeDomain(domain: LocalLearningCandidateDomain): string {
53
+ return domain;
54
+ }
55
+
56
+ export function localRegistryRoute(domain: LocalLearningCandidateDomain, action: string, id: string): string {
57
+ return `agent_local_registry domain:"${routeDomain(domain)}" action:"${action}" id:"${id}"`;
58
+ }
59
+
60
+ export function localRegistryModelRoute(domain: LocalLearningCandidateDomain): string {
61
+ return `agent_local_registry domain:"${routeDomain(domain)}" action:"get"`;
62
+ }
63
+
64
+ export function routeValue(value: string): string {
65
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
66
+ }
67
+
68
+ export function routeList(values: readonly string[]): string {
69
+ return `[${values.map((value) => `"${routeValue(value)}"`).join(',')}]`;
70
+ }
71
+
72
+ export function candidateBase(
73
+ domain: LocalLearningCandidateDomain,
74
+ item: AgentWorkspaceLocalLibraryItem,
75
+ status: LearningCandidateStatus,
76
+ priority: number,
77
+ reason: string,
78
+ next: string,
79
+ ): LearningCandidate {
80
+ const missingRequirements = item.missingRequirements ?? [];
81
+ return {
82
+ id: `${domain}:${item.id}:${status}`,
83
+ label: item.name,
84
+ domain,
85
+ recordId: item.id,
86
+ status,
87
+ priority: clampScore(priority),
88
+ reason,
89
+ next,
90
+ scores: scoresForItem(item),
91
+ reviewState: item.reviewState,
92
+ ...(item.enabled === undefined ? {} : { enabled: item.enabled }),
93
+ ...(item.active === undefined ? {} : { active: item.active }),
94
+ ...(item.confidence === undefined ? {} : { confidence: item.confidence }),
95
+ ...(missingRequirements.length === 0 ? {} : { missingRequirements }),
96
+ inspectRoute: localRegistryRoute(domain, 'get', item.id),
97
+ modelRoute: localRegistryModelRoute(domain),
98
+ reviewRoute: localRegistryRoute(domain, 'review', item.id),
99
+ staleRoute: `${localRegistryRoute(domain, 'stale', item.id)} reason:"..."`,
100
+ deleteRoute: `${localRegistryRoute(domain, 'delete', item.id)} confirm:true explicitUserRequest:"..."`,
101
+ };
102
+ }
@@ -0,0 +1,295 @@
1
+ import type { AgentWorkspaceLocalLibraryItem } from '../input/agent-workspace-types.ts';
2
+ import { previewHarnessText } from './agent-harness-text.ts';
3
+ import type { LearningCandidate, LearningConsolidationDiff, LearningConsolidationFields, LearningConsolidationPlan, LocalLearningCandidateDomain } from './agent-harness-learning-curator-types.ts';
4
+ import { clampScore, isReviewed, itemFreshness, itemSourceQuality, localRegistryModelRoute, localRegistryRoute, routeList, routeValue, scoresForItem } from './agent-harness-learning-curator-common.ts';
5
+ const CONSOLIDATION_DOMAINS: readonly LocalLearningCandidateDomain[] = ['memory', 'persona', 'skill', 'routine'];
6
+
7
+ function normalizeDuplicateKey(value: string): string {
8
+ return value
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9]+/g, ' ')
11
+ .trim()
12
+ .replace(/\s+/g, ' ');
13
+ }
14
+
15
+ function duplicateKeySlug(value: string): string {
16
+ return normalizeDuplicateKey(value).replace(/\s+/g, '-').slice(0, 52) || 'duplicate';
17
+ }
18
+
19
+ function uniqueText(values: readonly string[]): readonly string[] {
20
+ const seen = new Set<string>();
21
+ const result: string[] = [];
22
+ for (const value of values) {
23
+ const trimmed = value.trim();
24
+ if (!trimmed) continue;
25
+ const key = trimmed.toLowerCase();
26
+ if (seen.has(key)) continue;
27
+ seen.add(key);
28
+ result.push(trimmed);
29
+ }
30
+ return result;
31
+ }
32
+
33
+ function uniqueSorted(values: readonly string[]): readonly string[] {
34
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))]
35
+ .sort((left, right) => left.localeCompare(right));
36
+ }
37
+
38
+ function itemConsolidationScore(item: AgentWorkspaceLocalLibraryItem): number {
39
+ const scores = scoresForItem(item);
40
+ const reviewSignal = isReviewed(item) ? 26 : item.reviewState === 'fresh' ? 10 : 0;
41
+ const usageSignal = (item.enabled ? 12 : 0) + (item.active ? 16 : 0) + Math.min(12, (item.startCount ?? 0) * 3);
42
+ const confidenceSignal = item.confidence === undefined ? 0 : item.confidence / 4;
43
+ return scores.usefulness + scores.freshness + scores.sourceQuality - scores.risk + reviewSignal + usageSignal + confidenceSignal;
44
+ }
45
+
46
+ function chooseConsolidationSurvivor(items: readonly AgentWorkspaceLocalLibraryItem[]): AgentWorkspaceLocalLibraryItem {
47
+ return [...items].sort((left, right) => {
48
+ const byScore = itemConsolidationScore(right) - itemConsolidationScore(left);
49
+ if (byScore !== 0) return byScore;
50
+ const byReview = (isReviewed(right) ? 1 : 0) - (isReviewed(left) ? 1 : 0);
51
+ if (byReview !== 0) return byReview;
52
+ return left.id.localeCompare(right.id);
53
+ })[0]!;
54
+ }
55
+
56
+ function mergeableDescription(domain: LocalLearningCandidateDomain, item: AgentWorkspaceLocalLibraryItem): string {
57
+ if (domain === 'memory') {
58
+ const fallback = item.scope && item.cls ? `${item.scope}/${item.cls}` : '';
59
+ return item.description === fallback ? '' : item.description;
60
+ }
61
+ return item.description;
62
+ }
63
+
64
+ function mergedDescription(
65
+ domain: LocalLearningCandidateDomain,
66
+ survivor: AgentWorkspaceLocalLibraryItem,
67
+ duplicates: readonly AgentWorkspaceLocalLibraryItem[],
68
+ ): string {
69
+ const descriptions = uniqueText([survivor, ...duplicates].map((item) => mergeableDescription(domain, item)));
70
+ if (descriptions.length === 0) return '';
71
+ return previewHarnessText(descriptions.join('\n\n'), 900);
72
+ }
73
+
74
+ function mergedTags(items: readonly AgentWorkspaceLocalLibraryItem[]): readonly string[] {
75
+ return uniqueSorted(items.flatMap((item) => item.tags));
76
+ }
77
+
78
+ function mergedTriggers(items: readonly AgentWorkspaceLocalLibraryItem[]): readonly string[] {
79
+ return uniqueSorted(items.flatMap((item) => item.triggers));
80
+ }
81
+
82
+ function updateFieldsForConsolidation(
83
+ domain: LocalLearningCandidateDomain,
84
+ survivor: AgentWorkspaceLocalLibraryItem,
85
+ duplicates: readonly AgentWorkspaceLocalLibraryItem[],
86
+ ): LearningConsolidationFields | undefined {
87
+ const all = [survivor, ...duplicates];
88
+ const tags = mergedTags(all);
89
+ const triggers = mergedTriggers(all);
90
+ const description = mergedDescription(domain, survivor, duplicates);
91
+ if (domain === 'memory') {
92
+ const fields: LearningConsolidationFields = {
93
+ ...(description ? { detail: description } : {}),
94
+ ...(tags.length > 0 ? { tags } : {}),
95
+ };
96
+ return Object.keys(fields).length > 0 ? fields : undefined;
97
+ }
98
+ const fields: LearningConsolidationFields = {
99
+ ...(description ? { description } : {}),
100
+ ...(tags.length > 0 ? { tags } : {}),
101
+ ...(triggers.length > 0 ? { triggers } : {}),
102
+ };
103
+ return Object.keys(fields).length > 0 ? fields : undefined;
104
+ }
105
+
106
+ function rollbackFieldsForConsolidation(
107
+ domain: LocalLearningCandidateDomain,
108
+ survivor: AgentWorkspaceLocalLibraryItem,
109
+ ): LearningConsolidationFields {
110
+ const description = mergeableDescription(domain, survivor);
111
+ if (domain === 'memory') {
112
+ return {
113
+ ...(description ? { detail: description } : {}),
114
+ tags: survivor.tags,
115
+ };
116
+ }
117
+ return {
118
+ ...(description ? { description } : {}),
119
+ tags: survivor.tags,
120
+ triggers: survivor.triggers,
121
+ };
122
+ }
123
+
124
+ function consolidationDiff(
125
+ field: string,
126
+ survivor: string,
127
+ duplicateValues: readonly string[],
128
+ merged: string,
129
+ ): LearningConsolidationDiff | null {
130
+ const duplicates = uniqueText(duplicateValues);
131
+ if (duplicates.length === 0 && !merged) return null;
132
+ const normalizedSurvivor = survivor.trim().toLowerCase();
133
+ const differs = duplicates.some((value) => value.trim().toLowerCase() !== normalizedSurvivor)
134
+ || merged.trim().toLowerCase() !== normalizedSurvivor;
135
+ if (!differs) return null;
136
+ return {
137
+ field,
138
+ survivor: survivor || '(empty)',
139
+ duplicates,
140
+ merged: merged || '(empty)',
141
+ };
142
+ }
143
+
144
+ function consolidationDiffs(
145
+ domain: LocalLearningCandidateDomain,
146
+ survivor: AgentWorkspaceLocalLibraryItem,
147
+ duplicates: readonly AgentWorkspaceLocalLibraryItem[],
148
+ ): readonly LearningConsolidationDiff[] {
149
+ const all = [survivor, ...duplicates];
150
+ const tags = mergedTags(all).join(', ');
151
+ const triggers = mergedTriggers(all).join(', ');
152
+ return [
153
+ consolidationDiff('name', survivor.name, duplicates.map((item) => item.name), survivor.name),
154
+ consolidationDiff('description', mergeableDescription(domain, survivor), duplicates.map((item) => mergeableDescription(domain, item)), mergedDescription(domain, survivor, duplicates)),
155
+ consolidationDiff('tags', survivor.tags.join(', '), duplicates.map((item) => item.tags.join(', ')), tags),
156
+ domain === 'memory' ? null : consolidationDiff('triggers', survivor.triggers.join(', '), duplicates.map((item) => item.triggers.join(', ')), triggers),
157
+ ].filter((diff): diff is LearningConsolidationDiff => diff !== null);
158
+ }
159
+
160
+ function updateRouteForConsolidation(
161
+ domain: LocalLearningCandidateDomain,
162
+ survivor: AgentWorkspaceLocalLibraryItem,
163
+ duplicates: readonly AgentWorkspaceLocalLibraryItem[],
164
+ ): string | undefined {
165
+ const updateFields = updateFieldsForConsolidation(domain, survivor, duplicates);
166
+ if (!updateFields) return undefined;
167
+ const fields: string[] = [];
168
+ if (domain === 'memory') {
169
+ if (updateFields.detail) fields.push(`detail:"${routeValue(updateFields.detail)}"`);
170
+ if (updateFields.tags && updateFields.tags.length > 0) fields.push(`tags:${routeList(updateFields.tags)}`);
171
+ } else {
172
+ if (updateFields.description) fields.push(`description:"${routeValue(updateFields.description)}"`);
173
+ if (updateFields.tags && updateFields.tags.length > 0) fields.push(`tags:${routeList(updateFields.tags)}`);
174
+ if (updateFields.triggers && updateFields.triggers.length > 0) fields.push(`triggers:${routeList(updateFields.triggers)}`);
175
+ }
176
+ if (fields.length === 0) return undefined;
177
+ return [
178
+ localRegistryRoute(domain, 'update', survivor.id),
179
+ ...fields,
180
+ 'provenance:"learning-curator-consolidation"',
181
+ ].join(' ');
182
+ }
183
+
184
+ function rollbackUpdateRouteForConsolidation(domain: LocalLearningCandidateDomain, survivor: AgentWorkspaceLocalLibraryItem): string {
185
+ const rollbackFields = rollbackFieldsForConsolidation(domain, survivor);
186
+ const fields: string[] = [];
187
+ if (domain === 'memory') {
188
+ if (rollbackFields.detail) fields.push(`detail:"${routeValue(rollbackFields.detail)}"`);
189
+ fields.push(`tags:${routeList(rollbackFields.tags ?? [])}`);
190
+ } else {
191
+ if (rollbackFields.description) fields.push(`description:"${routeValue(rollbackFields.description)}"`);
192
+ fields.push(`tags:${routeList(rollbackFields.tags ?? [])}`);
193
+ fields.push(`triggers:${routeList(rollbackFields.triggers ?? [])}`);
194
+ }
195
+ return [
196
+ localRegistryRoute(domain, 'update', survivor.id),
197
+ ...fields,
198
+ 'provenance:"rollback-learning-curator-consolidation"',
199
+ ].join(' ');
200
+ }
201
+
202
+ function consolidationProposalFields(plan: LearningConsolidationPlan): Readonly<Record<string, string>> {
203
+ return {
204
+ survivorId: plan.survivorId,
205
+ duplicateIds: plan.duplicateIds.join(','),
206
+ visibleDiff: plan.diffs.length > 0
207
+ ? plan.diffs.map((diff) => `${diff.field}: keep [${diff.survivor}], merge [${diff.merged}]`).join('\n')
208
+ : 'Visible metadata matches; inspect record bodies before marking duplicates stale.',
209
+ staleFirst: plan.staleRoutes.join('\n'),
210
+ rollback: plan.rollbackRoutes.join('\n'),
211
+ };
212
+ }
213
+
214
+ function consolidationCandidate(
215
+ domain: LocalLearningCandidateDomain,
216
+ key: string,
217
+ items: readonly AgentWorkspaceLocalLibraryItem[],
218
+ ): LearningCandidate | null {
219
+ if (items.length < 2) return null;
220
+ const survivor = chooseConsolidationSurvivor(items);
221
+ const duplicates = items.filter((item) => item.id !== survivor.id);
222
+ if (duplicates.length === 0) return null;
223
+ const updateRoute = updateRouteForConsolidation(domain, survivor, duplicates);
224
+ const updateFields = updateFieldsForConsolidation(domain, survivor, duplicates);
225
+ const rollbackFields = rollbackFieldsForConsolidation(domain, survivor);
226
+ const staleRoutes = duplicates.map((item) => `${localRegistryRoute(domain, 'stale', item.id)} reason:"Duplicate of ${routeValue(survivor.id)}; staged by learning curator consolidation."`);
227
+ const deleteRoutes = duplicates.map((item) => `${localRegistryRoute(domain, 'delete', item.id)} confirm:true explicitUserRequest:"Delete duplicate ${routeValue(domain)} ${routeValue(item.id)} after reviewed consolidation."`);
228
+ const rollbackRoutes = [
229
+ ...(updateRoute ? [rollbackUpdateRouteForConsolidation(domain, survivor)] : []),
230
+ ...duplicates.map((item) => localRegistryRoute(domain, 'review', item.id)),
231
+ ];
232
+ const diffs = consolidationDiffs(domain, survivor, duplicates);
233
+ const plan: LearningConsolidationPlan = {
234
+ survivorId: survivor.id,
235
+ duplicateIds: duplicates.map((item) => item.id),
236
+ sharedKey: key,
237
+ diffs,
238
+ ...(updateFields ? { updateFields } : {}),
239
+ rollbackFields,
240
+ ...(updateRoute ? { updateRoute } : {}),
241
+ staleRoutes,
242
+ deleteRoutes,
243
+ rollbackRoutes,
244
+ };
245
+ const enabledDuplicateCount = duplicates.filter((item) => item.enabled || item.active).length;
246
+ return {
247
+ id: `consolidation:${domain}:${duplicateKeySlug(key)}`,
248
+ label: `Consolidate duplicate ${domain}: ${survivor.name}`,
249
+ domain,
250
+ recordId: survivor.id,
251
+ status: 'needs-consolidation',
252
+ priority: clampScore(72 + Math.min(16, duplicates.length * 4) + enabledDuplicateCount * 5),
253
+ reason: `${items.length} Agent-local ${domain} records share the same normalized name.`,
254
+ next: 'Inspect every record, update the survivor with merged visible fields, mark duplicates stale first, then delete only after explicit review.',
255
+ scores: {
256
+ usefulness: clampScore(64 + Math.min(18, items.length * 4)),
257
+ freshness: Math.max(...items.map(itemFreshness)),
258
+ sourceQuality: Math.max(...items.map(itemSourceQuality)),
259
+ risk: clampScore(34 + enabledDuplicateCount * 12 + Math.min(18, duplicates.length * 4)),
260
+ },
261
+ reviewState: survivor.reviewState,
262
+ ...(survivor.enabled === undefined ? {} : { enabled: survivor.enabled }),
263
+ ...(survivor.active === undefined ? {} : { active: survivor.active }),
264
+ ...(survivor.confidence === undefined ? {} : { confidence: survivor.confidence }),
265
+ proposalFields: consolidationProposalFields(plan),
266
+ consolidation: plan,
267
+ inspectRoute: localRegistryRoute(domain, 'get', survivor.id),
268
+ modelRoute: 'memory action:"curator" query:"consolidation"',
269
+ reviewRoute: localRegistryRoute(domain, 'review', survivor.id),
270
+ ...(staleRoutes[0] ? { staleRoute: staleRoutes[0] } : {}),
271
+ ...(updateRoute ? { updateRoute } : {}),
272
+ ...(deleteRoutes[0] ? { deleteRoute: deleteRoutes[0] } : {}),
273
+ cleanupRoutes: staleRoutes,
274
+ rollbackRoutes,
275
+ };
276
+ }
277
+
278
+ export function consolidationCandidatesForDomain(
279
+ domain: LocalLearningCandidateDomain,
280
+ items: readonly AgentWorkspaceLocalLibraryItem[],
281
+ ): readonly LearningCandidate[] {
282
+ if (!CONSOLIDATION_DOMAINS.includes(domain)) return [];
283
+ const groups = new Map<string, AgentWorkspaceLocalLibraryItem[]>();
284
+ for (const item of items) {
285
+ const key = normalizeDuplicateKey(item.name);
286
+ if (key.length < 4) continue;
287
+ const group = groups.get(key) ?? [];
288
+ group.push(item);
289
+ groups.set(key, group);
290
+ }
291
+ return [...groups.entries()].flatMap(([key, group]) => {
292
+ const candidate = consolidationCandidate(domain, key, group);
293
+ return candidate ? [candidate] : [];
294
+ });
295
+ }