@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.
- package/CHANGELOG.md +176 -0
- package/README.md +46 -30
- package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
- package/dist/package/main.js +132662 -91399
- package/docs/README.md +14 -7
- package/docs/channels-remote-and-api.md +8 -5
- package/docs/connected-host.md +14 -12
- package/docs/getting-started.md +56 -30
- package/docs/knowledge-artifacts-and-multimodal.md +7 -5
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +11 -3
- package/docs/tools-and-commands.md +114 -45
- package/docs/voice-and-live-tts.md +3 -1
- package/package.json +3 -2
- package/release/release-notes.md +130 -5
- package/release/release-readiness.json +120 -90
- package/src/agent/assistant-cockpit.ts +247 -0
- package/src/agent/autonomy-schedule-format.ts +96 -0
- package/src/agent/autonomy-schedule.ts +514 -0
- package/src/agent/channel-delivery-receipts.ts +292 -0
- package/src/agent/competitive-feature-inventory.ts +296 -0
- package/src/agent/document-registry-types.ts +106 -0
- package/src/agent/document-registry.ts +734 -0
- package/src/agent/harness-control.ts +2 -2
- package/src/agent/memory-prompt.ts +2 -2
- package/src/agent/operator-actions.ts +50 -6
- package/src/agent/persona-registry.ts +8 -0
- package/src/agent/project-context-files.ts +273 -0
- package/src/agent/prompt-context-receipts.ts +502 -0
- package/src/agent/reminder-schedule-format.ts +16 -2
- package/src/agent/research-run-registry.ts +582 -0
- package/src/agent/research-source-registry.ts +441 -0
- package/src/agent/routine-registry.ts +19 -3
- package/src/agent/routine-schedule-format.ts +27 -2
- package/src/agent/runtime-profile-starters.ts +7 -0
- package/src/agent/runtime-profile.ts +106 -4
- package/src/agent/schedule-edit-format.ts +129 -0
- package/src/agent/schedule-edit.ts +496 -0
- package/src/agent/schedule-next-routes.ts +42 -0
- package/src/agent/setup-wizard-checkpoint.ts +140 -0
- package/src/agent/setup-wizard.ts +447 -0
- package/src/agent/skill-registry-types.ts +102 -0
- package/src/agent/skill-registry.ts +81 -107
- package/src/agent/vibe-confirmation-routes.ts +62 -0
- package/src/agent/vibe-file.ts +285 -0
- package/src/cli/agent-knowledge-command.ts +45 -2
- package/src/cli/help.ts +3 -0
- package/src/cli/profiles-command.ts +25 -11
- package/src/config/agent-settings-policy.ts +1 -18
- package/src/input/agent-workspace-activation.ts +53 -0
- package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
- package/src/input/agent-workspace-artifact-metadata.ts +17 -0
- package/src/input/agent-workspace-basic-command-editors.ts +5 -1
- package/src/input/agent-workspace-categories.ts +153 -155
- package/src/input/agent-workspace-channel-triage.ts +481 -0
- package/src/input/agent-workspace-channels.ts +263 -0
- package/src/input/agent-workspace-command-editor.ts +152 -0
- package/src/input/agent-workspace-context-snapshot.ts +349 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
- package/src/input/agent-workspace-document-editor.ts +502 -0
- package/src/input/agent-workspace-document-ops-editor.ts +523 -0
- package/src/input/agent-workspace-host-category.ts +1 -1
- package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
- package/src/input/agent-workspace-model-compare-editor.ts +376 -0
- package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
- package/src/input/agent-workspace-model-compare-types.ts +82 -0
- package/src/input/agent-workspace-model-compare-utils.ts +35 -0
- package/src/input/agent-workspace-onboarding-categories.ts +141 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
- package/src/input/agent-workspace-operations-command-editors.ts +22 -0
- package/src/input/agent-workspace-research-report-editor.ts +212 -0
- package/src/input/agent-workspace-research-run-editor.ts +144 -0
- package/src/input/agent-workspace-research-source-editor.ts +167 -0
- package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
- package/src/input/agent-workspace-review-packet-utils.ts +265 -0
- package/src/input/agent-workspace-settings.ts +262 -30
- package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
- package/src/input/agent-workspace-setup-snapshot.ts +198 -0
- package/src/input/agent-workspace-setup.ts +34 -1
- package/src/input/agent-workspace-snapshot.ts +162 -147
- package/src/input/agent-workspace-types.ts +287 -2
- package/src/input/agent-workspace.ts +18 -0
- package/src/input/command-registry.ts +9 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
- package/src/input/commands/channels-runtime.ts +102 -114
- package/src/input/commands/delegation-runtime.ts +87 -24
- package/src/input/commands/knowledge-browser-flags.ts +12 -0
- package/src/input/commands/knowledge.ts +28 -14
- package/src/input/commands/operator-actions-runtime.ts +6 -3
- package/src/input/commands/schedule-runtime.ts +71 -14
- package/src/input/commands/session-content.ts +2 -2
- package/src/input/commands/shell-core.ts +2 -0
- package/src/input/commands/vibe-runtime.ts +140 -0
- package/src/input/commands.ts +2 -0
- package/src/input/connected-host-routes.ts +123 -0
- package/src/input/settings-modal-types.ts +14 -0
- package/src/main.ts +15 -15
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/agent-workspace-context-lines.ts +686 -0
- package/src/renderer/agent-workspace.ts +30 -419
- package/src/renderer/settings-modal-helpers.ts +12 -0
- package/src/renderer/settings-modal.ts +12 -0
- package/src/runtime/bootstrap-command-context.ts +11 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -3
- package/src/runtime/bootstrap-core.ts +43 -1
- package/src/runtime/bootstrap-shell.ts +6 -0
- package/src/runtime/bootstrap.ts +113 -30
- package/src/runtime/connected-host-auth.ts +3 -2
- package/src/runtime/execution-ledger.ts +231 -0
- package/src/runtime/services.ts +88 -10
- package/src/runtime/tool-permission-safety.ts +287 -0
- package/src/shell/session-continuity-hints.ts +39 -0
- package/src/tools/agent-artifacts-tool.ts +606 -0
- package/src/tools/agent-audit-tool.ts +155 -0
- package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
- package/src/tools/agent-autonomy-tool.ts +140 -0
- package/src/tools/agent-channel-send-tool.ts +24 -2
- package/src/tools/agent-channels-tool.ts +140 -0
- package/src/tools/agent-computer-tool.ts +168 -0
- package/src/tools/agent-context-policy.ts +267 -10
- package/src/tools/agent-context-tool.ts +143 -0
- package/src/tools/agent-delegation-tool.ts +128 -0
- package/src/tools/agent-device-tool.ts +240 -0
- package/src/tools/agent-documents-tool.ts +684 -0
- package/src/tools/agent-execution-tool.ts +230 -0
- package/src/tools/agent-harness-agent-orchestration.ts +703 -0
- package/src/tools/agent-harness-autonomy-intake.ts +785 -0
- package/src/tools/agent-harness-autonomy-live-records.ts +588 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
- package/src/tools/agent-harness-autonomy-queue.ts +560 -0
- package/src/tools/agent-harness-background-processes-types.ts +35 -0
- package/src/tools/agent-harness-background-processes.ts +794 -0
- package/src/tools/agent-harness-browser-cockpit-route.ts +219 -0
- package/src/tools/agent-harness-browser-control.ts +338 -0
- package/src/tools/agent-harness-channel-metadata.ts +168 -10
- package/src/tools/agent-harness-cli-command-policy.ts +110 -0
- package/src/tools/agent-harness-command-catalog.ts +4 -2
- package/src/tools/agent-harness-command-runner.ts +42 -0
- package/src/tools/agent-harness-connected-host-status.ts +7 -4
- package/src/tools/agent-harness-delegation-posture.ts +234 -19
- package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
- package/src/tools/agent-harness-document-ops-types.ts +72 -0
- package/src/tools/agent-harness-document-ops.ts +671 -0
- package/src/tools/agent-harness-execution-history.ts +489 -0
- package/src/tools/agent-harness-execution-posture.ts +382 -0
- package/src/tools/agent-harness-file-recovery.ts +135 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
- package/src/tools/agent-harness-learning-curator-common.ts +102 -0
- package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
- package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
- package/src/tools/agent-harness-learning-curator-types.ts +151 -0
- package/src/tools/agent-harness-learning-curator.ts +417 -0
- package/src/tools/agent-harness-local-model-benchmarks.ts +199 -0
- package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
- package/src/tools/agent-harness-local-model-endpoints.ts +673 -0
- package/src/tools/agent-harness-media-posture.ts +208 -2
- package/src/tools/agent-harness-memory-posture.ts +556 -0
- package/src/tools/agent-harness-metadata.ts +100 -168
- package/src/tools/agent-harness-mode-catalog.ts +84 -33
- package/src/tools/agent-harness-model-catalog.ts +162 -0
- package/src/tools/agent-harness-model-provider-health.ts +186 -0
- package/src/tools/agent-harness-model-readiness.ts +406 -0
- package/src/tools/agent-harness-model-routing-types.ts +266 -0
- package/src/tools/agent-harness-model-routing-utils.ts +30 -0
- package/src/tools/agent-harness-model-routing.ts +137 -190
- package/src/tools/agent-harness-operator-methods.ts +115 -133
- package/src/tools/agent-harness-pairing-posture.ts +431 -18
- package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
- package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
- package/src/tools/agent-harness-personal-ops-lanes.ts +204 -0
- package/src/tools/agent-harness-personal-ops-records.ts +732 -0
- package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
- package/src/tools/agent-harness-personal-ops-types.ts +222 -0
- package/src/tools/agent-harness-personal-ops.ts +606 -0
- package/src/tools/agent-harness-project-context.ts +144 -0
- package/src/tools/agent-harness-prompt-context.ts +589 -0
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-release-evidence.ts +9 -7
- package/src/tools/agent-harness-release-readiness.ts +9 -7
- package/src/tools/agent-harness-research-briefing.ts +427 -0
- package/src/tools/agent-harness-research-queue.ts +250 -0
- package/src/tools/agent-harness-research-runs.ts +295 -0
- package/src/tools/agent-harness-research-workflow.ts +322 -0
- package/src/tools/agent-harness-security-posture.ts +2 -2
- package/src/tools/agent-harness-service-posture.ts +9 -9
- package/src/tools/agent-harness-session-metadata.ts +3 -3
- package/src/tools/agent-harness-setup-connected-host.ts +501 -0
- package/src/tools/agent-harness-setup-describe.ts +557 -0
- package/src/tools/agent-harness-setup-handoffs.ts +502 -0
- package/src/tools/agent-harness-setup-model-helpers.ts +101 -0
- package/src/tools/agent-harness-setup-plan.ts +253 -0
- package/src/tools/agent-harness-setup-posture-types.ts +218 -0
- package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
- package/src/tools/agent-harness-setup-posture.ts +449 -221
- package/src/tools/agent-harness-setup-smoke.ts +508 -0
- package/src/tools/agent-harness-sudo-posture.ts +114 -0
- package/src/tools/agent-harness-tool-schema.ts +120 -7
- package/src/tools/agent-harness-tool-types.ts +78 -0
- package/src/tools/agent-harness-tool-utils.ts +43 -0
- package/src/tools/agent-harness-tool.ts +311 -370
- package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
- package/src/tools/agent-harness-ui-surface-types.ts +46 -0
- package/src/tools/agent-harness-vibe-health.ts +105 -0
- package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
- package/src/tools/agent-harness-workspace-actions.ts +114 -8
- package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
- package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
- package/src/tools/agent-host-tool.ts +159 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
- package/src/tools/agent-learning-consolidation-core.ts +327 -0
- package/src/tools/agent-learning-consolidation-tool.ts +758 -0
- package/src/tools/agent-memory-tool.ts +214 -0
- package/src/tools/agent-model-compare-export.ts +315 -0
- package/src/tools/agent-model-compare-handoff.ts +592 -0
- package/src/tools/agent-model-compare-judgment.ts +633 -0
- package/src/tools/agent-model-compare-run.ts +722 -0
- package/src/tools/agent-model-compare-tool.ts +698 -0
- package/src/tools/agent-model-compare-types.ts +191 -0
- package/src/tools/agent-model-compare-utils.ts +93 -0
- package/src/tools/agent-models-tool.ts +208 -0
- package/src/tools/agent-operator-action-tool.ts +1 -1
- package/src/tools/agent-operator-method-tool.ts +643 -0
- package/src/tools/agent-personal-ops-tool.ts +197 -0
- package/src/tools/agent-policy-explanation.ts +415 -0
- package/src/tools/agent-research-report-tool.ts +608 -0
- package/src/tools/agent-research-runs-tool.ts +434 -0
- package/src/tools/agent-research-sources-tool.ts +443 -0
- package/src/tools/agent-research-tool.ts +687 -0
- package/src/tools/agent-review-packet-presets-core.ts +757 -0
- package/src/tools/agent-review-packet-presets-tool.ts +466 -0
- package/src/tools/agent-review-packet-share-tool.ts +305 -0
- package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
- package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
- package/src/tools/agent-route-planner-candidates-work.ts +251 -0
- package/src/tools/agent-route-planner-helpers.ts +667 -0
- package/src/tools/agent-route-planner.ts +185 -0
- package/src/tools/agent-route-tool.ts +105 -0
- package/src/tools/agent-schedule-edit-tool.ts +210 -0
- package/src/tools/agent-schedule-tool.ts +282 -0
- package/src/tools/agent-security-tool.ts +145 -0
- package/src/tools/agent-sessions-tool.ts +122 -0
- package/src/tools/agent-settings-import-tool.ts +104 -0
- package/src/tools/agent-settings-tool.ts +176 -0
- package/src/tools/agent-setup-tool.ts +226 -0
- package/src/tools/agent-support-tool.ts +122 -0
- package/src/tools/agent-terminal-process-tools.ts +167 -0
- package/src/tools/agent-tool-policy-guard-types.ts +77 -0
- package/src/tools/agent-tool-policy-guard.ts +110 -89
- package/src/tools/agent-vibe-tool.ts +297 -0
- package/src/tools/agent-work-plan-tool.ts +265 -6
- package/src/tools/agent-workspace-tool.ts +305 -0
- package/src/tools/artifact-archive.ts +169 -0
- package/src/tools/tool-definition-compaction.ts +36 -0
- package/src/tools/tool-execution-safety.ts +41 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
+
import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
|
|
3
|
+
import type { MemoryBundle, MemoryRecord, MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
|
|
4
|
+
import type { ShellPathService } from '@/runtime/index.ts';
|
|
5
|
+
import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
|
|
6
|
+
import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
|
|
7
|
+
import { AgentSkillRegistry } from '../agent/skill-registry.ts';
|
|
8
|
+
import { MEMORY_CLASSES, MEMORY_REVIEW_STATES, MEMORY_SCOPES, MODES, WRITE_MODES, cloneRecord, deleteDuplicate, domainForCandidate, failure, getRecord, markDuplicateStale, nextGeneratedId, output, readLimit, readMode, readReceiptFile, readString, receiptId, receiptIdFromReceipt, receiptPath, requireConfirmedWrite, resolveCandidate, updateSurvivor, writeReceipt, type AgentLearningConsolidationDomain, type AgentLearningConsolidationToolArgs, type LearningConsolidationReceipt } from './agent-learning-consolidation-core.ts';
|
|
9
|
+
import type { LearningCandidate } from './agent-harness-learning-curator.ts';
|
|
10
|
+
function arrayField(snapshot: Record<string, unknown>, key: string): string[] {
|
|
11
|
+
const value = snapshot[key];
|
|
12
|
+
return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function stringField(snapshot: Record<string, unknown>, key: string): string | undefined {
|
|
16
|
+
const value = snapshot[key];
|
|
17
|
+
return typeof value === 'string' ? value : undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function numberField(snapshot: Record<string, unknown>, key: string): number | undefined {
|
|
21
|
+
const value = snapshot[key];
|
|
22
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function booleanField(snapshot: Record<string, unknown>, key: string): boolean | undefined {
|
|
26
|
+
const value = snapshot[key];
|
|
27
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function requirementsFromSnapshot(snapshot: Record<string, unknown>): readonly { readonly kind: 'env' | 'command'; readonly name: string; readonly description?: string }[] {
|
|
31
|
+
const value = snapshot.requirements;
|
|
32
|
+
if (!Array.isArray(value)) return [];
|
|
33
|
+
return value
|
|
34
|
+
.filter((entry): entry is Record<string, unknown> => typeof entry === 'object' && entry !== null && !Array.isArray(entry))
|
|
35
|
+
.map((entry) => {
|
|
36
|
+
const kind = entry.kind === 'command' ? 'command' as const : 'env' as const;
|
|
37
|
+
const name = readString(entry.name);
|
|
38
|
+
const description = readString(entry.description);
|
|
39
|
+
return {
|
|
40
|
+
kind,
|
|
41
|
+
name,
|
|
42
|
+
...(description ? { description } : {}),
|
|
43
|
+
};
|
|
44
|
+
})
|
|
45
|
+
.filter((entry) => entry.name);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function requirementNames(snapshot: Record<string, unknown>, kind: 'env' | 'command'): readonly string[] {
|
|
49
|
+
return requirementsFromSnapshot(snapshot)
|
|
50
|
+
.filter((requirement) => requirement.kind === kind)
|
|
51
|
+
.map((requirement) => requirement.name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function localRecordSource(snapshot: Record<string, unknown>): 'user' | 'agent' | 'imported' | 'system' {
|
|
55
|
+
const source = stringField(snapshot, 'source');
|
|
56
|
+
return source === 'agent' || source === 'imported' || source === 'system' ? source : 'user';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function memoryScope(snapshot: Record<string, unknown>): 'session' | 'project' | 'team' {
|
|
60
|
+
const scope = stringField(snapshot, 'scope');
|
|
61
|
+
return scope && MEMORY_SCOPES.has(scope) ? scope as 'session' | 'project' | 'team' : 'project';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function memoryClass(snapshot: Record<string, unknown>): MemoryRecord['cls'] {
|
|
65
|
+
const cls = stringField(snapshot, 'cls');
|
|
66
|
+
return cls && MEMORY_CLASSES.has(cls) ? cls as MemoryRecord['cls'] : 'fact';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function memoryReviewState(snapshot: Record<string, unknown>): MemoryRecord['reviewState'] {
|
|
70
|
+
const reviewState = stringField(snapshot, 'reviewState');
|
|
71
|
+
return reviewState && MEMORY_REVIEW_STATES.has(reviewState) ? reviewState as MemoryRecord['reviewState'] : 'fresh';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function memoryRecordFromSnapshot(snapshot: Record<string, unknown>): MemoryRecord {
|
|
75
|
+
const id = stringField(snapshot, 'id');
|
|
76
|
+
const summary = stringField(snapshot, 'summary');
|
|
77
|
+
if (!id || !summary) throw new Error('Delete receipt memory snapshot is missing id or summary.');
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
scope: memoryScope(snapshot),
|
|
81
|
+
cls: memoryClass(snapshot),
|
|
82
|
+
summary,
|
|
83
|
+
detail: stringField(snapshot, 'detail'),
|
|
84
|
+
tags: arrayField(snapshot, 'tags'),
|
|
85
|
+
provenance: Array.isArray(snapshot.provenance)
|
|
86
|
+
? snapshot.provenance.filter((entry): entry is MemoryRecord['provenance'][number] => (
|
|
87
|
+
typeof entry === 'object'
|
|
88
|
+
&& entry !== null
|
|
89
|
+
&& !Array.isArray(entry)
|
|
90
|
+
&& ['session', 'turn', 'task', 'event', 'file'].includes(String((entry as Record<string, unknown>).kind))
|
|
91
|
+
&& typeof (entry as Record<string, unknown>).ref === 'string'
|
|
92
|
+
))
|
|
93
|
+
: [],
|
|
94
|
+
reviewState: memoryReviewState(snapshot),
|
|
95
|
+
confidence: numberField(snapshot, 'confidence') ?? 60,
|
|
96
|
+
reviewedAt: numberField(snapshot, 'reviewedAt'),
|
|
97
|
+
reviewedBy: stringField(snapshot, 'reviewedBy'),
|
|
98
|
+
staleReason: stringField(snapshot, 'staleReason'),
|
|
99
|
+
createdAt: numberField(snapshot, 'createdAt') ?? Date.now(),
|
|
100
|
+
updatedAt: numberField(snapshot, 'updatedAt') ?? Date.now(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function restoreReviewState(
|
|
105
|
+
shellPaths: ShellPathService,
|
|
106
|
+
memoryRegistry: MemoryRegistry,
|
|
107
|
+
domain: AgentLearningConsolidationDomain,
|
|
108
|
+
id: string,
|
|
109
|
+
snapshot: Record<string, unknown>,
|
|
110
|
+
): void {
|
|
111
|
+
const reviewState = stringField(snapshot, 'reviewState') ?? 'fresh';
|
|
112
|
+
const staleReason = stringField(snapshot, 'staleReason') ?? 'Restored by learning curator rollback.';
|
|
113
|
+
if (domain === 'memory') {
|
|
114
|
+
const confidence = typeof snapshot.confidence === 'number' ? snapshot.confidence : undefined;
|
|
115
|
+
memoryRegistry.review(id, { state: reviewState === 'stale' || reviewState === 'reviewed' || reviewState === 'contradicted' ? reviewState : 'fresh', confidence, staleReason, reviewedBy: 'agent' });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (reviewState === 'reviewed') {
|
|
119
|
+
if (domain === 'persona') AgentPersonaRegistry.fromShellPaths(shellPaths).markReviewed(id);
|
|
120
|
+
else if (domain === 'skill') AgentSkillRegistry.fromShellPaths(shellPaths).markReviewed(id);
|
|
121
|
+
else AgentRoutineRegistry.fromShellPaths(shellPaths).markReviewed(id);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (reviewState === 'stale') {
|
|
125
|
+
if (domain === 'persona') AgentPersonaRegistry.fromShellPaths(shellPaths).markStale(id, staleReason);
|
|
126
|
+
else if (domain === 'skill') AgentSkillRegistry.fromShellPaths(shellPaths).markStale(id, staleReason);
|
|
127
|
+
else AgentRoutineRegistry.fromShellPaths(shellPaths).markStale(id, staleReason);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (domain === 'persona') {
|
|
131
|
+
AgentPersonaRegistry.fromShellPaths(shellPaths).update(id, {
|
|
132
|
+
description: stringField(snapshot, 'description'),
|
|
133
|
+
body: stringField(snapshot, 'body'),
|
|
134
|
+
tags: arrayField(snapshot, 'tags'),
|
|
135
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
136
|
+
provenance: 'rollback-learning-curator-consolidation',
|
|
137
|
+
});
|
|
138
|
+
} else if (domain === 'skill') {
|
|
139
|
+
AgentSkillRegistry.fromShellPaths(shellPaths).update(id, {
|
|
140
|
+
description: stringField(snapshot, 'description'),
|
|
141
|
+
procedure: stringField(snapshot, 'procedure'),
|
|
142
|
+
tags: arrayField(snapshot, 'tags'),
|
|
143
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
144
|
+
provenance: 'rollback-learning-curator-consolidation',
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
AgentRoutineRegistry.fromShellPaths(shellPaths).update(id, {
|
|
148
|
+
description: stringField(snapshot, 'description'),
|
|
149
|
+
steps: stringField(snapshot, 'steps'),
|
|
150
|
+
tags: arrayField(snapshot, 'tags'),
|
|
151
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
152
|
+
provenance: 'rollback-learning-curator-consolidation',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function restoreSurvivor(
|
|
158
|
+
shellPaths: ShellPathService,
|
|
159
|
+
memoryRegistry: MemoryRegistry,
|
|
160
|
+
receipt: LearningConsolidationReceipt,
|
|
161
|
+
): Record<string, unknown> {
|
|
162
|
+
const snapshot = receipt.beforeSurvivor;
|
|
163
|
+
if (!snapshot) throw new Error(`Receipt ${receipt.id} has no survivor snapshot.`);
|
|
164
|
+
if (receipt.domain === 'memory') {
|
|
165
|
+
const record = memoryRegistry.update(receipt.survivorId, {
|
|
166
|
+
detail: stringField(snapshot, 'detail') ?? '',
|
|
167
|
+
tags: arrayField(snapshot, 'tags'),
|
|
168
|
+
});
|
|
169
|
+
if (!record) throw new Error(`Unknown Agent-local memory ${receipt.survivorId}`);
|
|
170
|
+
restoreReviewState(shellPaths, memoryRegistry, receipt.domain, receipt.survivorId, snapshot);
|
|
171
|
+
return cloneRecord(memoryRegistry.get(receipt.survivorId));
|
|
172
|
+
}
|
|
173
|
+
updateSurvivor(shellPaths, memoryRegistry, receipt.domain, receipt.survivorId, {
|
|
174
|
+
description: stringField(snapshot, 'description') ?? '',
|
|
175
|
+
tags: arrayField(snapshot, 'tags'),
|
|
176
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
177
|
+
});
|
|
178
|
+
restoreReviewState(shellPaths, memoryRegistry, receipt.domain, receipt.survivorId, snapshot);
|
|
179
|
+
return cloneRecord(getRecord(shellPaths, memoryRegistry, receipt.domain, receipt.survivorId));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function recreateArguments(receipt: LearningConsolidationReceipt, snapshot: Record<string, unknown>): Record<string, unknown> {
|
|
183
|
+
if (receipt.domain === 'memory') {
|
|
184
|
+
return {
|
|
185
|
+
action: 'create',
|
|
186
|
+
scope: memoryScope(snapshot),
|
|
187
|
+
cls: memoryClass(snapshot),
|
|
188
|
+
summary: stringField(snapshot, 'summary') ?? '',
|
|
189
|
+
detail: stringField(snapshot, 'detail') ?? '',
|
|
190
|
+
tags: arrayField(snapshot, 'tags'),
|
|
191
|
+
confidence: numberField(snapshot, 'confidence') ?? 60,
|
|
192
|
+
provenance: `recreated-from-learning-consolidation:${receipt.id}:${stringField(snapshot, 'id') ?? 'unknown'}`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (receipt.domain === 'persona') {
|
|
196
|
+
return {
|
|
197
|
+
domain: 'persona',
|
|
198
|
+
action: 'create',
|
|
199
|
+
name: stringField(snapshot, 'name') ?? '',
|
|
200
|
+
description: stringField(snapshot, 'description') ?? '',
|
|
201
|
+
body: stringField(snapshot, 'body') ?? '',
|
|
202
|
+
tags: arrayField(snapshot, 'tags'),
|
|
203
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
204
|
+
provenance: stringField(snapshot, 'provenance') ?? `recreated-from-learning-consolidation:${receipt.id}`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (receipt.domain === 'skill') {
|
|
208
|
+
return {
|
|
209
|
+
domain: 'skill',
|
|
210
|
+
action: 'create',
|
|
211
|
+
name: stringField(snapshot, 'name') ?? '',
|
|
212
|
+
description: stringField(snapshot, 'description') ?? '',
|
|
213
|
+
procedure: stringField(snapshot, 'procedure') ?? '',
|
|
214
|
+
tags: arrayField(snapshot, 'tags'),
|
|
215
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
216
|
+
requiresEnv: requirementNames(snapshot, 'env'),
|
|
217
|
+
requiresCommands: requirementNames(snapshot, 'command'),
|
|
218
|
+
enabled: booleanField(snapshot, 'enabled') === true,
|
|
219
|
+
provenance: stringField(snapshot, 'provenance') ?? `recreated-from-learning-consolidation:${receipt.id}`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
domain: 'routine',
|
|
224
|
+
action: 'create',
|
|
225
|
+
name: stringField(snapshot, 'name') ?? '',
|
|
226
|
+
description: stringField(snapshot, 'description') ?? '',
|
|
227
|
+
steps: stringField(snapshot, 'steps') ?? '',
|
|
228
|
+
tags: arrayField(snapshot, 'tags'),
|
|
229
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
230
|
+
requiresEnv: requirementNames(snapshot, 'env'),
|
|
231
|
+
requiresCommands: requirementNames(snapshot, 'command'),
|
|
232
|
+
enabled: booleanField(snapshot, 'enabled') === true,
|
|
233
|
+
provenance: stringField(snapshot, 'provenance') ?? `recreated-from-learning-consolidation:${receipt.id}`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function currentLocalIds(shellPaths: ShellPathService, memoryRegistry: MemoryRegistry, domain: AgentLearningConsolidationDomain): Set<string> {
|
|
238
|
+
if (domain === 'memory') return new Set(memoryRegistry.getAll().map((record) => record.id));
|
|
239
|
+
if (domain === 'persona') return new Set(AgentPersonaRegistry.fromShellPaths(shellPaths).list().map((record) => record.id));
|
|
240
|
+
if (domain === 'skill') return new Set(AgentSkillRegistry.fromShellPaths(shellPaths).list().map((record) => record.id));
|
|
241
|
+
return new Set(AgentRoutineRegistry.fromShellPaths(shellPaths).list().map((record) => record.id));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function currentLocalNames(shellPaths: ShellPathService, domain: AgentLearningConsolidationDomain): Set<string> {
|
|
245
|
+
if (domain === 'memory') return new Set();
|
|
246
|
+
if (domain === 'persona') return new Set(AgentPersonaRegistry.fromShellPaths(shellPaths).list().map((record) => record.name.toLowerCase()));
|
|
247
|
+
if (domain === 'skill') return new Set(AgentSkillRegistry.fromShellPaths(shellPaths).list().map((record) => record.name.toLowerCase()));
|
|
248
|
+
return new Set(AgentRoutineRegistry.fromShellPaths(shellPaths).list().map((record) => record.name.toLowerCase()));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function recreateGuidance(
|
|
252
|
+
shellPaths: ShellPathService,
|
|
253
|
+
memoryRegistry: MemoryRegistry,
|
|
254
|
+
receipt: LearningConsolidationReceipt,
|
|
255
|
+
): Record<string, unknown> | null {
|
|
256
|
+
if (receipt.phase !== 'delete') return null;
|
|
257
|
+
const occupiedIds = currentLocalIds(shellPaths, memoryRegistry, receipt.domain);
|
|
258
|
+
const occupiedNames = currentLocalNames(shellPaths, receipt.domain);
|
|
259
|
+
const records = receipt.beforeDuplicates.map((snapshot) => {
|
|
260
|
+
const previousId = stringField(snapshot, 'id') ?? '(unknown)';
|
|
261
|
+
const name = stringField(snapshot, 'name') ?? stringField(snapshot, 'summary') ?? previousId;
|
|
262
|
+
const expectedId = receipt.domain === 'memory'
|
|
263
|
+
? previousId
|
|
264
|
+
: nextGeneratedId(name, occupiedIds, receipt.domain);
|
|
265
|
+
const nameConflict = receipt.domain !== 'memory' && occupiedNames.has(name.toLowerCase());
|
|
266
|
+
const idConflict = occupiedIds.has(previousId);
|
|
267
|
+
const possible = receipt.domain === 'memory'
|
|
268
|
+
? !idConflict
|
|
269
|
+
: expectedId === previousId && !nameConflict;
|
|
270
|
+
occupiedIds.add(expectedId);
|
|
271
|
+
if (receipt.domain !== 'memory') occupiedNames.add(name.toLowerCase());
|
|
272
|
+
return {
|
|
273
|
+
previousId,
|
|
274
|
+
name,
|
|
275
|
+
expectedId,
|
|
276
|
+
exactId: {
|
|
277
|
+
supported: true,
|
|
278
|
+
possible,
|
|
279
|
+
method: receipt.domain === 'memory'
|
|
280
|
+
? 'SDK memory bundle import preserves record ids.'
|
|
281
|
+
: 'Agent-local registry ids are deterministic from name when the old generated id and name are still free.',
|
|
282
|
+
reason: possible
|
|
283
|
+
? 'Exact-id recreation is currently available.'
|
|
284
|
+
: idConflict
|
|
285
|
+
? `A current ${receipt.domain} record already uses ${previousId}.`
|
|
286
|
+
: nameConflict
|
|
287
|
+
? `A current ${receipt.domain} record already uses the name ${name}.`
|
|
288
|
+
: `The next generated id would be ${expectedId}, not ${previousId}.`,
|
|
289
|
+
},
|
|
290
|
+
fallbackCreateRoute: receipt.domain === 'memory'
|
|
291
|
+
? 'memory action:"create"'
|
|
292
|
+
: `agent_local_registry domain:"${receipt.domain}" action:"create"`,
|
|
293
|
+
createArguments: recreateArguments(receipt, snapshot),
|
|
294
|
+
};
|
|
295
|
+
});
|
|
296
|
+
return {
|
|
297
|
+
automaticRollback: false,
|
|
298
|
+
recreateRoute: `agent_learning_consolidation mode:"recreate" receiptId:"${receipt.id}" confirm:true explicitUserRequest:"..."`,
|
|
299
|
+
policy: 'Delete remains the final phase. Recreate is a separate confirmed recovery action and refuses when exact ids are not currently safe.',
|
|
300
|
+
records,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function receiptSummary(
|
|
305
|
+
receipt: LearningConsolidationReceipt,
|
|
306
|
+
context?: { readonly shellPaths: ShellPathService; readonly memoryRegistry: MemoryRegistry },
|
|
307
|
+
): Record<string, unknown> {
|
|
308
|
+
const deleteRecovery = context ? recreateGuidance(context.shellPaths, context.memoryRegistry, receipt) : null;
|
|
309
|
+
return {
|
|
310
|
+
receiptId: receipt.id,
|
|
311
|
+
createdAt: receipt.createdAt,
|
|
312
|
+
domain: receipt.domain,
|
|
313
|
+
candidateId: receipt.candidateId,
|
|
314
|
+
phase: receipt.phase,
|
|
315
|
+
survivorId: receipt.survivorId,
|
|
316
|
+
duplicateIds: receipt.duplicateIds,
|
|
317
|
+
rollbackRoute: receipt.phase === 'merge' || receipt.phase === 'stale'
|
|
318
|
+
? `agent_learning_consolidation mode:"rollback" receiptId:"${receipt.id}" confirm:true explicitUserRequest:"..."`
|
|
319
|
+
: null,
|
|
320
|
+
recreateRoute: receipt.phase === 'delete'
|
|
321
|
+
? `agent_learning_consolidation mode:"recreate" receiptId:"${receipt.id}" confirm:true explicitUserRequest:"..."`
|
|
322
|
+
: null,
|
|
323
|
+
...(deleteRecovery ? { deleteRecovery } : {}),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function previewCandidate(candidate: LearningCandidate): Record<string, unknown> {
|
|
328
|
+
const plan = candidate.consolidation;
|
|
329
|
+
if (!plan) throw new Error(`Candidate ${candidate.id} is not a duplicate consolidation candidate.`);
|
|
330
|
+
return {
|
|
331
|
+
status: 'preview',
|
|
332
|
+
candidateId: candidate.id,
|
|
333
|
+
label: candidate.label,
|
|
334
|
+
domain: candidate.domain,
|
|
335
|
+
survivorId: plan.survivorId,
|
|
336
|
+
duplicateIds: plan.duplicateIds,
|
|
337
|
+
diffFields: plan.diffs.map((diff) => diff.field),
|
|
338
|
+
diffs: plan.diffs,
|
|
339
|
+
updateFields: plan.updateFields ?? null,
|
|
340
|
+
phases: [
|
|
341
|
+
{
|
|
342
|
+
id: 'merge',
|
|
343
|
+
route: `agent_learning_consolidation mode:"merge" candidateId:"${candidate.id}" confirm:true explicitUserRequest:"..."`,
|
|
344
|
+
effect: 'Update the survivor with merged visible fields and write a rollback receipt.',
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'stale',
|
|
348
|
+
route: `agent_learning_consolidation mode:"stale" candidateId:"${candidate.id}" confirm:true explicitUserRequest:"..."`,
|
|
349
|
+
effect: 'Mark duplicates stale before deletion and write a rollback receipt.',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: 'delete',
|
|
353
|
+
route: `agent_learning_consolidation mode:"delete" candidateId:"${candidate.id}" confirm:true explicitUserRequest:"..."`,
|
|
354
|
+
effect: 'Delete only duplicates that are already stale. Exact-id rollback is not automatic after delete.',
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
inspectRoute: `memory action:"candidate" candidateId:"${candidate.id}"`,
|
|
358
|
+
policy: 'Preview is read-only. Merge, stale, delete, and rollback each require confirm:true plus explicitUserRequest.',
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function applyMerge(
|
|
363
|
+
shellPaths: ShellPathService,
|
|
364
|
+
memoryRegistry: MemoryRegistry,
|
|
365
|
+
candidate: LearningCandidate,
|
|
366
|
+
explicitUserRequest: string,
|
|
367
|
+
): Record<string, unknown> {
|
|
368
|
+
const domain = domainForCandidate(candidate);
|
|
369
|
+
const plan = candidate.consolidation;
|
|
370
|
+
if (!plan?.updateFields) throw new Error(`Candidate ${candidate.id} has no survivor merge fields.`);
|
|
371
|
+
const beforeSurvivor = cloneRecord(getRecord(shellPaths, memoryRegistry, domain, plan.survivorId));
|
|
372
|
+
const afterSurvivor = updateSurvivor(shellPaths, memoryRegistry, domain, plan.survivorId, plan.updateFields);
|
|
373
|
+
const receipt: LearningConsolidationReceipt = {
|
|
374
|
+
id: receiptId(candidate, 'merge'),
|
|
375
|
+
createdAt: new Date().toISOString(),
|
|
376
|
+
domain,
|
|
377
|
+
candidateId: candidate.id,
|
|
378
|
+
phase: 'merge',
|
|
379
|
+
explicitUserRequest,
|
|
380
|
+
survivorId: plan.survivorId,
|
|
381
|
+
duplicateIds: plan.duplicateIds,
|
|
382
|
+
beforeSurvivor,
|
|
383
|
+
beforeDuplicates: [],
|
|
384
|
+
};
|
|
385
|
+
writeReceipt(shellPaths, receipt);
|
|
386
|
+
return {
|
|
387
|
+
status: 'applied',
|
|
388
|
+
phase: 'merge',
|
|
389
|
+
candidateId: candidate.id,
|
|
390
|
+
survivorId: plan.survivorId,
|
|
391
|
+
receipt: receiptSummary(receipt),
|
|
392
|
+
afterSurvivor,
|
|
393
|
+
next: 'Inspect the survivor, then run the stale phase only if the merged fields are correct.',
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function applyStale(
|
|
398
|
+
shellPaths: ShellPathService,
|
|
399
|
+
memoryRegistry: MemoryRegistry,
|
|
400
|
+
candidate: LearningCandidate,
|
|
401
|
+
explicitUserRequest: string,
|
|
402
|
+
): Record<string, unknown> {
|
|
403
|
+
const domain = domainForCandidate(candidate);
|
|
404
|
+
const plan = candidate.consolidation;
|
|
405
|
+
if (!plan) throw new Error(`Candidate ${candidate.id} is not a duplicate consolidation candidate.`);
|
|
406
|
+
const beforeDuplicates = plan.duplicateIds.map((id) => cloneRecord(getRecord(shellPaths, memoryRegistry, domain, id)));
|
|
407
|
+
const afterDuplicates = plan.duplicateIds.map((id) => markDuplicateStale(shellPaths, memoryRegistry, domain, id, plan.survivorId));
|
|
408
|
+
const receipt: LearningConsolidationReceipt = {
|
|
409
|
+
id: receiptId(candidate, 'stale'),
|
|
410
|
+
createdAt: new Date().toISOString(),
|
|
411
|
+
domain,
|
|
412
|
+
candidateId: candidate.id,
|
|
413
|
+
phase: 'stale',
|
|
414
|
+
explicitUserRequest,
|
|
415
|
+
survivorId: plan.survivorId,
|
|
416
|
+
duplicateIds: plan.duplicateIds,
|
|
417
|
+
beforeDuplicates,
|
|
418
|
+
};
|
|
419
|
+
writeReceipt(shellPaths, receipt);
|
|
420
|
+
return {
|
|
421
|
+
status: 'applied',
|
|
422
|
+
phase: 'stale',
|
|
423
|
+
candidateId: candidate.id,
|
|
424
|
+
survivorId: plan.survivorId,
|
|
425
|
+
duplicateIds: plan.duplicateIds,
|
|
426
|
+
receipt: receiptSummary(receipt),
|
|
427
|
+
afterDuplicates,
|
|
428
|
+
next: 'Re-run the curator and delete only if the user confirms the stale duplicates are no longer needed.',
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function applyDelete(
|
|
433
|
+
shellPaths: ShellPathService,
|
|
434
|
+
memoryRegistry: MemoryRegistry,
|
|
435
|
+
candidate: LearningCandidate,
|
|
436
|
+
explicitUserRequest: string,
|
|
437
|
+
): Record<string, unknown> {
|
|
438
|
+
const domain = domainForCandidate(candidate);
|
|
439
|
+
const plan = candidate.consolidation;
|
|
440
|
+
if (!plan) throw new Error(`Candidate ${candidate.id} is not a duplicate consolidation candidate.`);
|
|
441
|
+
const beforeDuplicates = plan.duplicateIds.map((id) => cloneRecord(getRecord(shellPaths, memoryRegistry, domain, id)));
|
|
442
|
+
const unstaled = beforeDuplicates.filter((record) => record.reviewState !== 'stale').map((record) => String(record.id ?? '(unknown)'));
|
|
443
|
+
if (unstaled.length > 0) {
|
|
444
|
+
throw new Error(`Delete refused. Stage duplicates stale first: ${unstaled.join(', ')}.`);
|
|
445
|
+
}
|
|
446
|
+
if (domain === 'skill') {
|
|
447
|
+
const bundles = AgentSkillRegistry.fromShellPaths(shellPaths).snapshot().bundles;
|
|
448
|
+
const references = plan.duplicateIds.flatMap((duplicateId) => (
|
|
449
|
+
bundles
|
|
450
|
+
.filter((bundle) => bundle.skillIds.includes(duplicateId))
|
|
451
|
+
.map((bundle) => `${duplicateId} in ${bundle.id}`)
|
|
452
|
+
));
|
|
453
|
+
if (references.length > 0) {
|
|
454
|
+
throw new Error(`Delete refused. Duplicate skill records are still referenced by bundles: ${references.join(', ')}. Inspect or update bundles before deleting.`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
for (const id of plan.duplicateIds) deleteDuplicate(shellPaths, memoryRegistry, domain, id);
|
|
458
|
+
const receipt: LearningConsolidationReceipt = {
|
|
459
|
+
id: receiptId(candidate, 'delete'),
|
|
460
|
+
createdAt: new Date().toISOString(),
|
|
461
|
+
domain,
|
|
462
|
+
candidateId: candidate.id,
|
|
463
|
+
phase: 'delete',
|
|
464
|
+
explicitUserRequest,
|
|
465
|
+
survivorId: plan.survivorId,
|
|
466
|
+
duplicateIds: plan.duplicateIds,
|
|
467
|
+
beforeDuplicates,
|
|
468
|
+
};
|
|
469
|
+
writeReceipt(shellPaths, receipt);
|
|
470
|
+
return {
|
|
471
|
+
status: 'applied',
|
|
472
|
+
phase: 'delete',
|
|
473
|
+
candidateId: candidate.id,
|
|
474
|
+
survivorId: plan.survivorId,
|
|
475
|
+
deletedDuplicateIds: plan.duplicateIds,
|
|
476
|
+
receipt: receiptSummary(receipt, { shellPaths, memoryRegistry }),
|
|
477
|
+
recreateGuidance: recreateGuidance(shellPaths, memoryRegistry, receipt),
|
|
478
|
+
next: 'Deletion is intentionally last. The receipt stores deleted record snapshots and exposes a separate confirmed recreate route when exact ids are still safe.',
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function applyRecreate(
|
|
483
|
+
shellPaths: ShellPathService,
|
|
484
|
+
memoryRegistry: MemoryRegistry,
|
|
485
|
+
args: AgentLearningConsolidationToolArgs,
|
|
486
|
+
explicitUserRequest: string,
|
|
487
|
+
): Promise<Record<string, unknown>> {
|
|
488
|
+
const deleteReceipt = findDeleteReceipt(shellPaths, args);
|
|
489
|
+
if (deleteReceipt.phase !== 'delete') {
|
|
490
|
+
throw new Error(`Recreate requires a delete receipt. Receipt ${deleteReceipt.id} is phase ${deleteReceipt.phase}.`);
|
|
491
|
+
}
|
|
492
|
+
const guidance = recreateGuidance(shellPaths, memoryRegistry, deleteReceipt) as {
|
|
493
|
+
readonly records?: readonly {
|
|
494
|
+
readonly previousId?: string;
|
|
495
|
+
readonly expectedId?: string;
|
|
496
|
+
readonly exactId?: { readonly possible?: boolean; readonly reason?: string };
|
|
497
|
+
}[];
|
|
498
|
+
} | null;
|
|
499
|
+
const records = guidance?.records ?? [];
|
|
500
|
+
const unsafe = records.filter((record) => record.exactId?.possible !== true);
|
|
501
|
+
if (unsafe.length > 0) {
|
|
502
|
+
throw new Error(`Exact-id recreate refused. ${unsafe.map((record) => `${record.previousId ?? '(unknown)'}: ${record.exactId?.reason ?? 'not currently safe'}`).join('; ')}`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (deleteReceipt.domain === 'memory') {
|
|
506
|
+
const memoryRecords = deleteReceipt.beforeDuplicates.map(memoryRecordFromSnapshot);
|
|
507
|
+
const conflicts = memoryRecords.filter((record) => memoryRegistry.get(record.id)).map((record) => record.id);
|
|
508
|
+
if (conflicts.length > 0) throw new Error(`Exact-id recreate refused. Current memory records already use: ${conflicts.join(', ')}.`);
|
|
509
|
+
const bundle: MemoryBundle = {
|
|
510
|
+
schemaVersion: 'v1',
|
|
511
|
+
exportedAt: Date.now(),
|
|
512
|
+
scope: 'all',
|
|
513
|
+
recordCount: memoryRecords.length,
|
|
514
|
+
linkCount: 0,
|
|
515
|
+
records: memoryRecords,
|
|
516
|
+
links: [],
|
|
517
|
+
};
|
|
518
|
+
const importResult = await memoryRegistry.importBundle(bundle);
|
|
519
|
+
const receipt: LearningConsolidationReceipt = {
|
|
520
|
+
id: receiptIdFromReceipt(deleteReceipt, 'recreate'),
|
|
521
|
+
createdAt: new Date().toISOString(),
|
|
522
|
+
domain: deleteReceipt.domain,
|
|
523
|
+
candidateId: deleteReceipt.candidateId,
|
|
524
|
+
phase: 'recreate',
|
|
525
|
+
explicitUserRequest,
|
|
526
|
+
survivorId: deleteReceipt.survivorId,
|
|
527
|
+
duplicateIds: deleteReceipt.duplicateIds,
|
|
528
|
+
beforeDuplicates: deleteReceipt.beforeDuplicates,
|
|
529
|
+
};
|
|
530
|
+
writeReceipt(shellPaths, receipt);
|
|
531
|
+
return {
|
|
532
|
+
status: 'applied',
|
|
533
|
+
phase: 'recreate',
|
|
534
|
+
recreatedFromReceiptId: deleteReceipt.id,
|
|
535
|
+
recreatedIds: memoryRecords.map((record) => record.id),
|
|
536
|
+
exactIdsPreserved: importResult.importedRecords === memoryRecords.length,
|
|
537
|
+
importResult,
|
|
538
|
+
receipt: receiptSummary(receipt, { shellPaths, memoryRegistry }),
|
|
539
|
+
next: 'Inspect recreated memory records, then re-run the learning curator before applying another consolidation phase.',
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const recreated: Record<string, unknown>[] = [];
|
|
544
|
+
for (const snapshot of deleteReceipt.beforeDuplicates) {
|
|
545
|
+
const previousId = stringField(snapshot, 'id');
|
|
546
|
+
if (!previousId) throw new Error('Delete receipt snapshot is missing id.');
|
|
547
|
+
if (deleteReceipt.domain === 'persona') {
|
|
548
|
+
const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
|
|
549
|
+
const created = registry.create({
|
|
550
|
+
name: stringField(snapshot, 'name') ?? '',
|
|
551
|
+
description: stringField(snapshot, 'description') ?? '',
|
|
552
|
+
body: stringField(snapshot, 'body') ?? '',
|
|
553
|
+
tags: arrayField(snapshot, 'tags'),
|
|
554
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
555
|
+
source: localRecordSource(snapshot),
|
|
556
|
+
provenance: stringField(snapshot, 'provenance') ?? `recreated-from-learning-consolidation:${deleteReceipt.id}`,
|
|
557
|
+
});
|
|
558
|
+
if (created.id !== previousId) throw new Error(`Exact-id recreate failed for ${previousId}; created ${created.id}.`);
|
|
559
|
+
if (stringField(snapshot, 'reviewState') !== 'fresh') restoreReviewState(shellPaths, memoryRegistry, deleteReceipt.domain, created.id, snapshot);
|
|
560
|
+
recreated.push(cloneRecord(getRecord(shellPaths, memoryRegistry, deleteReceipt.domain, created.id)));
|
|
561
|
+
} else if (deleteReceipt.domain === 'skill') {
|
|
562
|
+
const registry = AgentSkillRegistry.fromShellPaths(shellPaths);
|
|
563
|
+
const created = registry.create({
|
|
564
|
+
name: stringField(snapshot, 'name') ?? '',
|
|
565
|
+
description: stringField(snapshot, 'description') ?? '',
|
|
566
|
+
procedure: stringField(snapshot, 'procedure') ?? '',
|
|
567
|
+
tags: arrayField(snapshot, 'tags'),
|
|
568
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
569
|
+
requirements: requirementsFromSnapshot(snapshot),
|
|
570
|
+
enabled: booleanField(snapshot, 'enabled') === true,
|
|
571
|
+
source: localRecordSource(snapshot),
|
|
572
|
+
provenance: stringField(snapshot, 'provenance') ?? `recreated-from-learning-consolidation:${deleteReceipt.id}`,
|
|
573
|
+
});
|
|
574
|
+
if (created.id !== previousId) throw new Error(`Exact-id recreate failed for ${previousId}; created ${created.id}.`);
|
|
575
|
+
if (stringField(snapshot, 'reviewState') !== 'fresh') restoreReviewState(shellPaths, memoryRegistry, deleteReceipt.domain, created.id, snapshot);
|
|
576
|
+
recreated.push(cloneRecord(getRecord(shellPaths, memoryRegistry, deleteReceipt.domain, created.id)));
|
|
577
|
+
} else {
|
|
578
|
+
const registry = AgentRoutineRegistry.fromShellPaths(shellPaths);
|
|
579
|
+
const created = registry.create({
|
|
580
|
+
name: stringField(snapshot, 'name') ?? '',
|
|
581
|
+
description: stringField(snapshot, 'description') ?? '',
|
|
582
|
+
steps: stringField(snapshot, 'steps') ?? '',
|
|
583
|
+
tags: arrayField(snapshot, 'tags'),
|
|
584
|
+
triggers: arrayField(snapshot, 'triggers'),
|
|
585
|
+
requirements: requirementsFromSnapshot(snapshot),
|
|
586
|
+
enabled: booleanField(snapshot, 'enabled') === true,
|
|
587
|
+
source: localRecordSource(snapshot),
|
|
588
|
+
provenance: stringField(snapshot, 'provenance') ?? `recreated-from-learning-consolidation:${deleteReceipt.id}`,
|
|
589
|
+
});
|
|
590
|
+
if (created.id !== previousId) throw new Error(`Exact-id recreate failed for ${previousId}; created ${created.id}.`);
|
|
591
|
+
if (stringField(snapshot, 'reviewState') !== 'fresh') restoreReviewState(shellPaths, memoryRegistry, deleteReceipt.domain, created.id, snapshot);
|
|
592
|
+
recreated.push(cloneRecord(getRecord(shellPaths, memoryRegistry, deleteReceipt.domain, created.id)));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const receipt: LearningConsolidationReceipt = {
|
|
597
|
+
id: receiptIdFromReceipt(deleteReceipt, 'recreate'),
|
|
598
|
+
createdAt: new Date().toISOString(),
|
|
599
|
+
domain: deleteReceipt.domain,
|
|
600
|
+
candidateId: deleteReceipt.candidateId,
|
|
601
|
+
phase: 'recreate',
|
|
602
|
+
explicitUserRequest,
|
|
603
|
+
survivorId: deleteReceipt.survivorId,
|
|
604
|
+
duplicateIds: deleteReceipt.duplicateIds,
|
|
605
|
+
beforeDuplicates: deleteReceipt.beforeDuplicates,
|
|
606
|
+
};
|
|
607
|
+
writeReceipt(shellPaths, receipt);
|
|
608
|
+
return {
|
|
609
|
+
status: 'applied',
|
|
610
|
+
phase: 'recreate',
|
|
611
|
+
recreatedFromReceiptId: deleteReceipt.id,
|
|
612
|
+
recreatedIds: recreated.map((record) => stringField(record, 'id')).filter(Boolean),
|
|
613
|
+
exactIdsPreserved: true,
|
|
614
|
+
recreatedRecords: recreated,
|
|
615
|
+
receipt: receiptSummary(receipt, { shellPaths, memoryRegistry }),
|
|
616
|
+
next: 'Inspect recreated records, then re-run the learning curator before applying another consolidation phase.',
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function findReceipt(shellPaths: ShellPathService, args: AgentLearningConsolidationToolArgs): LearningConsolidationReceipt {
|
|
621
|
+
const receiptIdArg = readString(args.receiptId);
|
|
622
|
+
const candidateId = readString(args.candidateId);
|
|
623
|
+
const receipts = readReceiptFile(shellPaths).receipts;
|
|
624
|
+
const receipt = receiptIdArg
|
|
625
|
+
? receipts.find((entry) => entry.id === receiptIdArg)
|
|
626
|
+
: receipts.find((entry) => entry.candidateId === candidateId && entry.phase !== 'delete' && entry.phase !== 'recreate');
|
|
627
|
+
if (!receipt) throw new Error(receiptIdArg ? `Unknown learning consolidation receipt ${receiptIdArg}` : 'rollback requires receiptId or candidateId with a non-delete receipt.');
|
|
628
|
+
return receipt;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function findDeleteReceipt(shellPaths: ShellPathService, args: AgentLearningConsolidationToolArgs): LearningConsolidationReceipt {
|
|
632
|
+
const receiptIdArg = readString(args.receiptId);
|
|
633
|
+
const candidateId = readString(args.candidateId);
|
|
634
|
+
const receipts = readReceiptFile(shellPaths).receipts;
|
|
635
|
+
const receipt = receiptIdArg
|
|
636
|
+
? receipts.find((entry) => entry.id === receiptIdArg)
|
|
637
|
+
: receipts.find((entry) => entry.candidateId === candidateId && entry.phase === 'delete');
|
|
638
|
+
if (!receipt) throw new Error(receiptIdArg ? `Unknown learning consolidation receipt ${receiptIdArg}` : 'recreate requires receiptId or candidateId with a delete receipt.');
|
|
639
|
+
return receipt;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function applyRollback(
|
|
643
|
+
shellPaths: ShellPathService,
|
|
644
|
+
memoryRegistry: MemoryRegistry,
|
|
645
|
+
args: AgentLearningConsolidationToolArgs,
|
|
646
|
+
): Record<string, unknown> {
|
|
647
|
+
const receipt = findReceipt(shellPaths, args);
|
|
648
|
+
if (receipt.phase === 'delete') {
|
|
649
|
+
throw new Error(`Receipt ${receipt.id} is from a delete phase. Automatic rollback is unavailable after delete; use agent_learning_consolidation mode:"recreate" receiptId:"${receipt.id}" confirm:true explicitUserRequest:"..." to recreate deleted records only when the user asks and exact ids are still safe.`);
|
|
650
|
+
}
|
|
651
|
+
if (receipt.phase === 'merge') {
|
|
652
|
+
return {
|
|
653
|
+
status: 'applied',
|
|
654
|
+
phase: 'rollback',
|
|
655
|
+
rollbackOf: receiptSummary(receipt),
|
|
656
|
+
restoredSurvivor: restoreSurvivor(shellPaths, memoryRegistry, receipt),
|
|
657
|
+
next: 'Re-run the learning curator and inspect the candidate before applying another consolidation phase.',
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
for (const snapshot of receipt.beforeDuplicates) {
|
|
661
|
+
const id = stringField(snapshot, 'id');
|
|
662
|
+
if (!id) continue;
|
|
663
|
+
restoreReviewState(shellPaths, memoryRegistry, receipt.domain, id, snapshot);
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
status: 'applied',
|
|
667
|
+
phase: 'rollback',
|
|
668
|
+
rollbackOf: receiptSummary(receipt),
|
|
669
|
+
restoredDuplicateIds: receipt.beforeDuplicates.map((snapshot) => stringField(snapshot, 'id')).filter(Boolean),
|
|
670
|
+
next: 'Re-run the learning curator and inspect the candidate before applying another consolidation phase.',
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export function createAgentLearningConsolidationTool(shellPaths: ShellPathService, memoryRegistry: MemoryRegistry): Tool {
|
|
675
|
+
return {
|
|
676
|
+
definition: {
|
|
677
|
+
name: 'agent_learning_consolidation',
|
|
678
|
+
description: 'Manage confirmed local duplicate learning phases.',
|
|
679
|
+
parameters: {
|
|
680
|
+
type: 'object',
|
|
681
|
+
properties: {
|
|
682
|
+
mode: {
|
|
683
|
+
type: 'string',
|
|
684
|
+
enum: [...MODES],
|
|
685
|
+
description: 'preview, merge, stale, delete, rollback, recreate, or receipts.',
|
|
686
|
+
},
|
|
687
|
+
candidateId: {
|
|
688
|
+
type: 'string',
|
|
689
|
+
description: 'Learning curator duplicate-consolidation candidate id.',
|
|
690
|
+
},
|
|
691
|
+
query: {
|
|
692
|
+
type: 'string',
|
|
693
|
+
description: 'Candidate search text when candidateId is unknown.',
|
|
694
|
+
},
|
|
695
|
+
receiptId: {
|
|
696
|
+
type: 'string',
|
|
697
|
+
description: 'Receipt id for rollback.',
|
|
698
|
+
},
|
|
699
|
+
limit: {
|
|
700
|
+
type: 'number',
|
|
701
|
+
description: 'Receipt list limit.',
|
|
702
|
+
},
|
|
703
|
+
confirm: {
|
|
704
|
+
type: 'boolean',
|
|
705
|
+
description: 'Required true for merge, stale, delete, and rollback.',
|
|
706
|
+
},
|
|
707
|
+
explicitUserRequest: {
|
|
708
|
+
type: 'string',
|
|
709
|
+
description: 'Exact user request or faithful short summary authorizing the phase.',
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
required: ['mode'],
|
|
713
|
+
additionalProperties: false,
|
|
714
|
+
},
|
|
715
|
+
sideEffects: ['state'],
|
|
716
|
+
},
|
|
717
|
+
execute: async (rawArgs: unknown) => {
|
|
718
|
+
try {
|
|
719
|
+
const args = rawArgs as AgentLearningConsolidationToolArgs;
|
|
720
|
+
const mode = readMode(args.mode);
|
|
721
|
+
if (mode === 'receipts') {
|
|
722
|
+
return output({
|
|
723
|
+
status: 'receipts',
|
|
724
|
+
path: receiptPath(shellPaths),
|
|
725
|
+
receipts: readReceiptFile(shellPaths).receipts
|
|
726
|
+
.slice(0, readLimit(args.limit, 20))
|
|
727
|
+
.map((receipt) => receiptSummary(receipt, { shellPaths, memoryRegistry })),
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
if (mode === 'rollback') {
|
|
731
|
+
requireConfirmedWrite(args, mode);
|
|
732
|
+
return output(applyRollback(shellPaths, memoryRegistry, args));
|
|
733
|
+
}
|
|
734
|
+
if (mode === 'recreate') {
|
|
735
|
+
const explicitUserRequest = requireConfirmedWrite(args, mode);
|
|
736
|
+
return output(await applyRecreate(shellPaths, memoryRegistry, args, explicitUserRequest));
|
|
737
|
+
}
|
|
738
|
+
const candidate = resolveCandidate(shellPaths, memoryRegistry, args);
|
|
739
|
+
if (mode === 'preview') return output(previewCandidate(candidate));
|
|
740
|
+
if (!WRITE_MODES.has(mode)) return failure(`Unknown mode. Valid values ${MODES.join(', ')}.`);
|
|
741
|
+
const explicitUserRequest = requireConfirmedWrite(args, mode);
|
|
742
|
+
if (mode === 'merge') return output(applyMerge(shellPaths, memoryRegistry, candidate, explicitUserRequest));
|
|
743
|
+
if (mode === 'stale') return output(applyStale(shellPaths, memoryRegistry, candidate, explicitUserRequest));
|
|
744
|
+
return output(applyDelete(shellPaths, memoryRegistry, candidate, explicitUserRequest));
|
|
745
|
+
} catch (error) {
|
|
746
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export function registerAgentLearningConsolidationTool(
|
|
753
|
+
registry: ToolRegistry,
|
|
754
|
+
shellPaths: ShellPathService,
|
|
755
|
+
memoryRegistry: MemoryRegistry,
|
|
756
|
+
): void {
|
|
757
|
+
registry.register(createAgentLearningConsolidationTool(shellPaths, memoryRegistry));
|
|
758
|
+
}
|