@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.
- package/CHANGELOG.md +329 -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 +138903 -91477
- 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 +58 -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 +116 -45
- package/docs/voice-and-live-tts.md +3 -1
- package/package.json +3 -2
- package/release/release-notes.md +142 -5
- package/release/release-readiness.json +124 -94
- 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 +294 -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-artifact-receipts.ts +366 -0
- package/src/agent/setup-wizard-checkpoint.ts +140 -0
- package/src/agent/setup-wizard.ts +683 -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 +313 -30
- package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
- package/src/input/agent-workspace-setup-snapshot.ts +217 -0
- package/src/input/agent-workspace-setup.ts +61 -1
- package/src/input/agent-workspace-snapshot.ts +184 -149
- package/src/input/agent-workspace-types.ts +287 -2
- package/src/input/agent-workspace.ts +23 -1
- 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/input/setup-wizard-live-receipts.ts +76 -0
- package/src/main.ts +15 -15
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/agent-workspace-context-lines.ts +701 -0
- package/src/renderer/agent-workspace.ts +68 -421
- 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 +39 -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 +199 -5
- 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 +137 -7
- 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-policy.ts +75 -0
- package/src/tools/agent-harness-agent-orchestration.ts +791 -0
- package/src/tools/agent-harness-autonomy-intake.ts +785 -0
- package/src/tools/agent-harness-autonomy-live-records.ts +742 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
- package/src/tools/agent-harness-autonomy-queue.ts +571 -0
- package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
- package/src/tools/agent-harness-background-processes-types.ts +35 -0
- package/src/tools/agent-harness-background-processes.ts +798 -0
- package/src/tools/agent-harness-browser-cockpit-route.ts +373 -0
- package/src/tools/agent-harness-browser-control.ts +346 -0
- package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -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-device-live-read-models.ts +366 -0
- 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 +385 -0
- package/src/tools/agent-harness-file-recovery.ts +135 -0
- package/src/tools/agent-harness-interactive-runtime-records.ts +421 -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 +269 -0
- package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
- package/src/tools/agent-harness-local-model-endpoints.ts +788 -0
- package/src/tools/agent-harness-local-model-smoke.ts +277 -0
- package/src/tools/agent-harness-local-model-url.ts +78 -0
- package/src/tools/agent-harness-media-posture.ts +223 -2
- package/src/tools/agent-harness-memory-external-providers.ts +796 -0
- package/src/tools/agent-harness-memory-posture.ts +672 -0
- package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
- package/src/tools/agent-harness-memory-refinement.ts +340 -0
- package/src/tools/agent-harness-metadata.ts +100 -168
- package/src/tools/agent-harness-mode-catalog.ts +86 -33
- package/src/tools/agent-harness-model-catalog.ts +162 -0
- package/src/tools/agent-harness-model-provider-health.ts +283 -0
- package/src/tools/agent-harness-model-readiness.ts +432 -0
- package/src/tools/agent-harness-model-routing-types.ts +327 -0
- package/src/tools/agent-harness-model-routing-utils.ts +30 -0
- package/src/tools/agent-harness-model-routing.ts +165 -193
- package/src/tools/agent-harness-operator-methods.ts +115 -133
- package/src/tools/agent-harness-pairing-posture.ts +452 -18
- package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
- 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 +270 -0
- package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
- package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
- package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
- package/src/tools/agent-harness-personal-ops-records.ts +684 -0
- package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
- package/src/tools/agent-harness-personal-ops-types.ts +240 -0
- package/src/tools/agent-harness-personal-ops.ts +613 -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-remote-read-models.ts +541 -0
- package/src/tools/agent-harness-research-briefing.ts +446 -0
- package/src/tools/agent-harness-research-live-read-models.ts +500 -0
- package/src/tools/agent-harness-research-queue.ts +250 -0
- package/src/tools/agent-harness-research-runs.ts +307 -0
- package/src/tools/agent-harness-research-workflow.ts +395 -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 +102 -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 +533 -0
- package/src/tools/agent-harness-sudo-posture.ts +114 -0
- package/src/tools/agent-harness-tool-schema.ts +142 -7
- package/src/tools/agent-harness-tool-types.ts +83 -0
- package/src/tools/agent-harness-tool-utils.ts +43 -0
- package/src/tools/agent-harness-tool.ts +319 -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 +253 -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 +735 -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-runner.ts +367 -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 +515 -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 +106 -85
- 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/version.ts +1 -1
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
import type { ArtifactDescriptor } from '@pellux/goodvibes-sdk/platform/artifacts';
|
|
2
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
3
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
4
|
+
import { memoryExternalProviderLiveCertification, memoryExternalProviderReceiptCertification } from './agent-harness-memory-provider-certification.ts';
|
|
5
|
+
import type { MemoryExternalProviderCertification } from './agent-harness-memory-provider-certification.ts';
|
|
6
|
+
|
|
7
|
+
export type MemoryExternalProviderStatus = 'not-published' | 'receipt-evidence-found' | 'available' | 'needs-setup' | 'blocked' | 'error' | 'disabled' | 'unknown';
|
|
8
|
+
export type MemoryExternalProviderSetupStatus = 'contract-needed' | 'receipt-evidence-found' | 'ready' | 'needs-setup' | 'blocked' | 'error' | 'disabled';
|
|
9
|
+
export type MemoryExternalProviderContractStatus = 'missing' | 'artifact-evidence-found' | 'published';
|
|
10
|
+
|
|
11
|
+
interface ArtifactListLike {
|
|
12
|
+
readonly list?: (limit?: number) => readonly ArtifactDescriptor[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MemoryPostureProvider {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly label: string;
|
|
18
|
+
readonly kind: 'embedding' | 'external-memory';
|
|
19
|
+
readonly status: string;
|
|
20
|
+
readonly summary: string;
|
|
21
|
+
readonly modelRoute: string;
|
|
22
|
+
readonly setupRoute?: string;
|
|
23
|
+
readonly configured?: boolean;
|
|
24
|
+
readonly active?: boolean;
|
|
25
|
+
readonly dimensions?: number;
|
|
26
|
+
readonly deterministic?: boolean;
|
|
27
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
28
|
+
readonly liveRecord?: MemoryExternalProviderLiveRecord;
|
|
29
|
+
readonly setupGuide?: MemoryExternalProviderSetupGuide;
|
|
30
|
+
readonly latestReceipt?: MemoryExternalProviderReceiptEvidence;
|
|
31
|
+
readonly receiptEvidence?: readonly MemoryExternalProviderReceiptEvidence[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface MemoryExternalProviderSetupGuide {
|
|
35
|
+
readonly status: MemoryExternalProviderSetupStatus;
|
|
36
|
+
readonly userOutcome: string;
|
|
37
|
+
readonly currentState: string;
|
|
38
|
+
readonly safeFirstStep: string;
|
|
39
|
+
readonly inspectRoutes: readonly string[];
|
|
40
|
+
readonly nextRoutes: readonly MemoryExternalProviderRoute[];
|
|
41
|
+
readonly contractChecklist: readonly MemoryExternalProviderContractCheck[];
|
|
42
|
+
readonly receiptContract: MemoryExternalProviderReceiptContract;
|
|
43
|
+
readonly latestReceipt?: MemoryExternalProviderReceiptEvidence;
|
|
44
|
+
readonly receiptHistory?: readonly MemoryExternalProviderReceiptEvidence[];
|
|
45
|
+
readonly requiredHostContracts: readonly string[];
|
|
46
|
+
readonly credentialPolicy: string;
|
|
47
|
+
readonly confirmationPolicy: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface MemoryExternalProviderRoute {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly label: string;
|
|
53
|
+
readonly modelRoute: string;
|
|
54
|
+
readonly effect: 'read-only' | 'confirmed';
|
|
55
|
+
readonly why: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface MemoryExternalProviderContractCheck {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly label: string;
|
|
61
|
+
readonly status: MemoryExternalProviderContractStatus;
|
|
62
|
+
readonly requiredFor: string;
|
|
63
|
+
readonly owner: 'goodvibes-sdk-or-daemon';
|
|
64
|
+
readonly inspectRoute: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface MemoryExternalProviderReceiptContract {
|
|
68
|
+
readonly status: MemoryExternalProviderContractStatus;
|
|
69
|
+
readonly appliesTo: readonly string[];
|
|
70
|
+
readonly requiredFields: readonly string[];
|
|
71
|
+
readonly nextWhenPublished: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface MemoryExternalProviderLiveRecord {
|
|
75
|
+
readonly providerId: string;
|
|
76
|
+
readonly label: string | null;
|
|
77
|
+
readonly status: MemoryExternalProviderStatus;
|
|
78
|
+
readonly source: string;
|
|
79
|
+
readonly configured: boolean | null;
|
|
80
|
+
readonly reachable: boolean | null;
|
|
81
|
+
readonly credentialState: string | null;
|
|
82
|
+
readonly readReady: boolean | null;
|
|
83
|
+
readonly writeReady: boolean | null;
|
|
84
|
+
readonly syncReady: boolean | null;
|
|
85
|
+
readonly forgetReady: boolean | null;
|
|
86
|
+
readonly promptEligible: boolean | null;
|
|
87
|
+
readonly setupRoute: string | null;
|
|
88
|
+
readonly readRoute: string | null;
|
|
89
|
+
readonly writeRoute: string | null;
|
|
90
|
+
readonly syncRoute: string | null;
|
|
91
|
+
readonly forgetRoute: string | null;
|
|
92
|
+
readonly receiptRoute: string | null;
|
|
93
|
+
readonly receiptIds: readonly string[];
|
|
94
|
+
readonly sourceCount: number | null;
|
|
95
|
+
readonly recordCount: number | null;
|
|
96
|
+
readonly redaction: string | null;
|
|
97
|
+
readonly failureReason: string | null;
|
|
98
|
+
readonly lastReadAt: string | null;
|
|
99
|
+
readonly lastWriteAt: string | null;
|
|
100
|
+
readonly lastSyncAt: string | null;
|
|
101
|
+
readonly updatedAt: string | null;
|
|
102
|
+
readonly inspectRoute: string;
|
|
103
|
+
readonly certification?: MemoryExternalProviderCertification;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface MemoryExternalProviderReceiptEvidence {
|
|
107
|
+
readonly providerId: string;
|
|
108
|
+
readonly artifactId: string;
|
|
109
|
+
readonly filename: string | null;
|
|
110
|
+
readonly operation: string;
|
|
111
|
+
readonly status: string;
|
|
112
|
+
readonly createdAt: string | null;
|
|
113
|
+
readonly sourceCount: number | null;
|
|
114
|
+
readonly redaction: string | null;
|
|
115
|
+
readonly failureReason: string | null;
|
|
116
|
+
readonly nextRoute: string;
|
|
117
|
+
readonly inspectRoute: string;
|
|
118
|
+
readonly correlationId: string | null;
|
|
119
|
+
readonly certification?: MemoryExternalProviderCertification;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ExternalMemoryProviderCatalogEntry {
|
|
123
|
+
readonly id: string;
|
|
124
|
+
readonly label: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type MemoryProviderResolution =
|
|
128
|
+
| { readonly status: 'found'; readonly provider: Record<string, unknown> }
|
|
129
|
+
| { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
|
|
130
|
+
| { readonly status: 'missing_lookup'; readonly usage: string };
|
|
131
|
+
|
|
132
|
+
const EXTERNAL_MEMORY_PROVIDERS: readonly ExternalMemoryProviderCatalogEntry[] = [
|
|
133
|
+
{ id: 'honcho', label: 'Honcho' },
|
|
134
|
+
{ id: 'openviking', label: 'OpenViking' },
|
|
135
|
+
{ id: 'mem0', label: 'Mem0' },
|
|
136
|
+
{ id: 'hindsight', label: 'Hindsight' },
|
|
137
|
+
{ id: 'holographic', label: 'Holographic' },
|
|
138
|
+
{ id: 'retaindb', label: 'RetainDB' },
|
|
139
|
+
{ id: 'byterover', label: 'ByteRover' },
|
|
140
|
+
{ id: 'supermemory', label: 'Supermemory' },
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
export const EXTERNAL_MEMORY_REQUIRED_CONTRACTS = [
|
|
144
|
+
'Certified schema/version/publication/publisher/provenance evidence for provider records and receipts.',
|
|
145
|
+
'Provider status/readiness record with stable provider id.',
|
|
146
|
+
'Credential reference or setup state that never returns raw secret values.',
|
|
147
|
+
'Bounded read/search route with redaction and source provenance.',
|
|
148
|
+
'Explicit write/upsert/import route with confirmation and durable receipt.',
|
|
149
|
+
'Forget/delete/disable route or an explicit not-supported contract.',
|
|
150
|
+
'Sync/export/import receipts with timestamps and failure reasons.',
|
|
151
|
+
'Prompt-injection eligibility policy for what may enter the Agent prompt.',
|
|
152
|
+
] as const;
|
|
153
|
+
|
|
154
|
+
export const EXTERNAL_MEMORY_RECEIPT_FIELDS = [
|
|
155
|
+
'receiptId',
|
|
156
|
+
'providerId',
|
|
157
|
+
'operation',
|
|
158
|
+
'status',
|
|
159
|
+
'createdAt',
|
|
160
|
+
'sourceCount',
|
|
161
|
+
'redaction',
|
|
162
|
+
'failureReason',
|
|
163
|
+
'nextRoute',
|
|
164
|
+
'schemaVersion',
|
|
165
|
+
'publicationGuarantee',
|
|
166
|
+
'publisher',
|
|
167
|
+
'provenance',
|
|
168
|
+
] as const;
|
|
169
|
+
|
|
170
|
+
const EXTERNAL_MEMORY_RECEIPT_PURPOSES = new Set([
|
|
171
|
+
'agent-memory-provider-receipt',
|
|
172
|
+
'connected-host-memory-provider-receipt',
|
|
173
|
+
'external-memory-provider-receipt',
|
|
174
|
+
'goodvibes-memory-provider-receipt',
|
|
175
|
+
'memory-provider-receipt',
|
|
176
|
+
'external-memory-receipt',
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
function readString(value: unknown): string {
|
|
180
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function readNumber(value: unknown): number | null {
|
|
184
|
+
const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
|
|
185
|
+
return typeof parsed === 'number' && Number.isFinite(parsed) ? Math.max(0, Math.trunc(parsed)) : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readRecord(value: unknown): Record<string, unknown> {
|
|
189
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function readBooleanLike(value: unknown): boolean | null {
|
|
193
|
+
if (typeof value === 'boolean') return value;
|
|
194
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
195
|
+
if (value === 1) return true;
|
|
196
|
+
if (value === 0) return false;
|
|
197
|
+
}
|
|
198
|
+
const raw = readString(value).toLowerCase();
|
|
199
|
+
if (['true', 'yes', 'y', '1', 'on', 'enabled', 'available', 'ready', 'configured', 'connected', 'healthy', 'supported'].includes(raw)) return true;
|
|
200
|
+
if (['false', 'no', 'n', '0', 'off', 'disabled', 'unavailable', 'not-configured', 'not_configured', 'missing', 'blocked'].includes(raw)) return false;
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function readLimit(value: unknown, fallback: number): number {
|
|
205
|
+
const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
|
|
206
|
+
if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
|
|
207
|
+
return Math.max(1, Math.min(200, Math.trunc(parsed)));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function readTimestamp(value: unknown): string | null {
|
|
211
|
+
if (typeof value === 'number' && Number.isFinite(value)) return new Date(value).toISOString();
|
|
212
|
+
const raw = readString(value);
|
|
213
|
+
if (!raw) return null;
|
|
214
|
+
const parsed = Date.parse(raw);
|
|
215
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const EXTERNAL_MEMORY_SECRET_PATTERNS: readonly [RegExp, string][] = [
|
|
219
|
+
[/("?\b(?:api[-_]?key|apikey|token|secret|password|passwd|credential|authorization)\b"?\s*:\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1"<redacted>"'],
|
|
220
|
+
[/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTHORIZATION|BEARER)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi, '$1=<redacted>'],
|
|
221
|
+
[/(\b(?:token|secret|password|passwd|api[-_]?key|apikey|authorization|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1<redacted>'],
|
|
222
|
+
[/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>'],
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
function sanitizeExternalProviderText(value: unknown, limit = 160): string | null {
|
|
226
|
+
const raw = readString(value);
|
|
227
|
+
if (!raw) return null;
|
|
228
|
+
const redacted = EXTERNAL_MEMORY_SECRET_PATTERNS.reduce((text, [pattern, replacement]) => text.replace(pattern, replacement), raw);
|
|
229
|
+
return previewHarnessText(redacted, limit);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function titleFromProviderId(providerId: string): string {
|
|
233
|
+
const known = EXTERNAL_MEMORY_PROVIDERS.find((provider) => provider.id === providerId);
|
|
234
|
+
if (known) return known.label;
|
|
235
|
+
return providerId
|
|
236
|
+
.replace(/[-_]+/g, ' ')
|
|
237
|
+
.replace(/\b\w/g, (letter) => letter.toUpperCase())
|
|
238
|
+
.trim() || providerId;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function readRouteValue(value: unknown): string {
|
|
242
|
+
const direct = readString(value);
|
|
243
|
+
if (direct) return direct;
|
|
244
|
+
const record = readRecord(value);
|
|
245
|
+
return readString(record.modelRoute)
|
|
246
|
+
|| readString(record.route)
|
|
247
|
+
|| readString(record.command)
|
|
248
|
+
|| readString(record.action)
|
|
249
|
+
|| readString(record.href)
|
|
250
|
+
|| readString(record.url);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function upperCamel(value: string): string {
|
|
254
|
+
return value ? `${value[0]!.toUpperCase()}${value.slice(1)}` : value;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function readLiveRoute(record: Record<string, unknown>, aliases: readonly string[]): string | null {
|
|
258
|
+
const containers = [
|
|
259
|
+
record,
|
|
260
|
+
readRecord(record.routes),
|
|
261
|
+
readRecord(record.modelRoutes),
|
|
262
|
+
readRecord(record.actions),
|
|
263
|
+
readRecord(record.commands),
|
|
264
|
+
readRecord(record.links),
|
|
265
|
+
];
|
|
266
|
+
for (const alias of aliases) {
|
|
267
|
+
for (const container of containers) {
|
|
268
|
+
const route = readRouteValue(container[`${alias}Route`])
|
|
269
|
+
|| readRouteValue(container[alias])
|
|
270
|
+
|| readRouteValue(container[`${alias}Command`])
|
|
271
|
+
|| readRouteValue(container[`${alias}Action`]);
|
|
272
|
+
if (route) return route;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function readCapabilityState(value: unknown): boolean | null {
|
|
279
|
+
const direct = readBooleanLike(value);
|
|
280
|
+
if (direct !== null) return direct;
|
|
281
|
+
const record = readRecord(value);
|
|
282
|
+
return readBooleanLike(record.ready)
|
|
283
|
+
?? readBooleanLike(record.enabled)
|
|
284
|
+
?? readBooleanLike(record.available)
|
|
285
|
+
?? readBooleanLike(record.supported)
|
|
286
|
+
?? readBooleanLike(record.configured)
|
|
287
|
+
?? readBooleanLike(record.allowed)
|
|
288
|
+
?? (readRouteValue(record) ? true : null);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function readCapabilityFlag(
|
|
292
|
+
record: Record<string, unknown>,
|
|
293
|
+
aliases: readonly string[],
|
|
294
|
+
route: string | null,
|
|
295
|
+
): boolean | null {
|
|
296
|
+
const containers = [
|
|
297
|
+
record,
|
|
298
|
+
readRecord(record.capabilities),
|
|
299
|
+
readRecord(record.operations),
|
|
300
|
+
readRecord(record.features),
|
|
301
|
+
readRecord(record.supports),
|
|
302
|
+
readRecord(record.readiness),
|
|
303
|
+
];
|
|
304
|
+
let sawFalse = false;
|
|
305
|
+
for (const alias of aliases) {
|
|
306
|
+
const keys = [alias, `${alias}Ready`, `${alias}Enabled`, `${alias}Supported`, `can${upperCamel(alias)}`];
|
|
307
|
+
for (const container of containers) {
|
|
308
|
+
for (const key of keys) {
|
|
309
|
+
const state = readCapabilityState(container[key]);
|
|
310
|
+
if (state === true) return true;
|
|
311
|
+
if (state === false) sawFalse = true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (route) return true;
|
|
316
|
+
return sawFalse ? false : null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function normalizeExternalProviderCredentialState(record: Record<string, unknown>): string | null {
|
|
320
|
+
const candidate = record.credentialState
|
|
321
|
+
?? record.authState
|
|
322
|
+
?? record.authenticationState
|
|
323
|
+
?? record.credentialRef
|
|
324
|
+
?? record.secretRef
|
|
325
|
+
?? record.credentials
|
|
326
|
+
?? record.auth
|
|
327
|
+
?? record.configured;
|
|
328
|
+
const boolState = readBooleanLike(candidate);
|
|
329
|
+
if (boolState === true) return 'configured-secret-ref';
|
|
330
|
+
if (boolState === false) return 'missing';
|
|
331
|
+
const candidateRecord = readRecord(candidate);
|
|
332
|
+
if (Object.keys(candidateRecord).length > 0) {
|
|
333
|
+
const nestedState = readBooleanLike(candidateRecord.present)
|
|
334
|
+
?? readBooleanLike(candidateRecord.configured)
|
|
335
|
+
?? readBooleanLike(candidateRecord.connected)
|
|
336
|
+
?? readBooleanLike(candidateRecord.available)
|
|
337
|
+
?? readBooleanLike(candidateRecord.ready);
|
|
338
|
+
if (nestedState === true) return 'configured-secret-ref';
|
|
339
|
+
if (nestedState === false) return 'missing';
|
|
340
|
+
}
|
|
341
|
+
const raw = readString(candidate).toLowerCase();
|
|
342
|
+
if (!raw) return null;
|
|
343
|
+
if (['missing', 'none', 'not-configured', 'not_configured', 'required', 'needs-setup', 'needs_setup', 'unauthenticated'].includes(raw)) return 'missing';
|
|
344
|
+
if (raw.includes('oauth') || raw.includes('connected-host')) return 'connected-host-auth';
|
|
345
|
+
if (raw.includes('secret') || raw.includes('ref') || raw.includes('configured') || raw.includes('present') || raw.includes('available')) return 'configured-secret-ref';
|
|
346
|
+
return 'published-redacted';
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function normalizeExternalProviderLiveStatus(
|
|
350
|
+
value: unknown,
|
|
351
|
+
configured: boolean | null,
|
|
352
|
+
reachable: boolean | null,
|
|
353
|
+
enabled: boolean | null,
|
|
354
|
+
): MemoryExternalProviderStatus {
|
|
355
|
+
if (enabled === false) return 'disabled';
|
|
356
|
+
const raw = readString(value).toLowerCase().replace(/_/g, '-');
|
|
357
|
+
if (['ok', 'ready', 'healthy', 'available', 'connected', 'active', 'succeeded', 'success', 'synced'].includes(raw)) return 'available';
|
|
358
|
+
if (['needs-setup', 'setup-needed', 'not-configured', 'missing-credentials', 'auth-required', 'unauthenticated'].includes(raw)) return 'needs-setup';
|
|
359
|
+
if (['blocked', 'denied', 'forbidden', 'permission-denied'].includes(raw)) return 'blocked';
|
|
360
|
+
if (['error', 'errored', 'failed', 'failure', 'unreachable'].includes(raw)) return 'error';
|
|
361
|
+
if (['disabled', 'inactive', 'off'].includes(raw)) return 'disabled';
|
|
362
|
+
if (configured === true && reachable !== false) return 'available';
|
|
363
|
+
return raw ? 'unknown' : 'unknown';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function readExternalProviderLiveRecordId(record: Record<string, unknown>): string {
|
|
367
|
+
return normalizeExternalProviderId(
|
|
368
|
+
readString(record.providerId)
|
|
369
|
+
|| readString(record.provider)
|
|
370
|
+
|| readString(record.externalProviderId)
|
|
371
|
+
|| readString(record.memoryProviderId)
|
|
372
|
+
|| readString(record.backendId)
|
|
373
|
+
|| readString(record.id)
|
|
374
|
+
|| readString(record.key)
|
|
375
|
+
|| readString(record.name),
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function readReceiptIds(value: unknown): readonly string[] {
|
|
380
|
+
if (Array.isArray(value)) {
|
|
381
|
+
return [...new Set(value.flatMap((entry) => {
|
|
382
|
+
const direct = readString(entry);
|
|
383
|
+
if (direct) return [direct];
|
|
384
|
+
const record = readRecord(entry);
|
|
385
|
+
return [
|
|
386
|
+
readString(record.receiptId),
|
|
387
|
+
readString(record.id),
|
|
388
|
+
readString(record.artifactId),
|
|
389
|
+
].filter(Boolean);
|
|
390
|
+
}))];
|
|
391
|
+
}
|
|
392
|
+
const direct = readString(value);
|
|
393
|
+
if (!direct) return [];
|
|
394
|
+
return [...new Set(direct.split(',').map((entry) => entry.trim()).filter(Boolean))];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function liveRecordReceiptIds(record: Record<string, unknown>): readonly string[] {
|
|
398
|
+
return [
|
|
399
|
+
...readReceiptIds(record.receiptIds),
|
|
400
|
+
...readReceiptIds(record.receipts),
|
|
401
|
+
...readReceiptIds(record.latestReceiptId),
|
|
402
|
+
...readReceiptIds(record.receiptId),
|
|
403
|
+
].filter((value, index, list) => list.indexOf(value) === index);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function normalizeExternalProviderLiveRecord(value: unknown, source: string): MemoryExternalProviderLiveRecord | null {
|
|
407
|
+
const record = readRecord(value);
|
|
408
|
+
const providerId = readExternalProviderLiveRecordId(record);
|
|
409
|
+
if (!providerId) return null;
|
|
410
|
+
const label = readString(record.label) || readString(record.displayName) || readString(record.title) || titleFromProviderId(providerId);
|
|
411
|
+
const setupRoute = readLiveRoute(record, ['setup', 'configure', 'connect']);
|
|
412
|
+
const readRoute = readLiveRoute(record, ['read', 'search', 'query']);
|
|
413
|
+
const writeRoute = readLiveRoute(record, ['write', 'upsert', 'import']);
|
|
414
|
+
const syncRoute = readLiveRoute(record, ['sync', 'export']);
|
|
415
|
+
const forgetRoute = readLiveRoute(record, ['forget', 'delete', 'disable']);
|
|
416
|
+
const receiptRoute = readLiveRoute(record, ['receipt', 'receipts', 'history']);
|
|
417
|
+
const inspectRoute = readLiveRoute(record, ['inspect', 'status'])
|
|
418
|
+
?? `memory action:"provider" providerId:"${providerId}" includeParameters:true`;
|
|
419
|
+
const configured = readBooleanLike(record.configured)
|
|
420
|
+
?? readBooleanLike(record.isConfigured)
|
|
421
|
+
?? readBooleanLike(record.ready);
|
|
422
|
+
const reachable = readBooleanLike(record.reachable)
|
|
423
|
+
?? readBooleanLike(record.connected)
|
|
424
|
+
?? readBooleanLike(record.available)
|
|
425
|
+
?? readBooleanLike(record.healthy);
|
|
426
|
+
const enabled = readBooleanLike(record.enabled);
|
|
427
|
+
const readReady = readCapabilityFlag(record, ['read', 'search', 'query'], readRoute);
|
|
428
|
+
const writeReady = readCapabilityFlag(record, ['write', 'upsert', 'import'], writeRoute);
|
|
429
|
+
const syncReady = readCapabilityFlag(record, ['sync', 'export'], syncRoute);
|
|
430
|
+
const forgetReady = readCapabilityFlag(record, ['forget', 'delete', 'disable'], forgetRoute);
|
|
431
|
+
const receiptIds = liveRecordReceiptIds(record);
|
|
432
|
+
return {
|
|
433
|
+
providerId,
|
|
434
|
+
label,
|
|
435
|
+
status: normalizeExternalProviderLiveStatus(
|
|
436
|
+
record.status ?? record.state ?? record.readiness ?? record.connectionStatus ?? record.setupStatus,
|
|
437
|
+
configured,
|
|
438
|
+
reachable,
|
|
439
|
+
enabled,
|
|
440
|
+
),
|
|
441
|
+
source,
|
|
442
|
+
configured,
|
|
443
|
+
reachable,
|
|
444
|
+
credentialState: normalizeExternalProviderCredentialState(record),
|
|
445
|
+
readReady,
|
|
446
|
+
writeReady,
|
|
447
|
+
syncReady,
|
|
448
|
+
forgetReady,
|
|
449
|
+
promptEligible: readBooleanLike(record.promptEligible)
|
|
450
|
+
?? readBooleanLike(record.promptEligibility)
|
|
451
|
+
?? readBooleanLike(readRecord(record.promptPolicy).eligible),
|
|
452
|
+
setupRoute,
|
|
453
|
+
readRoute,
|
|
454
|
+
writeRoute,
|
|
455
|
+
syncRoute,
|
|
456
|
+
forgetRoute,
|
|
457
|
+
receiptRoute,
|
|
458
|
+
receiptIds,
|
|
459
|
+
sourceCount: readNumber(record.sourceCount ?? record.sourcesCount ?? readRecord(record.stats).sourceCount),
|
|
460
|
+
recordCount: readNumber(record.recordCount ?? record.memoryCount ?? record.itemCount ?? readRecord(record.stats).recordCount),
|
|
461
|
+
redaction: sanitizeExternalProviderText(record.redaction ?? record.redactionPolicy, 96),
|
|
462
|
+
failureReason: sanitizeExternalProviderText(record.failureReason ?? record.error ?? record.errorMessage ?? record.statusDetail),
|
|
463
|
+
lastReadAt: readTimestamp(record.lastReadAt ?? record.lastSearchAt),
|
|
464
|
+
lastWriteAt: readTimestamp(record.lastWriteAt ?? record.lastUpsertAt ?? record.lastImportAt),
|
|
465
|
+
lastSyncAt: readTimestamp(record.lastSyncAt ?? record.lastExportAt),
|
|
466
|
+
updatedAt: readTimestamp(record.updatedAt ?? record.checkedAt ?? record.createdAt),
|
|
467
|
+
inspectRoute,
|
|
468
|
+
certification: memoryExternalProviderLiveCertification({
|
|
469
|
+
record,
|
|
470
|
+
sourcePath: source,
|
|
471
|
+
providerId,
|
|
472
|
+
readContractPublished: readReady === true || Boolean(readRoute),
|
|
473
|
+
writeContractPublished: writeReady === true || Boolean(writeRoute),
|
|
474
|
+
syncContractPublished: syncReady === true || Boolean(syncRoute) || Boolean(receiptRoute) || receiptIds.length > 0,
|
|
475
|
+
forgetContractPublished: forgetReady !== null || Boolean(forgetRoute),
|
|
476
|
+
receiptIds,
|
|
477
|
+
receiptRoute,
|
|
478
|
+
}),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
interface ExternalMemoryProviderReadModelSource {
|
|
483
|
+
readonly path: string;
|
|
484
|
+
readonly source: unknown;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function externalMemoryReadModelSources(context: CommandContext): readonly ExternalMemoryProviderReadModelSource[] {
|
|
488
|
+
const platform = context.platform as unknown as Record<string, unknown>;
|
|
489
|
+
const clients = readRecord(context.clients);
|
|
490
|
+
const extensions = context.extensions as unknown as Record<string, unknown>;
|
|
491
|
+
const readModels = readRecord(platform.readModels);
|
|
492
|
+
const platformMemory = readRecord(readModels.memory);
|
|
493
|
+
const platformAgentMemory = readRecord(readModels.agentMemory);
|
|
494
|
+
const agentKnowledgeApi = readRecord(clients.agentKnowledgeApi);
|
|
495
|
+
const agentKnowledgeMemory = readRecord(agentKnowledgeApi.memory);
|
|
496
|
+
const agentKnowledgeService = readRecord(extensions.agentKnowledgeService);
|
|
497
|
+
return [
|
|
498
|
+
{ path: 'context.platform.readModels.memory.externalProviders', source: platformMemory.externalProviders },
|
|
499
|
+
{ path: 'context.platform.readModels.memory.providers', source: platformMemory.providers },
|
|
500
|
+
{ path: 'context.platform.readModels.agentMemory.externalProviders', source: platformAgentMemory.externalProviders },
|
|
501
|
+
{ path: 'context.platform.readModels.externalMemoryProviders', source: readModels.externalMemoryProviders },
|
|
502
|
+
{ path: 'context.platform.readModels.memoryProviders', source: readModels.memoryProviders },
|
|
503
|
+
{ path: 'context.platform.externalMemoryProviders', source: platform.externalMemoryProviders },
|
|
504
|
+
{ path: 'context.clients.agentKnowledgeApi.memory.externalProviders', source: agentKnowledgeMemory.externalProviders },
|
|
505
|
+
{ path: 'context.clients.agentKnowledgeApi.memory.providers', source: agentKnowledgeMemory.providers },
|
|
506
|
+
{ path: 'context.clients.agentKnowledgeApi.externalMemoryProviders', source: agentKnowledgeApi.externalMemoryProviders },
|
|
507
|
+
{ path: 'context.extensions.agentKnowledgeService.externalMemoryProviders', source: agentKnowledgeService.externalMemoryProviders },
|
|
508
|
+
{ path: 'context.extensions.externalMemoryProviders', source: extensions.externalMemoryProviders },
|
|
509
|
+
{
|
|
510
|
+
path: 'context.clients.agentKnowledgeApi.memory.listExternalProviders()',
|
|
511
|
+
source: typeof agentKnowledgeMemory.listExternalProviders === 'function'
|
|
512
|
+
? () => (agentKnowledgeMemory.listExternalProviders as () => unknown)()
|
|
513
|
+
: undefined,
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
path: 'context.clients.agentKnowledgeApi.memory.listProviders()',
|
|
517
|
+
source: typeof agentKnowledgeMemory.listProviders === 'function'
|
|
518
|
+
? () => (agentKnowledgeMemory.listProviders as () => unknown)()
|
|
519
|
+
: undefined,
|
|
520
|
+
},
|
|
521
|
+
];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async function readExternalProviderSnapshot(source: unknown): Promise<unknown> {
|
|
525
|
+
if (typeof source === 'function') return await (source as () => unknown | Promise<unknown>)();
|
|
526
|
+
const record = readRecord(source);
|
|
527
|
+
for (const methodName of ['getSnapshot', 'snapshot', 'listExternalProviders', 'listProviders', 'list']) {
|
|
528
|
+
const method = record[methodName];
|
|
529
|
+
if (typeof method === 'function') return await (method as () => unknown | Promise<unknown>)();
|
|
530
|
+
}
|
|
531
|
+
return source;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function externalProviderRecordsFromSnapshot(snapshot: unknown): readonly unknown[] {
|
|
535
|
+
if (Array.isArray(snapshot)) return snapshot;
|
|
536
|
+
const record = readRecord(snapshot);
|
|
537
|
+
for (const key of ['externalProviders', 'providers', 'memoryProviders', 'records', 'items', 'entries']) {
|
|
538
|
+
const value = record[key];
|
|
539
|
+
if (Array.isArray(value)) return value;
|
|
540
|
+
const valueRecord = readRecord(value);
|
|
541
|
+
if (Object.keys(valueRecord).length > 0) {
|
|
542
|
+
return Object.entries(valueRecord).map(([providerId, entry]) => {
|
|
543
|
+
const entryRecord = readRecord(entry);
|
|
544
|
+
if (Object.keys(entryRecord).length === 0 || readExternalProviderLiveRecordId(entryRecord)) return entry;
|
|
545
|
+
return { ...entryRecord, providerId };
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return readExternalProviderLiveRecordId(record) ? [record] : [];
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function liveProviderStatusRank(record: MemoryExternalProviderLiveRecord): number {
|
|
553
|
+
if (record.status === 'available') return 5;
|
|
554
|
+
if (record.status === 'needs-setup') return 4;
|
|
555
|
+
if (record.status === 'blocked') return 3;
|
|
556
|
+
if (record.status === 'error') return 2;
|
|
557
|
+
if (record.status === 'disabled') return 1;
|
|
558
|
+
return 0;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function preferLiveProviderRecord(
|
|
562
|
+
current: MemoryExternalProviderLiveRecord,
|
|
563
|
+
candidate: MemoryExternalProviderLiveRecord,
|
|
564
|
+
): MemoryExternalProviderLiveRecord {
|
|
565
|
+
const rankDelta = liveProviderStatusRank(candidate) - liveProviderStatusRank(current);
|
|
566
|
+
if (rankDelta > 0) return candidate;
|
|
567
|
+
if (rankDelta < 0) return current;
|
|
568
|
+
const currentTime = current.updatedAt ? Date.parse(current.updatedAt) : 0;
|
|
569
|
+
const candidateTime = candidate.updatedAt ? Date.parse(candidate.updatedAt) : 0;
|
|
570
|
+
if (candidateTime > currentTime) return candidate;
|
|
571
|
+
return current;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export async function externalMemoryLiveProviderRecords(context: CommandContext): Promise<readonly MemoryExternalProviderLiveRecord[]> {
|
|
575
|
+
const byProvider = new Map<string, MemoryExternalProviderLiveRecord>();
|
|
576
|
+
for (const entry of externalMemoryReadModelSources(context)) {
|
|
577
|
+
if (entry.source === undefined || entry.source === null) continue;
|
|
578
|
+
try {
|
|
579
|
+
const snapshot = await readExternalProviderSnapshot(entry.source);
|
|
580
|
+
for (const value of externalProviderRecordsFromSnapshot(snapshot)) {
|
|
581
|
+
const record = normalizeExternalProviderLiveRecord(value, entry.path);
|
|
582
|
+
if (!record) continue;
|
|
583
|
+
const current = byProvider.get(record.providerId);
|
|
584
|
+
byProvider.set(record.providerId, current ? preferLiveProviderRecord(current, record) : record);
|
|
585
|
+
}
|
|
586
|
+
} catch {
|
|
587
|
+
// A broken host read model should not make Agent-local memory posture disappear.
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return [...byProvider.values()].sort((left, right) => {
|
|
591
|
+
const rankDelta = liveProviderStatusRank(right) - liveProviderStatusRank(left);
|
|
592
|
+
if (rankDelta !== 0) return rankDelta;
|
|
593
|
+
const leftTime = left.updatedAt ? Date.parse(left.updatedAt) : 0;
|
|
594
|
+
const rightTime = right.updatedAt ? Date.parse(right.updatedAt) : 0;
|
|
595
|
+
if (rightTime !== leftTime) return rightTime - leftTime;
|
|
596
|
+
return left.providerId.localeCompare(right.providerId);
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function externalMemoryProviderCatalog(
|
|
601
|
+
liveRecords: readonly MemoryExternalProviderLiveRecord[],
|
|
602
|
+
receipts: readonly MemoryExternalProviderReceiptEvidence[] = [],
|
|
603
|
+
): readonly ExternalMemoryProviderCatalogEntry[] {
|
|
604
|
+
const providers = [...EXTERNAL_MEMORY_PROVIDERS];
|
|
605
|
+
for (const record of liveRecords) {
|
|
606
|
+
if (providers.some((provider) => provider.id === record.providerId)) continue;
|
|
607
|
+
providers.push({ id: record.providerId, label: record.label ?? titleFromProviderId(record.providerId) });
|
|
608
|
+
}
|
|
609
|
+
for (const receipt of receipts) {
|
|
610
|
+
if (providers.some((provider) => provider.id === receipt.providerId)) continue;
|
|
611
|
+
providers.push({ id: receipt.providerId, label: titleFromProviderId(receipt.providerId) });
|
|
612
|
+
}
|
|
613
|
+
return providers;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function liveRecordForProvider(
|
|
617
|
+
liveRecords: readonly MemoryExternalProviderLiveRecord[],
|
|
618
|
+
providerId: string,
|
|
619
|
+
): MemoryExternalProviderLiveRecord | null {
|
|
620
|
+
return liveRecords.find((record) => record.providerId === providerId) ?? null;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export function externalProviderLiveReady(record: MemoryExternalProviderLiveRecord | null): boolean {
|
|
624
|
+
return Boolean(record && (record.status === 'available' || (
|
|
625
|
+
record.configured === true
|
|
626
|
+
&& record.reachable !== false
|
|
627
|
+
&& (record.readReady === true || record.writeReady === true || record.syncReady === true)
|
|
628
|
+
)));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export function setupStatusFromLiveRecord(record: MemoryExternalProviderLiveRecord | null): MemoryExternalProviderSetupStatus | null {
|
|
632
|
+
if (!record) return null;
|
|
633
|
+
if (externalProviderLiveReady(record)) return 'ready';
|
|
634
|
+
if (record.status === 'needs-setup') return 'needs-setup';
|
|
635
|
+
if (record.status === 'blocked') return 'blocked';
|
|
636
|
+
if (record.status === 'error') return 'error';
|
|
637
|
+
if (record.status === 'disabled') return 'disabled';
|
|
638
|
+
return 'needs-setup';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export function aggregateExternalProviderLiveRecord(
|
|
642
|
+
liveRecords: readonly MemoryExternalProviderLiveRecord[],
|
|
643
|
+
): MemoryExternalProviderLiveRecord | null {
|
|
644
|
+
if (liveRecords.length === 0) return null;
|
|
645
|
+
const latest = liveRecords[0]!;
|
|
646
|
+
const any = (select: (record: MemoryExternalProviderLiveRecord) => boolean | null): boolean | null => {
|
|
647
|
+
if (liveRecords.some((record) => select(record) === true)) return true;
|
|
648
|
+
if (liveRecords.every((record) => select(record) === false)) return false;
|
|
649
|
+
return null;
|
|
650
|
+
};
|
|
651
|
+
return {
|
|
652
|
+
providerId: '<provider-id>',
|
|
653
|
+
label: 'External memory provider',
|
|
654
|
+
status: liveRecords.some(externalProviderLiveReady) ? 'available' : latest.status,
|
|
655
|
+
source: [...new Set(liveRecords.map((record) => record.source))].join(', '),
|
|
656
|
+
configured: any((record) => record.configured),
|
|
657
|
+
reachable: any((record) => record.reachable),
|
|
658
|
+
credentialState: liveRecords.some((record) => record.credentialState !== null) ? 'published-redacted' : null,
|
|
659
|
+
readReady: any((record) => record.readReady),
|
|
660
|
+
writeReady: any((record) => record.writeReady),
|
|
661
|
+
syncReady: any((record) => record.syncReady),
|
|
662
|
+
forgetReady: any((record) => record.forgetReady),
|
|
663
|
+
promptEligible: any((record) => record.promptEligible),
|
|
664
|
+
setupRoute: latest.setupRoute,
|
|
665
|
+
readRoute: latest.readRoute,
|
|
666
|
+
writeRoute: latest.writeRoute,
|
|
667
|
+
syncRoute: latest.syncRoute,
|
|
668
|
+
forgetRoute: latest.forgetRoute,
|
|
669
|
+
receiptRoute: latest.receiptRoute,
|
|
670
|
+
receiptIds: [...new Set(liveRecords.flatMap((record) => record.receiptIds))],
|
|
671
|
+
sourceCount: liveRecords.reduce((total, record) => total + (record.sourceCount ?? 0), 0) || null,
|
|
672
|
+
recordCount: liveRecords.reduce((total, record) => total + (record.recordCount ?? 0), 0) || null,
|
|
673
|
+
redaction: latest.redaction,
|
|
674
|
+
failureReason: latest.failureReason,
|
|
675
|
+
lastReadAt: latest.lastReadAt,
|
|
676
|
+
lastWriteAt: latest.lastWriteAt,
|
|
677
|
+
lastSyncAt: latest.lastSyncAt,
|
|
678
|
+
updatedAt: latest.updatedAt,
|
|
679
|
+
inspectRoute: 'memory action:"status" query:"external memory provider" includeParameters:true',
|
|
680
|
+
certification: latest.certification,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export function aggregateExternalProviderSetupStatus(
|
|
685
|
+
liveRecords: readonly MemoryExternalProviderLiveRecord[],
|
|
686
|
+
receipts: readonly MemoryExternalProviderReceiptEvidence[],
|
|
687
|
+
): MemoryExternalProviderSetupStatus {
|
|
688
|
+
if (liveRecords.length > 0) return liveRecords.some(externalProviderLiveReady) ? 'ready' : 'needs-setup';
|
|
689
|
+
return receipts.length > 0 ? 'receipt-evidence-found' : 'contract-needed';
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function artifactStore(context: CommandContext): ArtifactListLike | null {
|
|
693
|
+
const candidate = (context.platform as { readonly artifactStore?: unknown }).artifactStore;
|
|
694
|
+
return candidate && typeof candidate === 'object' ? candidate as ArtifactListLike : null;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function normalizeExternalProviderId(value: string): string {
|
|
698
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
699
|
+
const exact = EXTERNAL_MEMORY_PROVIDERS.find((provider) => provider.id === normalized);
|
|
700
|
+
if (exact) return exact.id;
|
|
701
|
+
const label = EXTERNAL_MEMORY_PROVIDERS.find((provider) => provider.label.toLowerCase().replace(/[^a-z0-9]/g, '') === normalized);
|
|
702
|
+
return label?.id ?? normalized;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function isExternalMemoryReceiptArtifact(artifact: ArtifactDescriptor): boolean {
|
|
706
|
+
const metadata = readRecord(artifact.metadata);
|
|
707
|
+
const purpose = readString(metadata.purpose).toLowerCase();
|
|
708
|
+
if (EXTERNAL_MEMORY_RECEIPT_PURPOSES.has(purpose)) return true;
|
|
709
|
+
return purpose.includes('memory') && purpose.includes('receipt') && Boolean(readExternalMemoryReceiptProviderId(metadata));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function readExternalMemoryReceiptProviderId(metadata: Record<string, unknown>): string {
|
|
713
|
+
return normalizeExternalProviderId(
|
|
714
|
+
readString(metadata.providerId)
|
|
715
|
+
|| readString(metadata.provider)
|
|
716
|
+
|| readString(metadata.externalProviderId)
|
|
717
|
+
|| readString(metadata.memoryProviderId)
|
|
718
|
+
|| readString(metadata.backendId),
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function readReceiptCreatedAt(artifact: ArtifactDescriptor, metadata: Record<string, unknown>): string | null {
|
|
723
|
+
const explicit = readString(metadata.createdAt) || readString(metadata.timestamp) || readString(metadata.completedAt);
|
|
724
|
+
if (explicit) {
|
|
725
|
+
const parsed = Date.parse(explicit);
|
|
726
|
+
if (Number.isFinite(parsed)) return new Date(parsed).toISOString();
|
|
727
|
+
}
|
|
728
|
+
return typeof artifact.createdAt === 'number' && Number.isFinite(artifact.createdAt)
|
|
729
|
+
? new Date(artifact.createdAt).toISOString()
|
|
730
|
+
: null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function normalizeReceiptStatus(value: unknown): string {
|
|
734
|
+
const raw = readString(value).toLowerCase();
|
|
735
|
+
if (!raw) return 'unknown';
|
|
736
|
+
if (['ok', 'ready', 'success', 'succeeded', 'complete', 'completed', 'synced', 'written', 'imported', 'exported'].includes(raw)) return 'succeeded';
|
|
737
|
+
if (['blocked', 'needs-review', 'needs_setup', 'needs-setup'].includes(raw)) return 'blocked';
|
|
738
|
+
if (['fail', 'failed', 'error', 'errored'].includes(raw)) return 'failed';
|
|
739
|
+
if (['running', 'pending', 'in-progress', 'in_progress'].includes(raw)) return 'running';
|
|
740
|
+
return raw;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function describeExternalMemoryReceiptArtifact(artifact: ArtifactDescriptor): MemoryExternalProviderReceiptEvidence | null {
|
|
744
|
+
const metadata = readRecord(artifact.metadata);
|
|
745
|
+
if (!isExternalMemoryReceiptArtifact(artifact)) return null;
|
|
746
|
+
const providerId = readExternalMemoryReceiptProviderId(metadata);
|
|
747
|
+
if (!providerId) return null;
|
|
748
|
+
const operation = readString(metadata.operation)
|
|
749
|
+
|| readString(metadata.action)
|
|
750
|
+
|| readString(metadata.memoryOperation)
|
|
751
|
+
|| readString(metadata.receiptOperation)
|
|
752
|
+
|| 'external-memory';
|
|
753
|
+
const nextRoute = readString(metadata.nextRoute)
|
|
754
|
+
|| `memory action:"provider" providerId:"${providerId}" includeParameters:true`;
|
|
755
|
+
return {
|
|
756
|
+
providerId,
|
|
757
|
+
artifactId: artifact.id,
|
|
758
|
+
filename: artifact.filename ?? null,
|
|
759
|
+
operation,
|
|
760
|
+
status: normalizeReceiptStatus(metadata.status ?? metadata.outcome ?? metadata.result),
|
|
761
|
+
createdAt: readReceiptCreatedAt(artifact, metadata),
|
|
762
|
+
sourceCount: readNumber(metadata.sourceCount ?? metadata.recordCount ?? metadata.itemCount),
|
|
763
|
+
redaction: readString(metadata.redaction) || readString(metadata.redactionPolicy) || null,
|
|
764
|
+
failureReason: readString(metadata.failureReason) || readString(metadata.error) || null,
|
|
765
|
+
nextRoute,
|
|
766
|
+
inspectRoute: `agent_artifacts show artifactId:"${artifact.id}" includeContent:false`,
|
|
767
|
+
correlationId: readString(metadata.correlationId) || readString(metadata.runId) || null,
|
|
768
|
+
certification: memoryExternalProviderReceiptCertification({
|
|
769
|
+
metadata,
|
|
770
|
+
sourcePath: `artifact ${artifact.id}`,
|
|
771
|
+
providerId,
|
|
772
|
+
artifactId: artifact.id,
|
|
773
|
+
}),
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
export function externalMemoryReceiptEvidence(context: CommandContext): readonly MemoryExternalProviderReceiptEvidence[] {
|
|
778
|
+
const store = artifactStore(context);
|
|
779
|
+
if (!store?.list) return [];
|
|
780
|
+
return store.list(100)
|
|
781
|
+
.map(describeExternalMemoryReceiptArtifact)
|
|
782
|
+
.filter((entry): entry is MemoryExternalProviderReceiptEvidence => entry !== null)
|
|
783
|
+
.sort((left, right) => {
|
|
784
|
+
const leftTime = left.createdAt ? Date.parse(left.createdAt) : 0;
|
|
785
|
+
const rightTime = right.createdAt ? Date.parse(right.createdAt) : 0;
|
|
786
|
+
if (rightTime !== leftTime) return rightTime - leftTime;
|
|
787
|
+
return right.artifactId.localeCompare(left.artifactId);
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
export function receiptEvidenceForProvider(
|
|
792
|
+
evidence: readonly MemoryExternalProviderReceiptEvidence[],
|
|
793
|
+
providerId: string,
|
|
794
|
+
): readonly MemoryExternalProviderReceiptEvidence[] {
|
|
795
|
+
return evidence.filter((entry) => entry.providerId === providerId);
|
|
796
|
+
}
|