@vellumai/assistant 0.4.49 → 0.4.50
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/ARCHITECTURE.md +24 -33
- package/README.md +3 -3
- package/docs/architecture/memory.md +180 -119
- package/package.json +2 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -0
- package/src/__tests__/credential-vault.test.ts +13 -1
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/invite-redemption-service.test.ts +65 -1
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +572 -5
- package/src/__tests__/oauth-store.test.ts +120 -6
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +46 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secure-keys.test.ts +7 -2
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/voice-invite-redemption.test.ts +32 -1
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +132 -0
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +43 -5
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +1 -1
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/doctor.ts +4 -3
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +31 -2
- package/src/cli/commands/oauth/connections.ts +431 -97
- package/src/cli/commands/oauth/providers.ts +15 -1
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +4 -10
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +61 -3
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +31 -3
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -0
- package/src/daemon/server.ts +12 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/instrument.ts +61 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +22 -3
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/oauth/byo-connection.test.ts +8 -1
- package/src/oauth/oauth-store.ts +113 -27
- package/src/oauth/seed-providers.ts +6 -0
- package/src/oauth/token-persistence.ts +11 -3
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +19 -1
- package/src/runtime/invite-service.ts +25 -0
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/conversation-routes.ts +9 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +2 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +2 -1
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/secure-keys.ts +25 -3
- package/src/security/token-manager.ts +36 -36
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +1 -3
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/schedule/create.ts +8 -1
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure, deterministic policy helpers for memory conflict eligibility.
|
|
3
|
-
* Used by contradiction checker, session conflict gate, and background resolver.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface ConflictPolicyConfig {
|
|
7
|
-
conflictableKinds: readonly string[];
|
|
8
|
-
[key: string]: unknown;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Returns true when the given memory item kind is eligible to participate
|
|
13
|
-
* in conflict detection according to the current policy.
|
|
14
|
-
*/
|
|
15
|
-
export function isConflictKindEligible(
|
|
16
|
-
kind: string,
|
|
17
|
-
config: ConflictPolicyConfig,
|
|
18
|
-
): boolean {
|
|
19
|
-
return config.conflictableKinds.includes(kind);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Returns true when both sides of a potential conflict pair are kind-eligible.
|
|
24
|
-
*/
|
|
25
|
-
export function isConflictKindPairEligible(
|
|
26
|
-
existingKind: string,
|
|
27
|
-
candidateKind: string,
|
|
28
|
-
config: ConflictPolicyConfig,
|
|
29
|
-
): boolean {
|
|
30
|
-
return (
|
|
31
|
-
isConflictKindEligible(existingKind, config) &&
|
|
32
|
-
isConflictKindEligible(candidateKind, config)
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ── Transient statement classification ─────────────────────────────────
|
|
37
|
-
|
|
38
|
-
const PR_URL_PATTERN = /github\.com\/[^/]+\/[^/]+\/pull\/\d+/i;
|
|
39
|
-
const ISSUE_TICKET_PATTERN = /\b(?:issue|pr|ticket|pull request)\s*#?\d+/i;
|
|
40
|
-
const TRACKING_LANGUAGE_PATTERN =
|
|
41
|
-
/\b(?:this pr|that issue|while we wait|currently tracking)\b/i;
|
|
42
|
-
|
|
43
|
-
// Statements about needing clarification are transient conversational artifacts
|
|
44
|
-
// extracted from previous conflict-gate interactions — not durable facts.
|
|
45
|
-
// Allowing them into the conflict pipeline creates self-reinforcing loops.
|
|
46
|
-
// Patterns are kept narrow to avoid filtering legitimate durable instructions.
|
|
47
|
-
const META_CLARIFICATION_PATTERN =
|
|
48
|
-
/\b(?:needs? clarification\b|unclear which (?:version|value|setting)\b|user should (?:specify|clarify)\b|conflicting (?:notes|instructions)(?:\s*[:."]|\s+(?:about|regarding|for)\b))/i;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Returns true when a statement looks like a transient tracking note
|
|
52
|
-
* (PR URLs, issue references, short-lived progress notes) rather than
|
|
53
|
-
* a durable user preference or instruction.
|
|
54
|
-
*/
|
|
55
|
-
export function isTransientTrackingStatement(statement: string): boolean {
|
|
56
|
-
if (PR_URL_PATTERN.test(statement)) return true;
|
|
57
|
-
if (ISSUE_TICKET_PATTERN.test(statement)) return true;
|
|
58
|
-
if (TRACKING_LANGUAGE_PATTERN.test(statement)) return true;
|
|
59
|
-
if (META_CLARIFICATION_PATTERN.test(statement)) return true;
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const DURABLE_INSTRUCTION_CUES =
|
|
64
|
-
/\b(?:always|never|default|every time|by default|style|format|tone|convention|standard)\b/i;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Returns true when a statement contains strong durable instruction cues,
|
|
68
|
-
* suggesting it represents a persistent user preference or style rule.
|
|
69
|
-
*/
|
|
70
|
-
export function isDurableInstructionStatement(statement: string): boolean {
|
|
71
|
-
return DURABLE_INSTRUCTION_CUES.test(statement);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ── Verification-state provenance ──────────────────────────────────────
|
|
75
|
-
|
|
76
|
-
// States indicating user involvement — either the user directly stated
|
|
77
|
-
// the information, explicitly confirmed it, or it was bulk-imported from
|
|
78
|
-
// a trusted external source the user chose to connect.
|
|
79
|
-
const USER_EVIDENCED_STATES = new Set([
|
|
80
|
-
"user_reported",
|
|
81
|
-
"user_confirmed",
|
|
82
|
-
"legacy_import",
|
|
83
|
-
]);
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Returns true when the verification state indicates user provenance
|
|
87
|
-
* (as opposed to purely assistant-inferred).
|
|
88
|
-
*/
|
|
89
|
-
export function isUserEvidencedVerificationState(state: string): boolean {
|
|
90
|
-
return USER_EVIDENCED_STATES.has(state);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Returns true when at least one side of a conflict pair has user-evidenced
|
|
95
|
-
* provenance. Assistant-inferred-only conflicts should not escalate into
|
|
96
|
-
* user-facing behavior.
|
|
97
|
-
*/
|
|
98
|
-
export function isConflictUserEvidenced(
|
|
99
|
-
existingState: string,
|
|
100
|
-
candidateState: string,
|
|
101
|
-
): boolean {
|
|
102
|
-
return (
|
|
103
|
-
isUserEvidencedVerificationState(existingState) ||
|
|
104
|
-
isUserEvidencedVerificationState(candidateState)
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Returns true when a statement of the given kind is eligible to participate
|
|
110
|
-
* in conflict detection at the statement level. This combines kind eligibility
|
|
111
|
-
* with statement-level durability heuristics.
|
|
112
|
-
*
|
|
113
|
-
* For instruction/style kinds: requires positive durable cues and no transient cues.
|
|
114
|
-
* For other eligible kinds: rejects if transient tracking cues dominate.
|
|
115
|
-
*/
|
|
116
|
-
export function isStatementConflictEligible(
|
|
117
|
-
kind: string,
|
|
118
|
-
statement: string,
|
|
119
|
-
config?: ConflictPolicyConfig,
|
|
120
|
-
): boolean {
|
|
121
|
-
if (config && !isConflictKindEligible(kind, config)) return false;
|
|
122
|
-
if (isTransientTrackingStatement(statement)) return false;
|
|
123
|
-
if (kind === "instruction" || kind === "style") {
|
|
124
|
-
return isDurableInstructionStatement(statement);
|
|
125
|
-
}
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
import { and, asc, eq } from "drizzle-orm";
|
|
2
|
-
import { v4 as uuid } from "uuid";
|
|
3
|
-
|
|
4
|
-
import { getDb, getSqlite, rawAll } from "./db.js";
|
|
5
|
-
import { enqueueMemoryJob } from "./jobs-store.js";
|
|
6
|
-
import { memoryItemConflicts, memoryItems } from "./schema.js";
|
|
7
|
-
import { clampUnitInterval } from "./validation.js";
|
|
8
|
-
|
|
9
|
-
export type MemoryConflictRelationship =
|
|
10
|
-
| "contradiction"
|
|
11
|
-
| "ambiguous_contradiction"
|
|
12
|
-
| "update"
|
|
13
|
-
| "complement";
|
|
14
|
-
|
|
15
|
-
export type MemoryConflictStatus =
|
|
16
|
-
| "pending_clarification"
|
|
17
|
-
| "resolved_keep_existing"
|
|
18
|
-
| "resolved_keep_candidate"
|
|
19
|
-
| "resolved_merge"
|
|
20
|
-
| "dismissed";
|
|
21
|
-
|
|
22
|
-
export type ResolvedMemoryConflictStatus = Exclude<
|
|
23
|
-
MemoryConflictStatus,
|
|
24
|
-
"pending_clarification"
|
|
25
|
-
>;
|
|
26
|
-
|
|
27
|
-
export interface MemoryItemConflict {
|
|
28
|
-
id: string;
|
|
29
|
-
scopeId: string;
|
|
30
|
-
existingItemId: string;
|
|
31
|
-
candidateItemId: string;
|
|
32
|
-
relationship: string;
|
|
33
|
-
status: MemoryConflictStatus;
|
|
34
|
-
clarificationQuestion: string | null;
|
|
35
|
-
resolutionNote: string | null;
|
|
36
|
-
lastAskedAt: number | null;
|
|
37
|
-
resolvedAt: number | null;
|
|
38
|
-
createdAt: number;
|
|
39
|
-
updatedAt: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface CreatePendingConflictInput {
|
|
43
|
-
scopeId: string;
|
|
44
|
-
existingItemId: string;
|
|
45
|
-
candidateItemId: string;
|
|
46
|
-
relationship: string;
|
|
47
|
-
clarificationQuestion?: string | null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface ResolveConflictInput {
|
|
51
|
-
status: ResolvedMemoryConflictStatus;
|
|
52
|
-
resolutionNote?: string | null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface PendingConflictDetail extends MemoryItemConflict {
|
|
56
|
-
existingStatement: string;
|
|
57
|
-
candidateStatement: string;
|
|
58
|
-
existingKind: string;
|
|
59
|
-
candidateKind: string;
|
|
60
|
-
existingVerificationState: string;
|
|
61
|
-
candidateVerificationState: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type ConflictResolutionAction =
|
|
65
|
-
| "keep_existing"
|
|
66
|
-
| "keep_candidate"
|
|
67
|
-
| "merge";
|
|
68
|
-
|
|
69
|
-
export interface ApplyConflictResolutionInput {
|
|
70
|
-
conflictId: string;
|
|
71
|
-
resolution: ConflictResolutionAction;
|
|
72
|
-
mergedStatement?: string | null;
|
|
73
|
-
resolutionNote?: string | null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function createOrUpdatePendingConflict(
|
|
77
|
-
input: CreatePendingConflictInput,
|
|
78
|
-
): MemoryItemConflict {
|
|
79
|
-
// Wrap in BEGIN IMMEDIATE so the SELECT-then-INSERT is atomic against concurrent
|
|
80
|
-
// writers. Without this, two parallel memory workers could both observe no
|
|
81
|
-
// existing conflict and both attempt to INSERT the same pair, resulting in a
|
|
82
|
-
// duplicate or an unexpected constraint violation.
|
|
83
|
-
return getSqlite()
|
|
84
|
-
.transaction((): MemoryItemConflict => {
|
|
85
|
-
const db = getDb();
|
|
86
|
-
const now = Date.now();
|
|
87
|
-
const scopeId = input.scopeId;
|
|
88
|
-
const existing = getPendingConflictByPair(
|
|
89
|
-
scopeId,
|
|
90
|
-
input.existingItemId,
|
|
91
|
-
input.candidateItemId,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
if (existing) {
|
|
95
|
-
db.update(memoryItemConflicts)
|
|
96
|
-
.set({
|
|
97
|
-
relationship: input.relationship,
|
|
98
|
-
clarificationQuestion:
|
|
99
|
-
input.clarificationQuestion !== undefined
|
|
100
|
-
? input.clarificationQuestion
|
|
101
|
-
: existing.clarificationQuestion,
|
|
102
|
-
updatedAt: now,
|
|
103
|
-
})
|
|
104
|
-
.where(eq(memoryItemConflicts.id, existing.id))
|
|
105
|
-
.run();
|
|
106
|
-
const updated = getConflictById(existing.id);
|
|
107
|
-
if (!updated) {
|
|
108
|
-
throw new Error(`Failed to reload updated conflict: ${existing.id}`);
|
|
109
|
-
}
|
|
110
|
-
return updated;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const id = uuid();
|
|
114
|
-
db.insert(memoryItemConflicts)
|
|
115
|
-
.values({
|
|
116
|
-
id,
|
|
117
|
-
scopeId,
|
|
118
|
-
existingItemId: input.existingItemId,
|
|
119
|
-
candidateItemId: input.candidateItemId,
|
|
120
|
-
relationship: input.relationship,
|
|
121
|
-
status: "pending_clarification",
|
|
122
|
-
clarificationQuestion: input.clarificationQuestion ?? null,
|
|
123
|
-
resolutionNote: null,
|
|
124
|
-
lastAskedAt: null,
|
|
125
|
-
resolvedAt: null,
|
|
126
|
-
createdAt: now,
|
|
127
|
-
updatedAt: now,
|
|
128
|
-
})
|
|
129
|
-
.run();
|
|
130
|
-
|
|
131
|
-
const created = getConflictById(id);
|
|
132
|
-
if (!created) {
|
|
133
|
-
throw new Error(`Failed to load created conflict: ${id}`);
|
|
134
|
-
}
|
|
135
|
-
return created;
|
|
136
|
-
})
|
|
137
|
-
.immediate();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function getConflictById(conflictId: string): MemoryItemConflict | null {
|
|
141
|
-
const db = getDb();
|
|
142
|
-
const row = db
|
|
143
|
-
.select()
|
|
144
|
-
.from(memoryItemConflicts)
|
|
145
|
-
.where(eq(memoryItemConflicts.id, conflictId))
|
|
146
|
-
.get();
|
|
147
|
-
return row ? toConflict(row) : null;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function getPendingConflictByPair(
|
|
151
|
-
scopeId: string,
|
|
152
|
-
existingItemId: string,
|
|
153
|
-
candidateItemId: string,
|
|
154
|
-
): MemoryItemConflict | null {
|
|
155
|
-
const db = getDb();
|
|
156
|
-
const row = db
|
|
157
|
-
.select()
|
|
158
|
-
.from(memoryItemConflicts)
|
|
159
|
-
.where(
|
|
160
|
-
and(
|
|
161
|
-
eq(memoryItemConflicts.scopeId, scopeId),
|
|
162
|
-
eq(memoryItemConflicts.existingItemId, existingItemId),
|
|
163
|
-
eq(memoryItemConflicts.candidateItemId, candidateItemId),
|
|
164
|
-
eq(memoryItemConflicts.status, "pending_clarification"),
|
|
165
|
-
),
|
|
166
|
-
)
|
|
167
|
-
.get();
|
|
168
|
-
return row ? toConflict(row) : null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function listPendingConflicts(
|
|
172
|
-
scopeId: string,
|
|
173
|
-
limit = 100,
|
|
174
|
-
): MemoryItemConflict[] {
|
|
175
|
-
if (limit <= 0) return [];
|
|
176
|
-
const db = getDb();
|
|
177
|
-
const rows = db
|
|
178
|
-
.select()
|
|
179
|
-
.from(memoryItemConflicts)
|
|
180
|
-
.where(
|
|
181
|
-
and(
|
|
182
|
-
eq(memoryItemConflicts.scopeId, scopeId),
|
|
183
|
-
eq(memoryItemConflicts.status, "pending_clarification"),
|
|
184
|
-
),
|
|
185
|
-
)
|
|
186
|
-
.orderBy(asc(memoryItemConflicts.createdAt))
|
|
187
|
-
.limit(limit)
|
|
188
|
-
.all();
|
|
189
|
-
return rows.map(toConflict);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export function listPendingConflictDetails(
|
|
193
|
-
scopeId: string,
|
|
194
|
-
limit = 100,
|
|
195
|
-
cursor?: { createdAt: number; id: string },
|
|
196
|
-
): PendingConflictDetail[] {
|
|
197
|
-
if (limit <= 0) return [];
|
|
198
|
-
interface ConflictDetailRow {
|
|
199
|
-
id: string;
|
|
200
|
-
scope_id: string;
|
|
201
|
-
existing_item_id: string;
|
|
202
|
-
candidate_item_id: string;
|
|
203
|
-
relationship: string;
|
|
204
|
-
status: MemoryConflictStatus;
|
|
205
|
-
clarification_question: string | null;
|
|
206
|
-
resolution_note: string | null;
|
|
207
|
-
last_asked_at: number | null;
|
|
208
|
-
resolved_at: number | null;
|
|
209
|
-
created_at: number;
|
|
210
|
-
updated_at: number;
|
|
211
|
-
existing_statement: string;
|
|
212
|
-
candidate_statement: string;
|
|
213
|
-
existing_kind: string;
|
|
214
|
-
candidate_kind: string;
|
|
215
|
-
existing_verification_state: string;
|
|
216
|
-
candidate_verification_state: string;
|
|
217
|
-
}
|
|
218
|
-
const cursorClause = cursor
|
|
219
|
-
? `AND (c.created_at > ? OR (c.created_at = ? AND c.id > ?))`
|
|
220
|
-
: "";
|
|
221
|
-
const params: (string | number)[] = cursor
|
|
222
|
-
? [scopeId, cursor.createdAt, cursor.createdAt, cursor.id, limit]
|
|
223
|
-
: [scopeId, limit];
|
|
224
|
-
const rows = rawAll<ConflictDetailRow>(
|
|
225
|
-
`
|
|
226
|
-
SELECT
|
|
227
|
-
c.id,
|
|
228
|
-
c.scope_id,
|
|
229
|
-
c.existing_item_id,
|
|
230
|
-
c.candidate_item_id,
|
|
231
|
-
c.relationship,
|
|
232
|
-
c.status,
|
|
233
|
-
c.clarification_question,
|
|
234
|
-
c.resolution_note,
|
|
235
|
-
c.last_asked_at,
|
|
236
|
-
c.resolved_at,
|
|
237
|
-
c.created_at,
|
|
238
|
-
c.updated_at,
|
|
239
|
-
existing_item.statement AS existing_statement,
|
|
240
|
-
candidate_item.statement AS candidate_statement,
|
|
241
|
-
existing_item.kind AS existing_kind,
|
|
242
|
-
candidate_item.kind AS candidate_kind,
|
|
243
|
-
existing_item.verification_state AS existing_verification_state,
|
|
244
|
-
candidate_item.verification_state AS candidate_verification_state
|
|
245
|
-
FROM memory_item_conflicts c
|
|
246
|
-
INNER JOIN memory_items existing_item ON existing_item.id = c.existing_item_id
|
|
247
|
-
INNER JOIN memory_items candidate_item ON candidate_item.id = c.candidate_item_id
|
|
248
|
-
WHERE c.scope_id = ?
|
|
249
|
-
AND c.status = 'pending_clarification'
|
|
250
|
-
${cursorClause}
|
|
251
|
-
ORDER BY c.created_at ASC, c.id ASC
|
|
252
|
-
LIMIT ?
|
|
253
|
-
`,
|
|
254
|
-
...params,
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
return rows.map((row) => ({
|
|
258
|
-
id: row.id,
|
|
259
|
-
scopeId: row.scope_id,
|
|
260
|
-
existingItemId: row.existing_item_id,
|
|
261
|
-
candidateItemId: row.candidate_item_id,
|
|
262
|
-
relationship: row.relationship,
|
|
263
|
-
status: row.status,
|
|
264
|
-
clarificationQuestion: row.clarification_question,
|
|
265
|
-
resolutionNote: row.resolution_note,
|
|
266
|
-
lastAskedAt: row.last_asked_at,
|
|
267
|
-
resolvedAt: row.resolved_at,
|
|
268
|
-
createdAt: row.created_at,
|
|
269
|
-
updatedAt: row.updated_at,
|
|
270
|
-
existingStatement: row.existing_statement,
|
|
271
|
-
candidateStatement: row.candidate_statement,
|
|
272
|
-
existingKind: row.existing_kind,
|
|
273
|
-
candidateKind: row.candidate_kind,
|
|
274
|
-
existingVerificationState: row.existing_verification_state,
|
|
275
|
-
candidateVerificationState: row.candidate_verification_state,
|
|
276
|
-
}));
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export function resolveConflict(
|
|
280
|
-
conflictId: string,
|
|
281
|
-
input: ResolveConflictInput,
|
|
282
|
-
): MemoryItemConflict | null {
|
|
283
|
-
const existing = getConflictById(conflictId);
|
|
284
|
-
if (!existing) return null;
|
|
285
|
-
|
|
286
|
-
const db = getDb();
|
|
287
|
-
const now = Date.now();
|
|
288
|
-
db.update(memoryItemConflicts)
|
|
289
|
-
.set({
|
|
290
|
-
status: input.status,
|
|
291
|
-
resolutionNote:
|
|
292
|
-
input.resolutionNote !== undefined
|
|
293
|
-
? input.resolutionNote
|
|
294
|
-
: existing.resolutionNote,
|
|
295
|
-
resolvedAt: now,
|
|
296
|
-
updatedAt: now,
|
|
297
|
-
})
|
|
298
|
-
.where(eq(memoryItemConflicts.id, conflictId))
|
|
299
|
-
.run();
|
|
300
|
-
|
|
301
|
-
return getConflictById(conflictId);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export function applyConflictResolution(
|
|
305
|
-
input: ApplyConflictResolutionInput,
|
|
306
|
-
): boolean {
|
|
307
|
-
const conflict = getConflictById(input.conflictId);
|
|
308
|
-
if (!conflict || conflict.status !== "pending_clarification") return false;
|
|
309
|
-
|
|
310
|
-
const db = getDb();
|
|
311
|
-
const now = Date.now();
|
|
312
|
-
const existingItem = db
|
|
313
|
-
.select()
|
|
314
|
-
.from(memoryItems)
|
|
315
|
-
.where(eq(memoryItems.id, conflict.existingItemId))
|
|
316
|
-
.get();
|
|
317
|
-
const candidateItem = db
|
|
318
|
-
.select()
|
|
319
|
-
.from(memoryItems)
|
|
320
|
-
.where(eq(memoryItems.id, conflict.candidateItemId))
|
|
321
|
-
.get();
|
|
322
|
-
|
|
323
|
-
if (!existingItem || !candidateItem) {
|
|
324
|
-
resolveConflict(conflict.id, {
|
|
325
|
-
status: "dismissed",
|
|
326
|
-
resolutionNote:
|
|
327
|
-
input.resolutionNote ?? "Conflict items missing at resolution time.",
|
|
328
|
-
});
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
switch (input.resolution) {
|
|
333
|
-
case "keep_existing": {
|
|
334
|
-
db.update(memoryItems)
|
|
335
|
-
.set({ status: "superseded", invalidAt: now })
|
|
336
|
-
.where(eq(memoryItems.id, candidateItem.id))
|
|
337
|
-
.run();
|
|
338
|
-
resolveConflict(conflict.id, {
|
|
339
|
-
status: "resolved_keep_existing",
|
|
340
|
-
resolutionNote: input.resolutionNote ?? null,
|
|
341
|
-
});
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
case "keep_candidate": {
|
|
345
|
-
db.update(memoryItems)
|
|
346
|
-
.set({ status: "superseded", invalidAt: now })
|
|
347
|
-
.where(eq(memoryItems.id, existingItem.id))
|
|
348
|
-
.run();
|
|
349
|
-
db.update(memoryItems)
|
|
350
|
-
.set({ status: "active", validFrom: now })
|
|
351
|
-
.where(eq(memoryItems.id, candidateItem.id))
|
|
352
|
-
.run();
|
|
353
|
-
resolveConflict(conflict.id, {
|
|
354
|
-
status: "resolved_keep_candidate",
|
|
355
|
-
resolutionNote: input.resolutionNote ?? null,
|
|
356
|
-
});
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
case "merge": {
|
|
360
|
-
const mergedStatement = (input.mergedStatement ?? "").trim();
|
|
361
|
-
const nextStatement =
|
|
362
|
-
mergedStatement.length > 0 ? mergedStatement : candidateItem.statement;
|
|
363
|
-
db.update(memoryItems)
|
|
364
|
-
.set({
|
|
365
|
-
statement: nextStatement,
|
|
366
|
-
status: "active",
|
|
367
|
-
invalidAt: null,
|
|
368
|
-
lastSeenAt: Math.max(
|
|
369
|
-
existingItem.lastSeenAt,
|
|
370
|
-
candidateItem.lastSeenAt,
|
|
371
|
-
now,
|
|
372
|
-
),
|
|
373
|
-
confidence: clampUnitInterval(
|
|
374
|
-
Math.max(existingItem.confidence, candidateItem.confidence),
|
|
375
|
-
),
|
|
376
|
-
})
|
|
377
|
-
.where(eq(memoryItems.id, existingItem.id))
|
|
378
|
-
.run();
|
|
379
|
-
db.update(memoryItems)
|
|
380
|
-
.set({ status: "superseded", invalidAt: now })
|
|
381
|
-
.where(eq(memoryItems.id, candidateItem.id))
|
|
382
|
-
.run();
|
|
383
|
-
enqueueMemoryJob("embed_item", { itemId: existingItem.id });
|
|
384
|
-
resolveConflict(conflict.id, {
|
|
385
|
-
status: "resolved_merge",
|
|
386
|
-
resolutionNote: input.resolutionNote ?? null,
|
|
387
|
-
});
|
|
388
|
-
return true;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function toConflict(
|
|
394
|
-
row: typeof memoryItemConflicts.$inferSelect,
|
|
395
|
-
): MemoryItemConflict {
|
|
396
|
-
return {
|
|
397
|
-
id: row.id,
|
|
398
|
-
scopeId: row.scopeId,
|
|
399
|
-
existingItemId: row.existingItemId,
|
|
400
|
-
candidateItemId: row.candidateItemId,
|
|
401
|
-
relationship: row.relationship,
|
|
402
|
-
status: row.status as MemoryConflictStatus,
|
|
403
|
-
clarificationQuestion: row.clarificationQuestion,
|
|
404
|
-
resolutionNote: row.resolutionNote,
|
|
405
|
-
lastAskedAt: row.lastAskedAt,
|
|
406
|
-
resolvedAt: row.resolvedAt,
|
|
407
|
-
createdAt: row.createdAt,
|
|
408
|
-
updatedAt: row.updatedAt,
|
|
409
|
-
};
|
|
410
|
-
}
|