opencode-graphiti 0.1.5 → 0.1.6

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.
Files changed (77) hide show
  1. package/esm/src/config.d.ts.map +1 -1
  2. package/esm/src/config.js +4 -2
  3. package/esm/src/handlers/chat.d.ts +2 -1
  4. package/esm/src/handlers/chat.d.ts.map +1 -1
  5. package/esm/src/handlers/chat.js +89 -49
  6. package/esm/src/handlers/compacting.d.ts +1 -0
  7. package/esm/src/handlers/compacting.d.ts.map +1 -1
  8. package/esm/src/handlers/compacting.js +2 -1
  9. package/esm/src/handlers/event.d.ts +2 -2
  10. package/esm/src/handlers/event.d.ts.map +1 -1
  11. package/esm/src/handlers/event.js +52 -1
  12. package/esm/src/handlers/system.d.ts +11 -0
  13. package/esm/src/handlers/system.d.ts.map +1 -0
  14. package/esm/src/handlers/system.js +18 -0
  15. package/esm/src/index.d.ts.map +1 -1
  16. package/esm/src/index.js +8 -2
  17. package/esm/src/services/client.d.ts +8 -1
  18. package/esm/src/services/client.d.ts.map +1 -1
  19. package/esm/src/services/client.js +23 -0
  20. package/esm/src/services/compaction.d.ts +10 -0
  21. package/esm/src/services/compaction.d.ts.map +1 -1
  22. package/esm/src/services/compaction.js +106 -18
  23. package/esm/src/services/context-limit.d.ts +2 -8
  24. package/esm/src/services/context-limit.d.ts.map +1 -1
  25. package/esm/src/services/context-limit.js +3 -1
  26. package/esm/src/services/context.d.ts +27 -2
  27. package/esm/src/services/context.d.ts.map +1 -1
  28. package/esm/src/services/context.js +107 -13
  29. package/esm/src/services/logger.d.ts.map +1 -1
  30. package/esm/src/services/logger.js +1 -2
  31. package/esm/src/session.d.ts +6 -25
  32. package/esm/src/session.d.ts.map +1 -1
  33. package/esm/src/session.js +4 -7
  34. package/esm/src/types/index.d.ts +8 -2
  35. package/esm/src/types/index.d.ts.map +1 -1
  36. package/package.json +2 -3
  37. package/script/src/config.d.ts.map +1 -1
  38. package/script/src/config.js +4 -2
  39. package/script/src/handlers/chat.d.ts +2 -1
  40. package/script/src/handlers/chat.d.ts.map +1 -1
  41. package/script/src/handlers/chat.js +88 -48
  42. package/script/src/handlers/compacting.d.ts +1 -0
  43. package/script/src/handlers/compacting.d.ts.map +1 -1
  44. package/script/src/handlers/compacting.js +2 -1
  45. package/script/src/handlers/event.d.ts +2 -2
  46. package/script/src/handlers/event.d.ts.map +1 -1
  47. package/script/src/handlers/event.js +52 -1
  48. package/script/src/handlers/system.d.ts +11 -0
  49. package/script/src/handlers/system.d.ts.map +1 -0
  50. package/script/src/handlers/system.js +21 -0
  51. package/script/src/index.d.ts.map +1 -1
  52. package/script/src/index.js +8 -2
  53. package/script/src/services/client.d.ts +8 -1
  54. package/script/src/services/client.d.ts.map +1 -1
  55. package/script/src/services/client.js +23 -0
  56. package/script/src/services/compaction.d.ts +10 -0
  57. package/script/src/services/compaction.d.ts.map +1 -1
  58. package/script/src/services/compaction.js +108 -17
  59. package/script/src/services/context-limit.d.ts +2 -8
  60. package/script/src/services/context-limit.d.ts.map +1 -1
  61. package/script/src/services/context-limit.js +3 -1
  62. package/script/src/services/context.d.ts +27 -2
  63. package/script/src/services/context.d.ts.map +1 -1
  64. package/script/src/services/context.js +118 -14
  65. package/script/src/services/logger.d.ts.map +1 -1
  66. package/script/src/services/logger.js +1 -35
  67. package/script/src/session.d.ts +6 -25
  68. package/script/src/session.d.ts.map +1 -1
  69. package/script/src/session.js +4 -7
  70. package/script/src/types/index.d.ts +8 -2
  71. package/script/src/types/index.d.ts.map +1 -1
  72. package/esm/_dnt.shims.d.ts +0 -6
  73. package/esm/_dnt.shims.d.ts.map +0 -1
  74. package/esm/_dnt.shims.js +0 -61
  75. package/script/_dnt.shims.d.ts +0 -6
  76. package/script/_dnt.shims.d.ts.map +0 -1
  77. package/script/_dnt.shims.js +0 -65
