ds4-context-engine 0.2.0 → 0.3.0-alpha.1

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.
@@ -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",
@@ -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 READ_ONLY_PROJECT_TOOLS = new Set(["read", "grep", "find", "ls"]);
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
- if (!this.config.enabled) return undefined;
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(event, ctx, pi);
808
+ const prepared = this.preparePrivacyContext(historyEvent, ctx, pi);
786
809
  return prepared.changed ? { messages: prepared.event.messages } : undefined;
787
810
  } catch {
788
- return { messages: event.messages.map(failClosedMessage) };
811
+ return { messages: historyEvent.messages.map(failClosedMessage) };
789
812
  }
790
813
  }
791
- if (this.phase !== "observer" && this.phase !== "managed") return undefined;
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(event, ctx, pi);
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(event.messages, ctx);
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 ?? event.messages.length,
1211
+ originalMessageCount: manifest.planning?.originalMessageCount ?? historyEvent.messages.length,
1185
1212
  originalEstimatedMessageTokens: manifest.planning?.originalMessageTokens
1186
- ?? estimateMessagesTokens(event.messages),
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: event.messages.map(failClosedMessage) }
1230
- : undefined;
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
- const sanitized = this.privacyEngine.sanitizeProviderPayload(payload, provider);
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 && READ_ONLY_PROJECT_TOOLS.has(toolName.toLowerCase())) return;
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.info("pin.created", { pinId: pin.id, scope: pin.scope });
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.info("pin.deleted", { pinId });
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.info("memory.created", { memoryId: memory.id, scope: memory.scope, hasKey: Boolean(memory.key) });
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.info("memory.superseded", { previousId, memoryId: memory.id });
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.info("memory.status_changed", { memoryId, status });
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.2.0";
1
+ export const EXTENSION_VERSION = "0.3.0-alpha.1";
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";