ds4-context-engine 0.2.0 → 0.3.0-alpha.2
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/README.md +31 -3
- package/docs/ADR/README.md +5 -0
- package/docs/ARCHITECTURE.md +9 -1
- package/docs/CONTEXT_PERSISTENCE_TOOL.md +131 -0
- package/docs/DOGFOODING_0.3.0_ALPHA.md +304 -0
- package/docs/MEMORY_AND_PINS.md +3 -3
- package/docs/PRIVACY.md +9 -1
- package/docs/RELEASING.md +15 -5
- package/docs/STORAGE.md +2 -2
- package/docs/releases/0.3.0-alpha.1.md +89 -0
- package/docs/releases/0.3.0-alpha.2.md +61 -0
- package/package.json +3 -2
- package/src/extension/context-persistence-contract.ts +159 -0
- package/src/extension/context-persistence-egress.ts +224 -0
- package/src/extension/context-persistence-result.ts +451 -0
- package/src/extension/context-persistence-tool.ts +1948 -0
- package/src/extension/index.ts +2 -0
- package/src/extension/runtime.ts +355 -22
- package/src/pi-adapter/version.ts +1 -1
package/src/extension/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { createOpenAIResponsesContinuationStream } from "../pi-adapter/openai-responses-stream.ts";
|
|
9
9
|
import { registerContextCommand } from "./commands.ts";
|
|
10
|
+
import { registerContextPersistenceTool } from "./context-persistence-tool.ts";
|
|
10
11
|
import { Ds4ContextRuntime, type RuntimeDependencies } from "./runtime.ts";
|
|
11
12
|
|
|
12
13
|
export type Ds4ExtensionDependencies = Partial<RuntimeDependencies>;
|
|
@@ -38,6 +39,7 @@ export function registerDs4ContextEngine(
|
|
|
38
39
|
const registeredContinuationProviders = new Set<string>();
|
|
39
40
|
|
|
40
41
|
registerContextCommand(pi, runtime);
|
|
42
|
+
registerContextPersistenceTool(pi, runtime);
|
|
41
43
|
pi.registerTool(defineTool({
|
|
42
44
|
name: "context_artifact_search",
|
|
43
45
|
label: "Search DS4 Artifact",
|
package/src/extension/runtime.ts
CHANGED
|
@@ -108,9 +108,14 @@ import {
|
|
|
108
108
|
type RankingFeedbackLabel,
|
|
109
109
|
} from "ds4-context-core/ranking/learned-ranker";
|
|
110
110
|
import {
|
|
111
|
+
classifyMarkedContent,
|
|
111
112
|
disabledPrivacyDiagnostics,
|
|
112
113
|
emptyPrivacyCounts,
|
|
114
|
+
highestClassification,
|
|
113
115
|
PrivacyPolicyEngine,
|
|
116
|
+
providerPrivacyPolicy,
|
|
117
|
+
redactSecrets,
|
|
118
|
+
sanitizeClassifiedText,
|
|
114
119
|
type PrivacyClassification,
|
|
115
120
|
type PrivacyDiagnostics,
|
|
116
121
|
} from "ds4-context-core/privacy/privacy-policy";
|
|
@@ -149,10 +154,21 @@ import {
|
|
|
149
154
|
POLICY_VERSION,
|
|
150
155
|
SUPPORTED_PI_VERSION,
|
|
151
156
|
} from "../pi-adapter/version.ts";
|
|
157
|
+
import { sanitizeContextPersistenceHistory } from "./context-persistence-egress.ts";
|
|
158
|
+
import type {
|
|
159
|
+
ContextPersistenceAppender,
|
|
160
|
+
ContextPersistenceBoundedRead,
|
|
161
|
+
ContextPersistenceMutationPolicy,
|
|
162
|
+
ContextPersistenceOutcomeRecord,
|
|
163
|
+
ContextPersistencePage,
|
|
164
|
+
ContextPersistenceProvenance,
|
|
165
|
+
ContextPersistenceRuntimeState,
|
|
166
|
+
ContextPersistenceSanitization,
|
|
167
|
+
} from "./context-persistence-tool.ts";
|
|
152
168
|
|
|
153
169
|
export type RuntimePhase = "idle" | "initializing" | "disabled" | "observer" | "managed" | "degraded" | "closed";
|
|
154
170
|
|
|
155
|
-
const
|
|
171
|
+
const PROJECT_FILE_NEUTRAL_TOOLS = new Set(["read", "grep", "find", "ls", "context_persistence"]);
|
|
156
172
|
const MAX_PRIOR_RANKING_OUTCOMES = 50_000;
|
|
157
173
|
|
|
158
174
|
function comparablePath(path: string): string {
|
|
@@ -779,20 +795,31 @@ export class Ds4ContextRuntime {
|
|
|
779
795
|
ctx: ExtensionContext,
|
|
780
796
|
pi: ExtensionAPI,
|
|
781
797
|
): { messages?: ContextEvent["messages"] } | undefined {
|
|
782
|
-
|
|
798
|
+
let history: ReturnType<typeof sanitizeContextPersistenceHistory<ContextEvent["messages"]>>;
|
|
799
|
+
try {
|
|
800
|
+
history = sanitizeContextPersistenceHistory(event.messages);
|
|
801
|
+
} catch {
|
|
802
|
+
return { messages: event.messages.map(failClosedMessage) };
|
|
803
|
+
}
|
|
804
|
+
const historyEvent = history.changed ? { ...event, messages: history.value } : event;
|
|
805
|
+
if (!this.config.enabled) return history.changed ? { messages: history.value } : undefined;
|
|
783
806
|
if (this.phase === "degraded" && this.config.privacy.enabled) {
|
|
784
807
|
try {
|
|
785
|
-
const prepared = this.preparePrivacyContext(
|
|
808
|
+
const prepared = this.preparePrivacyContext(historyEvent, ctx, pi);
|
|
786
809
|
return prepared.changed ? { messages: prepared.event.messages } : undefined;
|
|
787
810
|
} catch {
|
|
788
|
-
return { messages:
|
|
811
|
+
return { messages: historyEvent.messages.map(failClosedMessage) };
|
|
789
812
|
}
|
|
790
813
|
}
|
|
791
|
-
if (this.phase !== "observer" && this.phase !== "managed")
|
|
814
|
+
if (this.phase !== "observer" && this.phase !== "managed") {
|
|
815
|
+
return history.changed ? { messages: history.value } : undefined;
|
|
816
|
+
}
|
|
792
817
|
|
|
793
|
-
let privacyFallback: { messages: ContextEvent["messages"] } | undefined
|
|
818
|
+
let privacyFallback: { messages: ContextEvent["messages"] } | undefined = history.changed
|
|
819
|
+
? { messages: history.value }
|
|
820
|
+
: undefined;
|
|
794
821
|
try {
|
|
795
|
-
const preparedPrivacy = this.preparePrivacyContext(
|
|
822
|
+
const preparedPrivacy = this.preparePrivacyContext(historyEvent, ctx, pi);
|
|
796
823
|
if (preparedPrivacy.changed) privacyFallback = { messages: preparedPrivacy.event.messages };
|
|
797
824
|
this.syncSessionIndex(ctx);
|
|
798
825
|
this.refreshProjectMemorySources();
|
|
@@ -808,7 +835,7 @@ export class Ds4ContextRuntime {
|
|
|
808
835
|
const artifactReferenceByMessageIndex = new Map<number, NonNullable<ContextManifest["artifacts"]>[number]>();
|
|
809
836
|
let artifactized = false;
|
|
810
837
|
if (this.config.context.mode === "managed" && this.artifactManager) {
|
|
811
|
-
const sourceEntryIds = findExactPiMessageSourceIds(
|
|
838
|
+
const sourceEntryIds = findExactPiMessageSourceIds(historyEvent.messages, ctx);
|
|
812
839
|
const transformed = this.artifactManager.transform(
|
|
813
840
|
preparedPrivacy.event.messages,
|
|
814
841
|
sourceEntryIds,
|
|
@@ -1181,9 +1208,9 @@ export class Ds4ContextRuntime {
|
|
|
1181
1208
|
mode: manifest.planning?.mode ?? "observer",
|
|
1182
1209
|
messageCount: manifest.composition.messageCount,
|
|
1183
1210
|
estimatedMessageTokens: manifest.composition.messageTokens,
|
|
1184
|
-
originalMessageCount: manifest.planning?.originalMessageCount ??
|
|
1211
|
+
originalMessageCount: manifest.planning?.originalMessageCount ?? historyEvent.messages.length,
|
|
1185
1212
|
originalEstimatedMessageTokens: manifest.planning?.originalMessageTokens
|
|
1186
|
-
?? estimateMessagesTokens(
|
|
1213
|
+
?? estimateMessagesTokens(historyEvent.messages),
|
|
1187
1214
|
...(usage?.tokens !== null && usage?.tokens !== undefined ? { reportedTokens: usage.tokens } : {}),
|
|
1188
1215
|
...(manifest.planning?.durationMs !== undefined
|
|
1189
1216
|
? { planningDurationMs: manifest.planning.durationMs }
|
|
@@ -1226,16 +1253,26 @@ export class Ds4ContextRuntime {
|
|
|
1226
1253
|
});
|
|
1227
1254
|
if (privacyFallback) return privacyFallback;
|
|
1228
1255
|
return this.config.privacy.enabled
|
|
1229
|
-
? { messages:
|
|
1230
|
-
:
|
|
1256
|
+
? { messages: historyEvent.messages.map(failClosedMessage) }
|
|
1257
|
+
: privacyFallback;
|
|
1231
1258
|
}
|
|
1232
1259
|
}
|
|
1233
1260
|
|
|
1234
1261
|
enforceProviderPayload(payload: unknown, ctx: ExtensionContext): unknown {
|
|
1235
|
-
if (!this.config.privacy.enabled || !this.privacyEngine) return payload;
|
|
1236
1262
|
const provider = ctx.model?.provider ?? this.lastPrivacy.provider ?? "unknown";
|
|
1263
|
+
let safePayload: unknown;
|
|
1237
1264
|
try {
|
|
1238
|
-
|
|
1265
|
+
safePayload = sanitizeContextPersistenceHistory(payload).value;
|
|
1266
|
+
} catch {
|
|
1267
|
+
this.logger.error("privacy.provider_fail_closed", {
|
|
1268
|
+
provider,
|
|
1269
|
+
errorName: "ContextPersistenceEgressError",
|
|
1270
|
+
});
|
|
1271
|
+
return {};
|
|
1272
|
+
}
|
|
1273
|
+
if (!this.config.privacy.enabled || !this.privacyEngine) return safePayload;
|
|
1274
|
+
try {
|
|
1275
|
+
const sanitized = this.privacyEngine.sanitizeProviderPayload(safePayload, provider);
|
|
1239
1276
|
const redactions = sanitized.blockedBlocks + sanitized.secretRedactions;
|
|
1240
1277
|
this.lastPrivacy = {
|
|
1241
1278
|
...this.lastPrivacy,
|
|
@@ -1317,13 +1354,15 @@ export class Ds4ContextRuntime {
|
|
|
1317
1354
|
fallbackReason?: string;
|
|
1318
1355
|
},
|
|
1319
1356
|
): PreparedNativeContinuation {
|
|
1357
|
+
let safePayload: unknown = {};
|
|
1320
1358
|
try {
|
|
1359
|
+
safePayload = sanitizeContextPersistenceHistory(payload).value;
|
|
1321
1360
|
const manifest = this.lastManifest?.provider === model.provider
|
|
1322
1361
|
&& this.lastManifest.model === model.id
|
|
1323
1362
|
? this.lastManifest
|
|
1324
1363
|
: undefined;
|
|
1325
1364
|
const prepared = this.nativeContinuation.prepare({
|
|
1326
|
-
payload,
|
|
1365
|
+
payload: safePayload,
|
|
1327
1366
|
provider: model.provider,
|
|
1328
1367
|
model: model.id,
|
|
1329
1368
|
api: model.api,
|
|
@@ -1358,7 +1397,7 @@ export class Ds4ContextRuntime {
|
|
|
1358
1397
|
model: model.id,
|
|
1359
1398
|
errorType: error instanceof Error ? error.name : "UnknownError",
|
|
1360
1399
|
});
|
|
1361
|
-
return { payload, tracked: false };
|
|
1400
|
+
return { payload: safePayload, tracked: false };
|
|
1362
1401
|
}
|
|
1363
1402
|
}
|
|
1364
1403
|
|
|
@@ -1511,7 +1550,7 @@ export class Ds4ContextRuntime {
|
|
|
1511
1550
|
}
|
|
1512
1551
|
|
|
1513
1552
|
projectMayHaveChanged(toolName?: string): void {
|
|
1514
|
-
if (toolName &&
|
|
1553
|
+
if (toolName && PROJECT_FILE_NEUTRAL_TOOLS.has(toolName.toLowerCase())) return;
|
|
1515
1554
|
if (this.projectKnowledge) this.projectRefreshPending = true;
|
|
1516
1555
|
}
|
|
1517
1556
|
|
|
@@ -1532,6 +1571,300 @@ export class Ds4ContextRuntime {
|
|
|
1532
1571
|
return this.lastRetrieval;
|
|
1533
1572
|
}
|
|
1534
1573
|
|
|
1574
|
+
contextPersistenceState(ctx: ExtensionContext): ContextPersistenceRuntimeState {
|
|
1575
|
+
const currentSession = this.session ?? this.snapshotCanonicalSession(ctx);
|
|
1576
|
+
const canonicalSessionReady = currentSession.sessionId.trim().length > 0
|
|
1577
|
+
&& Boolean(currentSession.sessionFile);
|
|
1578
|
+
const memoryReady = canonicalSessionReady
|
|
1579
|
+
&& this.config.enabled
|
|
1580
|
+
&& this.config.memory.enabled
|
|
1581
|
+
&& (this.phase === "observer" || this.phase === "managed")
|
|
1582
|
+
&& Boolean(this.memoryManager)
|
|
1583
|
+
&& this.lastMemory.status === "ready";
|
|
1584
|
+
return {
|
|
1585
|
+
available: memoryReady,
|
|
1586
|
+
...(!memoryReady
|
|
1587
|
+
? { errorCode: this.config.memory.enabled ? "runtime-unavailable" as const : "memory-unavailable" as const }
|
|
1588
|
+
: {}),
|
|
1589
|
+
phase: this.phase,
|
|
1590
|
+
sessionId: currentSession.sessionId,
|
|
1591
|
+
projectIdentity: currentSession.projectPath,
|
|
1592
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
1593
|
+
crossSessionEnabled: this.config.memory.crossSession,
|
|
1594
|
+
crossSessionReady: this.lastCrossSessionMemory.status === "ready",
|
|
1595
|
+
defaultClassification: this.config.privacy.defaultClassification,
|
|
1596
|
+
maxResults: this.config.memory.maxResults,
|
|
1597
|
+
maxPinChars: this.config.memory.maxPinChars,
|
|
1598
|
+
maxClaimChars: this.config.memory.maxClaimChars,
|
|
1599
|
+
provider: ctx.model?.provider ?? "unknown",
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
contextPersistenceSanitizeText(
|
|
1604
|
+
text: string,
|
|
1605
|
+
storedClassification: PrivacyClassification | undefined,
|
|
1606
|
+
provider: string,
|
|
1607
|
+
): ContextPersistenceSanitization {
|
|
1608
|
+
let effective = storedClassification ?? this.config.privacy.defaultClassification;
|
|
1609
|
+
const markerFloor = classifyMarkedContent(text);
|
|
1610
|
+
if (markerFloor) effective = highestClassification(effective, markerFloor);
|
|
1611
|
+
const redacted = redactSecrets(text);
|
|
1612
|
+
if (redacted.count > 0) effective = highestClassification(effective, "sensitive");
|
|
1613
|
+
const policy = providerPrivacyPolicy(this.config.privacy, provider || "unknown");
|
|
1614
|
+
const allowed = policy.allowedClassifications.includes(effective)
|
|
1615
|
+
&& !(policy.destination === "remote" && effective === "local-only");
|
|
1616
|
+
if (!allowed) return { value: "", classification: effective, allowed: false };
|
|
1617
|
+
const sanitized = sanitizeClassifiedText(redacted.value, policy, effective, false);
|
|
1618
|
+
return {
|
|
1619
|
+
value: sanitized.value,
|
|
1620
|
+
classification: effective,
|
|
1621
|
+
allowed: sanitized.blockedBlocks === 0,
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
contextPersistenceMutationPolicy(
|
|
1626
|
+
text: string,
|
|
1627
|
+
requested: PrivacyClassification | undefined,
|
|
1628
|
+
inherited: PrivacyClassification | undefined,
|
|
1629
|
+
provider: string,
|
|
1630
|
+
): ContextPersistenceMutationPolicy {
|
|
1631
|
+
let stored = requested;
|
|
1632
|
+
if (inherited) stored = stored ? highestClassification(stored, inherited) : inherited;
|
|
1633
|
+
const markerFloor = classifyMarkedContent(text);
|
|
1634
|
+
if (markerFloor) stored = stored ? highestClassification(stored, markerFloor) : markerFloor;
|
|
1635
|
+
const redacted = redactSecrets(text);
|
|
1636
|
+
if (redacted.count > 0) {
|
|
1637
|
+
stored = stored ? highestClassification(stored, "sensitive") : "sensitive";
|
|
1638
|
+
}
|
|
1639
|
+
const effective = stored ?? this.config.privacy.defaultClassification;
|
|
1640
|
+
const policy = providerPrivacyPolicy(this.config.privacy, provider || "unknown");
|
|
1641
|
+
const allowed = policy.allowedClassifications.includes(effective)
|
|
1642
|
+
&& !(policy.destination === "remote" && effective === "local-only");
|
|
1643
|
+
return {
|
|
1644
|
+
classification: effective,
|
|
1645
|
+
...(stored ? { storedClassification: stored } : {}),
|
|
1646
|
+
allowed,
|
|
1647
|
+
secretDetected: redacted.count > 0,
|
|
1648
|
+
markerDetected: markerFloor !== undefined,
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
contextPersistenceResolveProvenance(
|
|
1653
|
+
ctx: ExtensionContext,
|
|
1654
|
+
toolCallId: string,
|
|
1655
|
+
): ContextPersistenceProvenance | undefined {
|
|
1656
|
+
const branch = ctx.sessionManager.getBranch();
|
|
1657
|
+
const containsToolCall = (entry: unknown): boolean => {
|
|
1658
|
+
if (!entry || typeof entry !== "object") return false;
|
|
1659
|
+
const record = entry as Record<string, unknown>;
|
|
1660
|
+
if (record.type !== "message" || !record.message || typeof record.message !== "object") return false;
|
|
1661
|
+
const message = record.message as Record<string, unknown>;
|
|
1662
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) return false;
|
|
1663
|
+
return message.content.some((block) => {
|
|
1664
|
+
if (!block || typeof block !== "object") return false;
|
|
1665
|
+
const value = block as Record<string, unknown>;
|
|
1666
|
+
return value.type === "toolCall" && value.id === toolCallId;
|
|
1667
|
+
});
|
|
1668
|
+
};
|
|
1669
|
+
const cutoff = branch.findIndex(containsToolCall);
|
|
1670
|
+
const end = cutoff >= 0 ? cutoff : branch.length;
|
|
1671
|
+
let primarySourceEntryId: string | undefined;
|
|
1672
|
+
for (let index = end - 1; index >= 0; index--) {
|
|
1673
|
+
const entry = branch[index];
|
|
1674
|
+
if (entry?.type === "message" && entry.message.role === "user") {
|
|
1675
|
+
primarySourceEntryId = entry.id;
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
if (!primarySourceEntryId) return undefined;
|
|
1680
|
+
const leafId = ctx.sessionManager.getLeafId();
|
|
1681
|
+
return {
|
|
1682
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
1683
|
+
...(leafId ? { leafId } : {}),
|
|
1684
|
+
branchEntryIds: branch.map((entry) => entry.id),
|
|
1685
|
+
primarySourceEntryId,
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
contextPersistenceValidateProvenance(
|
|
1690
|
+
ctx: ExtensionContext,
|
|
1691
|
+
provenance: ContextPersistenceProvenance,
|
|
1692
|
+
): boolean {
|
|
1693
|
+
if (ctx.sessionManager.getSessionId() !== provenance.sessionId) return false;
|
|
1694
|
+
if ((ctx.sessionManager.getLeafId() ?? undefined) !== provenance.leafId) return false;
|
|
1695
|
+
const branchIds = ctx.sessionManager.getBranch().map((entry) => entry.id);
|
|
1696
|
+
if (branchIds.length !== provenance.branchEntryIds.length) return false;
|
|
1697
|
+
for (let index = 0; index < branchIds.length; index++) {
|
|
1698
|
+
if (branchIds[index] !== provenance.branchEntryIds[index]) return false;
|
|
1699
|
+
}
|
|
1700
|
+
return branchIds.includes(provenance.primarySourceEntryId);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
contextPersistenceResolvePin(id: string, activeOnly: boolean): PinItem | undefined {
|
|
1704
|
+
return this.memoryManager?.resolveVisiblePin(id, activeOnly);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
contextPersistenceResolveMemory(id: string, activeOnly: boolean): MemoryItem | undefined {
|
|
1708
|
+
return this.memoryManager?.resolveVisibleMemory(id, activeOnly);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
contextPersistenceListPinsPage(
|
|
1712
|
+
ctx: ExtensionContext,
|
|
1713
|
+
activeOnly: boolean,
|
|
1714
|
+
limit: number,
|
|
1715
|
+
): ContextPersistencePage<PinItem> {
|
|
1716
|
+
const manager = this.memoryManager;
|
|
1717
|
+
if (!manager) return { items: [], hasMore: false };
|
|
1718
|
+
const page = manager.listPinsPage(
|
|
1719
|
+
activeOnly,
|
|
1720
|
+
ctx.sessionManager.getBranch().map((entry) => entry.id),
|
|
1721
|
+
limit,
|
|
1722
|
+
);
|
|
1723
|
+
return { items: page.items, hasMore: page.hasMore };
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
contextPersistenceListMemoriesPage(
|
|
1727
|
+
activeOnly: boolean,
|
|
1728
|
+
limit: number,
|
|
1729
|
+
): ContextPersistencePage<MemoryItem> {
|
|
1730
|
+
const page = this.memoryManager?.listMemoriesPage(activeOnly, limit);
|
|
1731
|
+
return page ? { items: page.items, hasMore: page.hasMore } : { items: [], hasMore: false };
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
contextPersistenceScanPins(
|
|
1735
|
+
activeOnly: boolean,
|
|
1736
|
+
pageSize: number,
|
|
1737
|
+
scanCap: number,
|
|
1738
|
+
signal?: AbortSignal,
|
|
1739
|
+
): ContextPersistenceBoundedRead<PinItem> {
|
|
1740
|
+
return this.memoryManager?.scanPinsBounded(
|
|
1741
|
+
activeOnly,
|
|
1742
|
+
pageSize,
|
|
1743
|
+
scanCap,
|
|
1744
|
+
() => signal?.aborted !== true,
|
|
1745
|
+
) ?? { items: [], incomplete: false, aborted: signal?.aborted === true };
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
contextPersistenceScanMemories(
|
|
1749
|
+
activeOnly: boolean,
|
|
1750
|
+
pageSize: number,
|
|
1751
|
+
scanCap: number,
|
|
1752
|
+
signal?: AbortSignal,
|
|
1753
|
+
): ContextPersistenceBoundedRead<MemoryItem> {
|
|
1754
|
+
return this.memoryManager?.scanMemoriesBounded(
|
|
1755
|
+
activeOnly,
|
|
1756
|
+
pageSize,
|
|
1757
|
+
scanCap,
|
|
1758
|
+
() => signal?.aborted !== true,
|
|
1759
|
+
) ?? { items: [], incomplete: false, aborted: signal?.aborted === true };
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
contextPersistenceProjectSourcesPage(
|
|
1763
|
+
limit: number,
|
|
1764
|
+
): ContextPersistencePage<ProjectMemorySource> {
|
|
1765
|
+
const page = this.memoryManager?.projectMemorySourcesPage(limit);
|
|
1766
|
+
return page ? { items: page.items, hasMore: page.hasMore } : { items: [], hasMore: false };
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
contextPersistenceResolveProjectSource(sessionId: string): ProjectMemorySource | undefined {
|
|
1770
|
+
return this.memoryManager?.resolveProjectMemorySource(sessionId);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
contextPersistenceSetProjectSourceExcluded(
|
|
1774
|
+
sessionId: string,
|
|
1775
|
+
excluded: boolean,
|
|
1776
|
+
reason?: string,
|
|
1777
|
+
): ProjectMemorySource | undefined {
|
|
1778
|
+
this.setProjectMemorySourceExcluded(sessionId, excluded, reason);
|
|
1779
|
+
return this.memoryManager?.resolveProjectMemorySource(sessionId);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
contextPersistenceCreatePin(
|
|
1783
|
+
input: {
|
|
1784
|
+
content: string;
|
|
1785
|
+
scope: "session" | "branch" | "project";
|
|
1786
|
+
sourceEntryId: string;
|
|
1787
|
+
supersedes?: string;
|
|
1788
|
+
classification?: PrivacyClassification;
|
|
1789
|
+
},
|
|
1790
|
+
ctx: ExtensionContext,
|
|
1791
|
+
appendEntry: ContextPersistenceAppender,
|
|
1792
|
+
): { pin: PinItem; duplicate: boolean } {
|
|
1793
|
+
return this.createPin(input, ctx, (customType, data) => appendEntry(customType, data));
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
contextPersistenceUnpin(
|
|
1797
|
+
id: string,
|
|
1798
|
+
reason: string | undefined,
|
|
1799
|
+
ctx: ExtensionContext,
|
|
1800
|
+
appendEntry: ContextPersistenceAppender,
|
|
1801
|
+
): PinItem {
|
|
1802
|
+
return this.unpin(id, reason, ctx, (customType, data) => appendEntry(customType, data));
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
contextPersistenceCreateMemory(
|
|
1806
|
+
input: CreateMemoryInput,
|
|
1807
|
+
ctx: ExtensionContext,
|
|
1808
|
+
appendEntry: ContextPersistenceAppender,
|
|
1809
|
+
): { memory: MemoryItem; duplicate: boolean } {
|
|
1810
|
+
return this.createMemory(input, ctx, (customType, data) => appendEntry(customType, data));
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
contextPersistenceSupersedeMemory(
|
|
1814
|
+
id: string,
|
|
1815
|
+
claim: string,
|
|
1816
|
+
sourceEntryIds: string[],
|
|
1817
|
+
classification: PrivacyClassification | undefined,
|
|
1818
|
+
ctx: ExtensionContext,
|
|
1819
|
+
appendEntry: ContextPersistenceAppender,
|
|
1820
|
+
): MemoryItem {
|
|
1821
|
+
return this.supersedeMemory(
|
|
1822
|
+
id,
|
|
1823
|
+
claim,
|
|
1824
|
+
sourceEntryIds,
|
|
1825
|
+
classification,
|
|
1826
|
+
ctx,
|
|
1827
|
+
(customType, data) => appendEntry(customType, data),
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
contextPersistenceSetMemoryStatus(
|
|
1832
|
+
id: string,
|
|
1833
|
+
status: "invalid" | "expired",
|
|
1834
|
+
reason: string | undefined,
|
|
1835
|
+
ctx: ExtensionContext,
|
|
1836
|
+
appendEntry: ContextPersistenceAppender,
|
|
1837
|
+
): MemoryItem {
|
|
1838
|
+
return this.setMemoryStatus(
|
|
1839
|
+
id,
|
|
1840
|
+
status,
|
|
1841
|
+
reason,
|
|
1842
|
+
ctx,
|
|
1843
|
+
(customType, data) => appendEntry(customType, data),
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
contextPersistenceRecordOutcome(record: ContextPersistenceOutcomeRecord): void {
|
|
1848
|
+
const metadata = {
|
|
1849
|
+
action: record.action,
|
|
1850
|
+
outcome: record.outcome,
|
|
1851
|
+
persistenceClass: record.persistenceClass,
|
|
1852
|
+
...(record.scope ? { scope: record.scope } : {}),
|
|
1853
|
+
...(record.itemId ? { itemId: record.itemId } : {}),
|
|
1854
|
+
...(record.classification ? { classification: record.classification } : {}),
|
|
1855
|
+
...(record.resultCount !== undefined ? { resultCount: record.resultCount } : {}),
|
|
1856
|
+
...(record.duplicate !== undefined ? { duplicate: record.duplicate } : {}),
|
|
1857
|
+
...(record.errorCode ? { errorCode: record.errorCode } : {}),
|
|
1858
|
+
};
|
|
1859
|
+
if (record.outcome === "committed_projection_pending" || record.outcome === "indeterminate") {
|
|
1860
|
+
this.logger.warn("context_persistence.outcome", metadata);
|
|
1861
|
+
} else if (record.errorCode === "boundary-failure") {
|
|
1862
|
+
this.logger.error("context_persistence.boundary_failed", metadata);
|
|
1863
|
+
} else {
|
|
1864
|
+
this.logger.debug("context_persistence.outcome", metadata);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1535
1868
|
listPins(activeOnly = false): PinItem[] {
|
|
1536
1869
|
return this.memoryManager?.listPins(activeOnly) ?? [];
|
|
1537
1870
|
}
|
|
@@ -1667,7 +2000,7 @@ export class Ds4ContextRuntime {
|
|
|
1667
2000
|
const id = proposal.mutation.item.id;
|
|
1668
2001
|
const pin = manager.getPin(id);
|
|
1669
2002
|
if (!pin) throw new Error(`Pin ${id} was not materialized`);
|
|
1670
|
-
this.logger.
|
|
2003
|
+
this.logger.debug("pin.created", { pinId: pin.id, scope: pin.scope });
|
|
1671
2004
|
return { pin, duplicate: false };
|
|
1672
2005
|
}
|
|
1673
2006
|
|
|
@@ -1679,7 +2012,7 @@ export class Ds4ContextRuntime {
|
|
|
1679
2012
|
this.reconcileMemory(ctx);
|
|
1680
2013
|
const pin = manager.getPin(pinId);
|
|
1681
2014
|
if (!pin) throw new Error(`Pin ${pinId} was not materialized`);
|
|
1682
|
-
this.logger.
|
|
2015
|
+
this.logger.debug("pin.deleted", { pinId });
|
|
1683
2016
|
return pin;
|
|
1684
2017
|
}
|
|
1685
2018
|
|
|
@@ -1706,7 +2039,7 @@ export class Ds4ContextRuntime {
|
|
|
1706
2039
|
this.reconcileMemory(ctx);
|
|
1707
2040
|
const memory = manager.getMemory(proposal.mutation.item.id);
|
|
1708
2041
|
if (!memory) throw new Error(`Memory ${proposal.mutation.item.id} was not materialized`);
|
|
1709
|
-
this.logger.
|
|
2042
|
+
this.logger.debug("memory.created", { memoryId: memory.id, scope: memory.scope, hasKey: Boolean(memory.key) });
|
|
1710
2043
|
return { memory, duplicate: false };
|
|
1711
2044
|
}
|
|
1712
2045
|
|
|
@@ -1732,7 +2065,7 @@ export class Ds4ContextRuntime {
|
|
|
1732
2065
|
this.reconcileMemory(ctx);
|
|
1733
2066
|
const memory = manager.getMemory(mutation.item.id);
|
|
1734
2067
|
if (!memory) throw new Error(`Memory ${mutation.item.id} was not materialized`);
|
|
1735
|
-
this.logger.
|
|
2068
|
+
this.logger.debug("memory.superseded", { previousId, memoryId: memory.id });
|
|
1736
2069
|
return memory;
|
|
1737
2070
|
}
|
|
1738
2071
|
|
|
@@ -1750,7 +2083,7 @@ export class Ds4ContextRuntime {
|
|
|
1750
2083
|
this.reconcileMemory(ctx);
|
|
1751
2084
|
const memory = manager.getMemory(memoryId);
|
|
1752
2085
|
if (!memory) throw new Error(`Memory ${memoryId} was not materialized`);
|
|
1753
|
-
this.logger.
|
|
2086
|
+
this.logger.debug("memory.status_changed", { memoryId, status });
|
|
1754
2087
|
return memory;
|
|
1755
2088
|
}
|
|
1756
2089
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const EXTENSION_VERSION = "0.
|
|
1
|
+
export const EXTENSION_VERSION = "0.3.0-alpha.2";
|
|
2
2
|
export const SUPPORTED_PI_VERSION = "0.84.3";
|
|
3
3
|
export const OBSERVER_PLANNER_VERSION = "observer-model-aware-v1";
|
|
4
4
|
export const PLANNER_VERSION = "managed-learned-ranking-v1";
|