@@ -7,17 +7,8 @@ const logger_js_1 = require("../services/logger.js");
7
7
  const utils_js_1 = require("../utils.js");
8
8
  /** Creates the `chat.message` hook handler. */
9
9
  function createChatHandler(deps) {
10
- const { sessionManager, injectionInterval, client } = deps;
11
- const removeSyntheticMemoryParts = (parts) => parts.filter((part) => {
12
- if (part.type !== "text")
13
- return true;
14
- if (part.id?.startsWith("graphiti-memory-"))
15
- return false;
16
- if (part.id?.startsWith("graphiti-refresh-"))
17
- return false;
18
- return true;
19
- });
20
- const injectMemoryContext = async (state, messageText, output, prefix, useUserScope, characterBudget, shouldReinject) => {
10
+ const { sessionManager, driftThreshold, factStaleDays, client } = deps;
11
+ const searchAndCacheMemoryContext = async (state, messageText, useUserScope, characterBudget, seedFactUuids) => {
21
12
  const userGroupId = state.userGroupId;
22
13
  const projectFactsPromise = client.searchFacts({
23
14
  query: messageText,
@@ -50,45 +41,83 @@ function createChatHandler(deps) {
50
41
  userFactsPromise,
51
42
  userNodesPromise,
52
43
  ]);
53
- const projectContext = (0, context_js_1.formatMemoryContext)(projectFacts, projectNodes);
54
- const userContext = (0, context_js_1.formatMemoryContext)(userFacts, userNodes);
55
- if (!projectContext && !userContext)
44
+ const projectContext = (0, context_js_1.deduplicateContext)({
45
+ facts: projectFacts,
46
+ nodes: projectNodes,
47
+ });
48
+ const userContext = (0, context_js_1.deduplicateContext)({
49
+ facts: userFacts,
50
+ nodes: userNodes,
51
+ });
52
+ const projectContextString = (0, context_js_1.formatMemoryContext)(projectContext.facts, projectContext.nodes, { factStaleDays });
53
+ const userContextString = (0, context_js_1.formatMemoryContext)(userContext.facts, userContext.nodes, { factStaleDays });
54
+ if (!projectContextString && !userContextString)
56
55
  return;
56
+ let snapshotPrimer = "";
57
+ if (useUserScope && characterBudget > 0) {
58
+ try {
59
+ const episodes = await client.getEpisodes({
60
+ groupId: state.groupId,
61
+ lastN: 10,
62
+ });
63
+ const snapshot = episodes
64
+ .filter((episode) => {
65
+ const description = episode.sourceDescription ??
66
+ episode.source_description ?? "";
67
+ return description === "session-snapshot";
68
+ })
69
+ .sort((a, b) => {
70
+ const aTime = a.created_at ? Date.parse(a.created_at) : 0;
71
+ const bTime = b.created_at ? Date.parse(b.created_at) : 0;
72
+ return bTime - aTime;
73
+ })[0];
74
+ if (snapshot?.content) {
75
+ const snapshotBudget = Math.min(characterBudget, 1200);
76
+ snapshotPrimer = [
77
+ "## Session Snapshot",
78
+ "> Most recent session snapshot; use to restore active strategy and open questions.",
79
+ "",
80
+ snapshot.content.slice(0, snapshotBudget),
81
+ ].join("\n");
82
+ }
83
+ }
84
+ catch (err) {
85
+ logger_js_1.logger.error("Failed to load session snapshot", { err });
86
+ }
87
+ }
57
88
  const projectBudget = useUserScope
58
89
  ? Math.floor(characterBudget * 0.7)
59
90
  : characterBudget;
60
91
  const userBudget = characterBudget - projectBudget;
61
- const truncatedProject = projectContext.slice(0, projectBudget);
62
- const truncatedUser = useUserScope ? userContext.slice(0, userBudget) : "";
63
- const memoryContext = [truncatedProject, truncatedUser]
92
+ const truncatedProject = projectContextString.slice(0, projectBudget);
93
+ const truncatedUser = useUserScope
94
+ ? userContextString.slice(0, userBudget)
95
+ : "";
96
+ const memoryContext = [snapshotPrimer, truncatedProject, truncatedUser]
64
97
  .filter((section) => section.trim().length > 0)
65
98
  .join("\n\n")
66
99
  .slice(0, characterBudget);
67
100
  if (!memoryContext)
68
101
  return;
69
- if ("system" in output.message) {
70
- try {
71
- output.message.system = memoryContext;
72
- return;
73
- }
74
- catch (_err) {
75
- // Fall through to synthetic injection.
76
- }
77
- }
78
- if (shouldReinject) {
79
- output.parts = removeSyntheticMemoryParts(output.parts);
80
- }
81
- {
82
- output.parts.unshift({
83
- type: "text",
84
- text: memoryContext,
85
- id: `${prefix}${Date.now()}`,
86
- sessionID: output.message.sessionID,
87
- messageID: output.message.id,
88
- synthetic: true,
89
- });
102
+ const factUuids = seedFactUuids ??
103
+ new Set([
104
+ ...projectContext.facts.map((fact) => fact.uuid),
105
+ ...userContext.facts.map((fact) => fact.uuid),
106
+ ]);
107
+ state.cachedMemoryContext = memoryContext;
108
+ logger_js_1.logger.info(`Cached ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes for system prompt injection`);
109
+ state.lastInjectionFactUuids = factUuids;
110
+ };
111
+ const computeJaccardSimilarity = (left, right) => {
112
+ if (left.size === 0 && right.size === 0)
113
+ return 1;
114
+ let intersection = 0;
115
+ for (const value of left) {
116
+ if (right.has(value))
117
+ intersection += 1;
90
118
  }
91
- logger_js_1.logger.info(`Injected ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes`);
119
+ const union = left.size + right.size - intersection;
120
+ return union === 0 ? 1 : intersection / union;
92
121
  };
93
122
  return async ({ sessionID }, output) => {
94
123
  if (await sessionManager.isSubagentSession(sessionID)) {
@@ -115,19 +144,30 @@ function createChatHandler(deps) {
115
144
  messageLength: messageText.length,
116
145
  });
117
146
  const shouldInjectOnFirst = !state.injectedMemories;
118
- const shouldReinject = !shouldInjectOnFirst &&
119
- injectionInterval > 0 &&
120
- (state.messageCount - state.lastInjectionMessageCount) >=
121
- injectionInterval;
122
- if (!shouldInjectOnFirst && !shouldReinject)
123
- return;
147
+ let shouldReinject = false;
148
+ let currentFactUuids = null;
149
+ if (!shouldInjectOnFirst) {
150
+ const driftFacts = await client.searchFacts({
151
+ query: messageText,
152
+ groupIds: [state.groupId],
153
+ maxFacts: 20,
154
+ });
155
+ currentFactUuids = new Set(driftFacts.map((fact) => fact.uuid));
156
+ const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
157
+ shouldReinject = similarity < driftThreshold;
158
+ if (!shouldReinject) {
159
+ logger_js_1.logger.debug("Skipping reinjection; drift above threshold", {
160
+ sessionID,
161
+ similarity,
162
+ });
163
+ return;
164
+ }
165
+ }
124
166
  try {
125
- const prefix = shouldReinject ? "graphiti-refresh-" : "graphiti-memory-";
126
167
  const useUserScope = shouldInjectOnFirst;
127
168
  const characterBudget = (0, context_limit_js_1.calculateInjectionBudget)(state.contextLimit);
128
- await injectMemoryContext(state, messageText, output, prefix, useUserScope, characterBudget, shouldReinject);
169
+ await searchAndCacheMemoryContext(state, messageText, useUserScope, characterBudget, currentFactUuids);
129
170
  state.injectedMemories = true;
130
- state.lastInjectionMessageCount = state.messageCount;
131
171
  }
132
172
  catch (err) {
133
173
  logger_js_1.logger.error("Failed to inject memories:", err);
@@ -9,6 +9,7 @@ export interface CompactingHandlerDeps {
9
9
  sessionManager: SessionManager;
10
10
  client: GraphitiClient;
11
11
  defaultGroupId: string;
12
+ factStaleDays: number;
12
13
  }
13
14
  /** Creates the `experimental.session.compacting` hook handler. */
14
15
  export declare function createCompactingHandler(deps: CompactingHandlerDeps): ({ sessionID }: CompactingInput, output: CompactingOutput) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"compacting.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/compacting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;AAC5E,KAAK,eAAe,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtD,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,kEAAkE;AAClE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,qBAAqB,IAI/D,eAAe,eAAe,EAC9B,QAAQ,gBAAgB,mBAyB3B"}
1
+ {"version":3,"file":"compacting.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/compacting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;AAC5E,KAAK,eAAe,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtD,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,kEAAkE;AAClE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,qBAAqB,IAI/D,eAAe,eAAe,EAC9B,QAAQ,gBAAgB,mBA0B3B"}
@@ -6,7 +6,7 @@ const context_limit_js_1 = require("../services/context-limit.js");
6
6
  const logger_js_1 = require("../services/logger.js");
7
7
  /** Creates the `experimental.session.compacting` hook handler. */
8
8
  function createCompactingHandler(deps) {
9
- const { sessionManager, client, defaultGroupId } = deps;
9
+ const { sessionManager, client, defaultGroupId, factStaleDays } = deps;
10
10
  return async ({ sessionID }, output) => {
11
11
  const state = sessionManager.getState(sessionID);
12
12
  if (!state?.isMain) {
@@ -23,6 +23,7 @@ function createCompactingHandler(deps) {
23
23
  user: state.userGroupId,
24
24
  },
25
25
  contextStrings: output.context,
26
+ factStaleDays,
26
27
  });
27
28
  if (additionalContext.length > 0) {
28
29
  output.context.push(...additionalContext);
@@ -1,6 +1,6 @@
1
1
  import type { Hooks } from "@opencode-ai/plugin";
2
+ import type { OpencodeClient } from "@opencode-ai/sdk";
2
3
  import type { GraphitiClient } from "../services/client.js";
3
- import type { ProviderListClient } from "../services/context-limit.js";
4
4
  import type { SessionManager } from "../session.js";
5
5
  type EventHook = NonNullable<Hooks["event"]>;
6
6
  type EventInput = Parameters<EventHook>[0];
@@ -9,7 +9,7 @@ export interface EventHandlerDeps {
9
9
  sessionManager: SessionManager;
10
10
  client: GraphitiClient;
11
11
  defaultGroupId: string;
12
- sdkClient: ProviderListClient;
12
+ sdkClient: OpencodeClient;
13
13
  directory: string;
14
14
  groupIdPrefix: string;
15
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7C,KAAK,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3C,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wCAAwC;AACxC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IAWzC,WAAW,UAAU,mBA+JpC"}
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7C,KAAK,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3C,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wCAAwC;AACxC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IAkDzC,WAAW,UAAU,mBAkLpC"}
@@ -9,6 +9,41 @@ const utils_js_1 = require("../utils.js");
9
9
  function createEventHandler(deps) {
10
10
  const { sessionManager, client, defaultGroupId, sdkClient, directory, groupIdPrefix, } = deps;
11
11
  const defaultUserGroupId = (0, utils_js_1.makeUserGroupId)(groupIdPrefix);
12
+ const buildSessionSnapshot = (sessionId, messages) => {
13
+ const recentMessages = messages.slice(-12);
14
+ const recentAssistant = [...recentMessages]
15
+ .reverse()
16
+ .find((message) => message.startsWith("Assistant:"))
17
+ ?.replace(/^Assistant:\s*/, "")
18
+ .trim();
19
+ const recentUser = [...recentMessages]
20
+ .reverse()
21
+ .find((message) => message.startsWith("User:"))
22
+ ?.replace(/^User:\s*/, "")
23
+ .trim();
24
+ const questionRegex = /[^\n\r?]{3,200}\?/g;
25
+ const questions = recentMessages
26
+ .flatMap((message) => {
27
+ const text = message.replace(/^(User|Assistant):\s*/, "");
28
+ return text.match(questionRegex) ?? [];
29
+ })
30
+ .map((question) => question.trim());
31
+ const uniqueQuestions = Array.from(new Set(questions)).slice(0, 6);
32
+ const lines = [];
33
+ lines.push(`Session ${sessionId} working snapshot`);
34
+ if (recentUser)
35
+ lines.push(`Recent user focus: ${recentUser}`);
36
+ if (recentAssistant) {
37
+ lines.push(`Recent assistant focus: ${recentAssistant}`);
38
+ }
39
+ if (uniqueQuestions.length > 0) {
40
+ lines.push("Open questions:");
41
+ for (const question of uniqueQuestions) {
42
+ lines.push(`- ${question}`);
43
+ }
44
+ }
45
+ return lines.join("\n");
46
+ };
12
47
  return async ({ event }) => {
13
48
  try {
14
49
  if (event.type === "session.created") {
@@ -27,7 +62,7 @@ function createEventHandler(deps) {
27
62
  groupId: defaultGroupId,
28
63
  userGroupId: defaultUserGroupId,
29
64
  injectedMemories: false,
30
- lastInjectionMessageCount: 0,
65
+ lastInjectionFactUuids: new Set(),
31
66
  messageCount: 0,
32
67
  pendingMessages: [],
33
68
  contextLimit: 200_000,
@@ -74,6 +109,22 @@ function createEventHandler(deps) {
74
109
  logger_js_1.logger.debug("Ignoring non-main idle session:", sessionId);
75
110
  return;
76
111
  }
112
+ try {
113
+ const snapshotContent = buildSessionSnapshot(sessionId, state.pendingMessages);
114
+ if (snapshotContent.trim()) {
115
+ await client.addEpisode({
116
+ name: `Snapshot: ${sessionId}`,
117
+ episodeBody: snapshotContent,
118
+ groupId: state.groupId,
119
+ source: "text",
120
+ sourceDescription: "session-snapshot",
121
+ });
122
+ logger_js_1.logger.info("Saved session snapshot", { sessionId });
123
+ }
124
+ }
125
+ catch (err) {
126
+ logger_js_1.logger.error("Failed to save session snapshot", { sessionId, err });
127
+ }
77
128
  await sessionManager.flushPendingMessages(sessionId, "Buffered messages from OpenCode session", 50);
78
129
  return;
79
130
  }
@@ -0,0 +1,11 @@
1
+ import type { Hooks } from "@opencode-ai/plugin";
2
+ import type { SessionManager } from "../session.js";
3
+ type SystemTransformHook = NonNullable<Hooks["experimental.chat.system.transform"]>;
4
+ type SystemTransformInput = Parameters<SystemTransformHook>[0];
5
+ type SystemTransformOutput = Parameters<SystemTransformHook>[1];
6
+ export interface SystemHandlerDeps {
7
+ sessionManager: SessionManager;
8
+ }
9
+ export declare function createSystemHandler(deps: SystemHandlerDeps): ({ sessionID }: SystemTransformInput, output: SystemTransformOutput) => Promise<void>;
10
+ export {};
11
+ //# sourceMappingURL=system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/system.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,mBAAmB,GAAG,WAAW,CACpC,KAAK,CAAC,oCAAoC,CAAC,CAC5C,CAAC;AACF,KAAK,oBAAoB,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,KAAK,qBAAqB,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,IAMvD,eAAe,oBAAoB,EACnC,QAAQ,qBAAqB,mBAYhC"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSystemHandler = createSystemHandler;
4
+ const logger_js_1 = require("../services/logger.js");
5
+ function createSystemHandler(deps) {
6
+ const { sessionManager } = deps;
7
+ // Assumes chat.message hook completes before system.transform fires for the same turn.
8
+ // deno-lint-ignore require-await
9
+ return async ({ sessionID }, output) => {
10
+ if (!sessionID)
11
+ return;
12
+ const state = sessionManager.getState(sessionID);
13
+ if (!state?.isMain)
14
+ return;
15
+ if (!state.cachedMemoryContext)
16
+ return;
17
+ output.system.push(state.cachedMemoryContext);
18
+ state.cachedMemoryContext = undefined;
19
+ logger_js_1.logger.info("Injected memory context into system prompt");
20
+ };
21
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAW/D;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,MAkDtB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAW/D;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,MAuDtB,CAAC"}
@@ -5,6 +5,7 @@ const config_js_1 = require("./config.js");
5
5
  const chat_js_1 = require("./handlers/chat.js");
6
6
  const compacting_js_1 = require("./handlers/compacting.js");
7
7
  const event_js_1 = require("./handlers/event.js");
8
+ const system_js_1 = require("./handlers/system.js");
8
9
  const client_js_1 = require("./services/client.js");
9
10
  const logger_js_1 = require("./services/logger.js");
10
11
  const session_js_1 = require("./session.js");
@@ -30,19 +31,24 @@ const graphiti = async (input) => {
30
31
  sessionManager,
31
32
  client,
32
33
  defaultGroupId,
33
- sdkClient: sdkClient,
34
+ sdkClient,
34
35
  directory: input.directory,
35
36
  groupIdPrefix: config.groupIdPrefix,
36
37
  }),
37
38
  "chat.message": (0, chat_js_1.createChatHandler)({
38
39
  sessionManager,
39
- injectionInterval: config.injectionInterval,
40
+ driftThreshold: config.driftThreshold,
41
+ factStaleDays: config.factStaleDays,
40
42
  client,
41
43
  }),
42
44
  "experimental.session.compacting": (0, compacting_js_1.createCompactingHandler)({
43
45
  sessionManager,
44
46
  client,
45
47
  defaultGroupId,
48
+ factStaleDays: config.factStaleDays,
49
+ }),
50
+ "experimental.chat.system.transform": (0, system_js_1.createSystemHandler)({
51
+ sessionManager,
46
52
  }),
47
53
  };
48
54
  };
@@ -1,4 +1,4 @@
1
- import type { GraphitiFact, GraphitiNode } from "../types/index.js";
1
+ import type { GraphitiEpisode, GraphitiFact, GraphitiNode } from "../types/index.js";
2
2
  /**
3
3
  * Graphiti MCP client wrapper for connecting, querying,
4
4
  * and persisting episodes with basic reconnection handling.
@@ -57,6 +57,13 @@ export declare class GraphitiClient {
57
57
  groupIds?: string[];
58
58
  maxNodes?: number;
59
59
  }): Promise<GraphitiNode[]>;
60
+ /**
61
+ * Retrieve recent episodes for a group.
62
+ */
63
+ getEpisodes(params: {
64
+ groupId?: string;
65
+ lastN?: number;
66
+ }): Promise<GraphitiEpisode[]>;
60
67
  /**
61
68
  * Check whether the Graphiti MCP server is reachable.
62
69
  */
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/src/services/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,YAAY,EAEZ,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAG3B;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IAEzB;;OAEG;gBACS,QAAQ,EAAE,MAAM;IAS5B,oDAAoD;IACpD,OAAO,CAAC,wBAAwB;IAUhC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAgBjC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAOnB,QAAQ;IAkCtB,OAAO,CAAC,gBAAgB;YASV,SAAS;IAavB;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO;IAyBzC;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjB;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB3B;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB3B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAQpC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/src/services/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEZ,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAG3B;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IAEzB;;OAEG;gBACS,QAAQ,EAAE,MAAM;IAS5B,oDAAoD;IACpD,OAAO,CAAC,wBAAwB;IAUhC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAgBjC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAOnB,QAAQ;IAkCtB,OAAO,CAAC,gBAAgB;YASV,SAAS;IAavB;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO;IAyBzC;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjB;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB3B;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB3B;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAqB9B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAQpC"}
@@ -221,6 +221,29 @@ class GraphitiClient {
221
221
  return [];
222
222
  }
223
223
  }
224
+ /**
225
+ * Retrieve recent episodes for a group.
226
+ */
227
+ async getEpisodes(params) {
228
+ try {
229
+ const result = await this.callTool("get_episodes", {
230
+ group_id: params.groupId,
231
+ last_n: params.lastN,
232
+ });
233
+ if (Array.isArray(result))
234
+ return result;
235
+ if (result &&
236
+ typeof result === "object" &&
237
+ Array.isArray(result.episodes)) {
238
+ return result.episodes;
239
+ }
240
+ return [];
241
+ }
242
+ catch (err) {
243
+ logger_js_1.logger.error("getEpisodes error:", err);
244
+ return [];
245
+ }
246
+ }
224
247
  /**
225
248
  * Check whether the Graphiti MCP server is reachable.
226
249
  */
@@ -1,4 +1,13 @@
1
1
  import type { GraphitiFact, GraphitiNode } from "../types/index.js";
2
+ export declare const classifyFacts: (facts: GraphitiFact[], now: Date) => {
3
+ decisions: GraphitiFact[];
4
+ active: GraphitiFact[];
5
+ background: GraphitiFact[];
6
+ };
7
+ export declare const takeFactsWithinBudget: (facts: GraphitiFact[], budget: number, formatOptions: {
8
+ factStaleDays: number;
9
+ now: Date;
10
+ }) => GraphitiFact[];
2
11
  /**
3
12
  * Persist a compaction summary episode when enabled.
4
13
  */
@@ -38,5 +47,6 @@ export declare function getCompactionContext(params: {
38
47
  user?: string;
39
48
  };
40
49
  contextStrings: string[];
50
+ factStaleDays?: number;
41
51
  }): Promise<string[]>;
42
52
  //# sourceMappingURL=compaction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/src/services/compaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIpE;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,IAAI,EAAE;YACjB,IAAI,EAAE,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;YACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;SAC5B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE;QACN,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QAC9B,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;KAC/B,CAAC;IACF,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgIpB"}
1
+ {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/src/services/compaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AA0BpE,eAAO,MAAM,aAAa,GACxB,OAAO,YAAY,EAAE,EACrB,KAAK,IAAI,KACR;IACD,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,UAAU,EAAE,YAAY,EAAE,CAAC;CA4B5B,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,OAAO,YAAY,EAAE,EACrB,QAAQ,MAAM,EACd,eAAe;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,KAClD,YAAY,EAoBd,CAAC;AAEF;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,IAAI,EAAE;YACjB,IAAI,EAAE,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;YACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;SAC5B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE;QACN,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QAC9B,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;KAC/B,CAAC;IACF,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA0KpB"}
@@ -1,9 +1,76 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.takeFactsWithinBudget = exports.classifyFacts = void 0;
3
4
  exports.handleCompaction = handleCompaction;
4
5
  exports.getCompactionContext = getCompactionContext;
5
6
  const context_js_1 = require("./context.js");
6
7
  const logger_js_1 = require("./logger.js");
8
+ const DAY_MS = 24 * 60 * 60 * 1000;
9
+ const DECISION_KEYWORDS = [
10
+ "decided",
11
+ "must",
12
+ "should",
13
+ "prefer",
14
+ "constraint",
15
+ "require",
16
+ "chose",
17
+ "always",
18
+ "never",
19
+ "schema",
20
+ "architecture",
21
+ "agreed",
22
+ "design",
23
+ "selected",
24
+ ];
25
+ const classifyFacts = (facts, now) => {
26
+ const decisions = [];
27
+ const active = [];
28
+ const background = [];
29
+ const cutoff = now.getTime() - 7 * DAY_MS;
30
+ for (const fact of facts) {
31
+ const text = fact.fact.toLowerCase();
32
+ // Use word boundary regex to match whole words only
33
+ const hasDecisionKeyword = DECISION_KEYWORDS.some((keyword) => {
34
+ const regex = new RegExp(`\\b${keyword}\\b`, "i");
35
+ return regex.test(text);
36
+ });
37
+ if (hasDecisionKeyword) {
38
+ decisions.push(fact);
39
+ continue;
40
+ }
41
+ const validAt = fact.valid_at ? Date.parse(fact.valid_at) : NaN;
42
+ if (!Number.isNaN(validAt) && validAt >= cutoff) {
43
+ active.push(fact);
44
+ continue;
45
+ }
46
+ background.push(fact);
47
+ }
48
+ return { decisions, active, background };
49
+ };
50
+ exports.classifyFacts = classifyFacts;
51
+ const takeFactsWithinBudget = (facts, budget, formatOptions) => {
52
+ if (budget <= 0 || facts.length === 0)
53
+ return [];
54
+ const classified = (0, exports.classifyFacts)(facts, formatOptions.now);
55
+ const prioritized = [
56
+ ...classified.decisions,
57
+ ...classified.active,
58
+ ...classified.background,
59
+ ];
60
+ const lines = (0, context_js_1.formatFactLines)(prioritized, formatOptions);
61
+ const selected = [];
62
+ let remaining = budget;
63
+ for (let i = 0; i < lines.length; i++) {
64
+ const line = lines[i];
65
+ const length = line.length + 1;
66
+ if (remaining - length < 0)
67
+ continue;
68
+ selected.push(prioritized[i]);
69
+ remaining -= length;
70
+ }
71
+ return selected;
72
+ };
73
+ exports.takeFactsWithinBudget = takeFactsWithinBudget;
7
74
  /**
8
75
  * Persist a compaction summary episode when enabled.
9
76
  */
@@ -30,6 +97,8 @@ async function handleCompaction(params) {
30
97
  */
31
98
  async function getCompactionContext(params) {
32
99
  const { client, characterBudget, groupIds, contextStrings } = params;
100
+ const now = new Date();
101
+ const factStaleDays = params.factStaleDays ?? 30;
33
102
  try {
34
103
  const queryText = contextStrings.slice(0, 3).join(" ").slice(0, 500);
35
104
  if (!queryText.trim())
@@ -70,14 +139,40 @@ async function getCompactionContext(params) {
70
139
  userFacts.length === 0 && userNodes.length === 0) {
71
140
  return [];
72
141
  }
73
- const buildSection = (header, facts, nodes) => {
142
+ const formatOptions = { factStaleDays, now };
143
+ const projectContext = (0, context_js_1.deduplicateContext)({
144
+ facts: projectFacts,
145
+ nodes: projectNodes,
146
+ });
147
+ const userContext = (0, context_js_1.deduplicateContext)({
148
+ facts: userFacts,
149
+ nodes: userNodes,
150
+ });
151
+ const buildSection = (header, facts, nodes, budget) => {
74
152
  const lines = [];
75
153
  lines.push(header);
76
154
  lines.push("<instruction>Background context only; do not reference in titles, summaries, or opening responses unless directly relevant.</instruction>");
77
- if (facts.length > 0) {
78
- lines.push("<facts>");
79
- lines.push(...(0, context_js_1.formatFactLines)(facts));
80
- lines.push("</facts>");
155
+ const classified = (0, exports.classifyFacts)(facts, now);
156
+ const decisionBudget = Math.floor(budget * 0.4);
157
+ const activeBudget = Math.floor(budget * 0.35);
158
+ const backgroundBudget = budget - decisionBudget - activeBudget;
159
+ const selectedDecisions = (0, exports.takeFactsWithinBudget)(classified.decisions, decisionBudget, formatOptions);
160
+ const selectedActive = (0, exports.takeFactsWithinBudget)(classified.active, activeBudget, formatOptions);
161
+ const selectedBackground = (0, exports.takeFactsWithinBudget)(classified.background, backgroundBudget, formatOptions);
162
+ if (selectedDecisions.length > 0) {
163
+ lines.push("<decisions>");
164
+ lines.push(...(0, context_js_1.formatFactLines)(selectedDecisions, formatOptions));
165
+ lines.push("</decisions>");
166
+ }
167
+ if (selectedActive.length > 0) {
168
+ lines.push("<active_context>");
169
+ lines.push(...(0, context_js_1.formatFactLines)(selectedActive, formatOptions));
170
+ lines.push("</active_context>");
171
+ }
172
+ if (selectedBackground.length > 0) {
173
+ lines.push("<background>");
174
+ lines.push(...(0, context_js_1.formatFactLines)(selectedBackground, formatOptions));
175
+ lines.push("</background>");
81
176
  }
82
177
  if (nodes.length > 0) {
83
178
  lines.push("<nodes>");
@@ -86,25 +181,19 @@ async function getCompactionContext(params) {
86
181
  }
87
182
  return lines.join("\n");
88
183
  };
89
- const projectSection = buildSection('<memory source="project">', projectFacts, projectNodes);
90
- const userSection = buildSection('<memory source="user">', userFacts, userNodes);
91
184
  const headerLines = [
92
185
  "<summary>",
93
- "<current_goal>",
94
- "- ",
95
- "</current_goal>",
96
- "",
97
- "<work_completed>",
186
+ "<decisions>",
98
187
  "- ",
99
- "</work_completed>",
188
+ "</decisions>",
100
189
  "",
101
- "<remaining_tasks>",
190
+ "<active_context>",
102
191
  "- ",
103
- "</remaining_tasks>",
192
+ "</active_context>",
104
193
  "",
105
- "<constraints_decisions>",
194
+ "<background>",
106
195
  "- ",
107
- "</constraints_decisions>",
196
+ "</background>",
108
197
  "",
109
198
  "<persistent_memory>",
110
199
  ];
@@ -113,6 +202,8 @@ async function getCompactionContext(params) {
113
202
  const remainingBudget = Math.max(characterBudget - base.length, 0);
114
203
  const projectBudget = Math.floor(remainingBudget * 0.7);
115
204
  const userBudget = remainingBudget - projectBudget;
205
+ const projectSection = buildSection('<memory source="project">', projectContext.facts, projectContext.nodes, projectBudget);
206
+ const userSection = buildSection('<memory source="user">', userContext.facts, userContext.nodes, userBudget);
116
207
  const truncatedProject = projectSection.slice(0, projectBudget);
117
208
  const truncatedUser = userSection.slice(0, userBudget);
118
209
  const sections = [header];