opencode-graphiti 0.1.9 → 0.1.10

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 (88) hide show
  1. package/README.md +7 -1
  2. package/esm/_dnt.polyfills.d.ts +50 -0
  3. package/esm/_dnt.polyfills.d.ts.map +1 -0
  4. package/esm/_dnt.polyfills.js +37 -0
  5. package/esm/mod.d.ts +1 -0
  6. package/esm/mod.d.ts.map +1 -1
  7. package/esm/mod.js +1 -0
  8. package/esm/src/config.d.ts +7 -1
  9. package/esm/src/config.d.ts.map +1 -1
  10. package/esm/src/config.js +17 -5
  11. package/esm/src/handlers/chat.d.ts.map +1 -1
  12. package/esm/src/handlers/chat.js +51 -42
  13. package/esm/src/handlers/event.d.ts +1 -1
  14. package/esm/src/handlers/event.d.ts.map +1 -1
  15. package/esm/src/handlers/event.js +42 -46
  16. package/esm/src/handlers/messages.d.ts.map +1 -1
  17. package/esm/src/handlers/messages.js +2 -3
  18. package/esm/src/index.js +2 -2
  19. package/esm/src/services/client.d.ts +7 -0
  20. package/esm/src/services/client.d.ts.map +1 -1
  21. package/esm/src/services/client.js +22 -24
  22. package/esm/src/services/compaction.d.ts.map +1 -1
  23. package/esm/src/services/compaction.js +24 -33
  24. package/esm/src/services/constants.d.ts +7 -0
  25. package/esm/src/services/constants.d.ts.map +1 -0
  26. package/esm/src/services/constants.js +6 -0
  27. package/esm/src/services/context-limit.d.ts +1 -1
  28. package/esm/src/services/context-limit.d.ts.map +1 -1
  29. package/esm/src/services/context-limit.js +12 -14
  30. package/esm/src/services/context.d.ts +27 -7
  31. package/esm/src/services/context.d.ts.map +1 -1
  32. package/esm/src/services/context.js +34 -2
  33. package/esm/src/services/logger.js +2 -4
  34. package/esm/src/services/sdk-normalize.d.ts +55 -0
  35. package/esm/src/services/sdk-normalize.d.ts.map +1 -0
  36. package/esm/src/services/sdk-normalize.js +61 -0
  37. package/esm/src/session.d.ts +4 -2
  38. package/esm/src/session.d.ts.map +1 -1
  39. package/esm/src/session.js +38 -34
  40. package/esm/src/types/index.d.ts +13 -14
  41. package/esm/src/types/index.d.ts.map +1 -1
  42. package/esm/src/utils.d.ts +6 -0
  43. package/esm/src/utils.d.ts.map +1 -1
  44. package/esm/src/utils.js +16 -2
  45. package/package.json +1 -1
  46. package/script/_dnt.polyfills.d.ts +50 -0
  47. package/script/_dnt.polyfills.d.ts.map +1 -0
  48. package/script/_dnt.polyfills.js +38 -0
  49. package/script/mod.d.ts +1 -0
  50. package/script/mod.d.ts.map +1 -1
  51. package/script/mod.js +1 -0
  52. package/script/src/config.d.ts +7 -1
  53. package/script/src/config.d.ts.map +1 -1
  54. package/script/src/config.js +20 -5
  55. package/script/src/handlers/chat.d.ts.map +1 -1
  56. package/script/src/handlers/chat.js +49 -40
  57. package/script/src/handlers/event.d.ts +1 -1
  58. package/script/src/handlers/event.d.ts.map +1 -1
  59. package/script/src/handlers/event.js +41 -45
  60. package/script/src/handlers/messages.d.ts.map +1 -1
  61. package/script/src/handlers/messages.js +2 -3
  62. package/script/src/index.js +2 -2
  63. package/script/src/services/client.d.ts +7 -0
  64. package/script/src/services/client.d.ts.map +1 -1
  65. package/script/src/services/client.js +22 -24
  66. package/script/src/services/compaction.d.ts.map +1 -1
  67. package/script/src/services/compaction.js +24 -33
  68. package/script/src/services/constants.d.ts +7 -0
  69. package/script/src/services/constants.d.ts.map +1 -0
  70. package/script/src/services/constants.js +9 -0
  71. package/script/src/services/context-limit.d.ts +1 -1
  72. package/script/src/services/context-limit.d.ts.map +1 -1
  73. package/script/src/services/context-limit.js +13 -15
  74. package/script/src/services/context.d.ts +27 -7
  75. package/script/src/services/context.d.ts.map +1 -1
  76. package/script/src/services/context.js +36 -4
  77. package/script/src/services/logger.js +2 -4
  78. package/script/src/services/sdk-normalize.d.ts +55 -0
  79. package/script/src/services/sdk-normalize.d.ts.map +1 -0
  80. package/script/src/services/sdk-normalize.js +67 -0
  81. package/script/src/session.d.ts +4 -2
  82. package/script/src/session.d.ts.map +1 -1
  83. package/script/src/session.js +38 -34
  84. package/script/src/types/index.d.ts +13 -14
  85. package/script/src/types/index.d.ts.map +1 -1
  86. package/script/src/utils.d.ts +6 -0
  87. package/script/src/utils.d.ts.map +1 -1
  88. package/script/src/utils.js +18 -3
@@ -7,20 +7,17 @@ const logger_js_1 = require("../services/logger.js");
7
7
  const utils_js_1 = require("../utils.js");
8
8
  /** Creates the `event` hook handler. */
9
9
  function createEventHandler(deps) {
10
- const { sessionManager, client, defaultGroupId, sdkClient, directory, groupIdPrefix, } = deps;
11
- const defaultUserGroupId = (0, utils_js_1.makeUserGroupId)(groupIdPrefix);
12
- /** Stores the last successfully saved snapshot body per session ID. */
13
- const lastSnapshotBody = new Map();
10
+ const { sessionManager, client, defaultGroupId, defaultUserGroupId, sdkClient, directory, } = deps;
11
+ /** Per-handler context-limit cache — no cross-instance sharing. */
12
+ const contextLimitCache = new Map();
14
13
  const buildSessionSnapshot = (sessionId, messages) => {
15
14
  const recentMessages = messages.slice(-12);
16
- const recentAssistant = [...recentMessages]
17
- .reverse()
18
- .find((message) => message.startsWith("Assistant:"))
15
+ const recentAssistant = recentMessages
16
+ .findLast((message) => message.startsWith("Assistant:"))
19
17
  ?.replace(/^Assistant:\s*/, "")
20
18
  .trim();
21
- const recentUser = [...recentMessages]
22
- .reverse()
23
- .find((message) => message.startsWith("User:"))
19
+ const recentUser = recentMessages
20
+ .findLast((message) => message.startsWith("User:"))
24
21
  ?.replace(/^User:\s*/, "")
25
22
  .trim();
26
23
  const questionRegex = /[^\n\r?]{3,200}\?/g;
@@ -60,19 +57,7 @@ function createEventHandler(deps) {
60
57
  parentID: info.parentID,
61
58
  });
62
59
  if (isMain) {
63
- sessionManager.setState(sessionId, {
64
- groupId: defaultGroupId,
65
- userGroupId: defaultUserGroupId,
66
- injectedMemories: false,
67
- lastInjectionFactUuids: [],
68
- cachedMemoryContext: undefined,
69
- cachedFactUuids: undefined,
70
- visibleFactUuids: [],
71
- messageCount: 0,
72
- pendingMessages: [],
73
- contextLimit: 200_000,
74
- isMain,
75
- });
60
+ sessionManager.setState(sessionId, sessionManager.createDefaultState(defaultGroupId, defaultUserGroupId));
76
61
  }
77
62
  else {
78
63
  logger_js_1.logger.debug("Ignoring subagent session:", sessionId);
@@ -115,25 +100,32 @@ function createEventHandler(deps) {
115
100
  return;
116
101
  }
117
102
  try {
118
- const snapshotContent = buildSessionSnapshot(sessionId, state.pendingMessages);
119
- if (snapshotContent.trim()) {
120
- if (lastSnapshotBody.get(sessionId) === snapshotContent) {
121
- logger_js_1.logger.debug("Skipping duplicate session snapshot", {
122
- sessionId,
123
- });
124
- }
125
- else {
126
- await client.addEpisode({
127
- name: `Snapshot: ${sessionId}`,
128
- episodeBody: snapshotContent,
129
- groupId: state.groupId,
130
- source: "text",
131
- sourceDescription: "session-snapshot",
132
- });
133
- lastSnapshotBody.set(sessionId, snapshotContent);
134
- logger_js_1.logger.info("Saved session snapshot", { sessionId });
103
+ if (state.pendingMessages.length > 0) {
104
+ const snapshotContent = buildSessionSnapshot(sessionId, state.pendingMessages);
105
+ if (snapshotContent.trim()) {
106
+ if (state.lastSnapshotBody === snapshotContent) {
107
+ logger_js_1.logger.debug("Skipping duplicate session snapshot", {
108
+ sessionId,
109
+ });
110
+ }
111
+ else {
112
+ await client.addEpisode({
113
+ name: `Snapshot: ${sessionId}`,
114
+ episodeBody: snapshotContent,
115
+ groupId: state.groupId,
116
+ source: "text",
117
+ sourceDescription: "session-snapshot",
118
+ });
119
+ state.lastSnapshotBody = snapshotContent;
120
+ logger_js_1.logger.info("Saved session snapshot", { sessionId });
121
+ }
135
122
  }
136
123
  }
124
+ else {
125
+ logger_js_1.logger.debug("Skipping idle snapshot: no pending messages", {
126
+ sessionId,
127
+ });
128
+ }
137
129
  }
138
130
  catch (err) {
139
131
  logger_js_1.logger.error("Failed to save session snapshot", { sessionId, err });
@@ -175,11 +167,15 @@ function createEventHandler(deps) {
175
167
  return;
176
168
  sessionManager.finalizeAssistantMessage(state, sessionId, info.id, "message.updated");
177
169
  if (info.tokens && info.providerID && info.modelID) {
178
- (0, context_limit_js_1.resolveContextLimit)(info.providerID, info.modelID, sdkClient, directory)
179
- .then((limit) => {
180
- state.contextLimit = limit;
181
- })
182
- .catch((err) => logger_js_1.logger.debug("Failed to resolve context limit", err));
170
+ // Fire-and-forget: update contextLimit asynchronously without
171
+ // blocking event responsiveness. The state update is eventually
172
+ // consistent — a missed update only affects injection budget sizing,
173
+ // not correctness. We snapshot `state` here; if the session is
174
+ // deleted before the promise resolves the write is a harmless no-op.
175
+ const capturedState = state;
176
+ (0, context_limit_js_1.resolveContextLimit)(info.providerID, info.modelID, sdkClient, directory, contextLimitCache).then((limit) => {
177
+ capturedState.contextLimit = limit;
178
+ }).catch((err) => logger_js_1.logger.debug("Failed to resolve context limit", err));
183
179
  }
184
180
  return;
185
181
  }
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAGjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,qBAAqB,GAAG,WAAW,CACtC,KAAK,CAAC,sCAAsC,CAAC,CAC9C,CAAC;AACF,KAAK,sBAAsB,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,KAAK,uBAAuB,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,IAK3D,QAAQ,sBAAsB,EAC9B,QAAQ,uBAAuB,mBAkFlC"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAGjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,qBAAqB,GAAG,WAAW,CACtC,KAAK,CAAC,sCAAsC,CAAC,CAC9C,CAAC;AACF,KAAK,sBAAsB,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,KAAK,uBAAuB,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,IAK3D,QAAQ,sBAAsB,EAC9B,QAAQ,uBAAuB,mBAiFlC"}
@@ -7,9 +7,8 @@ function createMessagesHandler(deps) {
7
7
  const { sessionManager } = deps;
8
8
  // deno-lint-ignore require-await
9
9
  return async (_input, output) => {
10
- const lastUserEntry = [...output.messages]
11
- .reverse()
12
- .find((message) => message.info.role === "user");
10
+ const lastUserEntry = output.messages
11
+ .findLast((message) => message.info.role === "user");
13
12
  if (!lastUserEntry)
14
13
  return;
15
14
  const sessionID = lastUserEntry.info.sessionID;
@@ -14,7 +14,7 @@ const utils_js_1 = require("./utils.js");
14
14
  * OpenCode plugin entry point for Graphiti memory integration.
15
15
  */
16
16
  const graphiti = async (input) => {
17
- const config = (0, config_js_1.loadConfig)();
17
+ const config = (0, config_js_1.loadConfig)(input.directory);
18
18
  const client = new client_js_1.GraphitiClient(config.endpoint);
19
19
  const sdkClient = input.client;
20
20
  const connected = await client.connect();
@@ -31,9 +31,9 @@ const graphiti = async (input) => {
31
31
  sessionManager,
32
32
  client,
33
33
  defaultGroupId,
34
+ defaultUserGroupId,
34
35
  sdkClient,
35
36
  directory: input.directory,
36
- groupIdPrefix: config.groupIdPrefix,
37
37
  }),
38
38
  "chat.message": (0, chat_js_1.createChatHandler)({
39
39
  sessionManager,
@@ -41,6 +41,13 @@ export declare class GraphitiClient {
41
41
  source?: "text" | "json" | "message";
42
42
  sourceDescription?: string;
43
43
  }): Promise<void>;
44
+ /**
45
+ * Extract an array from a tool result that may be a bare array or a
46
+ * wrapped-array response object (`{ [key]: T[] }`).
47
+ * Returns the array when found, otherwise `null`.
48
+ * Public for testing.
49
+ */
50
+ parseWrappedArray<T>(result: unknown, wrappedKey: string): T[] | null;
44
51
  /**
45
52
  * Search Graphiti facts matching the provided query.
46
53
  */
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/src/services/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,YAAY,EACb,MAAM,mBAAmB,CAAC;AAI3B;;;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;;;;;OAKG;IACH,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI;IAYrE;;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;IAc3B;;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;IAc3B;;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;IAe9B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAQpC"}
@@ -8,6 +8,7 @@ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
8
8
  const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
9
9
  const deno_js_1 = __importDefault(require("../../deno.js"));
10
10
  const logger_js_1 = require("./logger.js");
11
+ const sdk_normalize_js_1 = require("./sdk-normalize.js");
11
12
  /**
12
13
  * Graphiti MCP client wrapper for connecting, querying,
13
14
  * and persisting episodes with basic reconnection handling.
@@ -173,6 +174,22 @@ class GraphitiClient {
173
174
  });
174
175
  logger_js_1.logger.debug("Added episode:", params.name);
175
176
  }
177
+ /**
178
+ * Extract an array from a tool result that may be a bare array or a
179
+ * wrapped-array response object (`{ [key]: T[] }`).
180
+ * Returns the array when found, otherwise `null`.
181
+ * Public for testing.
182
+ */
183
+ parseWrappedArray(result, wrappedKey) {
184
+ if (Array.isArray(result))
185
+ return result;
186
+ if (result &&
187
+ typeof result === "object" &&
188
+ Array.isArray(result[wrappedKey])) {
189
+ return result[wrappedKey];
190
+ }
191
+ return null;
192
+ }
176
193
  /**
177
194
  * Search Graphiti facts matching the provided query.
178
195
  */
@@ -183,14 +200,7 @@ class GraphitiClient {
183
200
  group_ids: params.groupIds,
184
201
  max_facts: params.maxFacts || 10,
185
202
  });
186
- if (Array.isArray(result))
187
- return result;
188
- if (result &&
189
- typeof result === "object" &&
190
- Array.isArray(result.facts)) {
191
- return result.facts;
192
- }
193
- return [];
203
+ return this.parseWrappedArray(result, "facts") ?? [];
194
204
  }
195
205
  catch (err) {
196
206
  logger_js_1.logger.error("searchFacts error:", err);
@@ -207,14 +217,7 @@ class GraphitiClient {
207
217
  group_ids: params.groupIds,
208
218
  max_nodes: params.maxNodes || 10,
209
219
  });
210
- if (Array.isArray(result))
211
- return result;
212
- if (result &&
213
- typeof result === "object" &&
214
- Array.isArray(result.nodes)) {
215
- return result.nodes;
216
- }
217
- return [];
220
+ return this.parseWrappedArray(result, "nodes") ?? [];
218
221
  }
219
222
  catch (err) {
220
223
  logger_js_1.logger.error("searchNodes error:", err);
@@ -230,14 +233,9 @@ class GraphitiClient {
230
233
  group_id: params.groupId,
231
234
  last_n: params.lastN,
232
235
  });
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 [];
236
+ const raw = this.parseWrappedArray(result, "episodes") ??
237
+ [];
238
+ return raw.map(sdk_normalize_js_1.normalizeEpisode);
241
239
  }
242
240
  catch (err) {
243
241
  logger_js_1.logger.error("getEpisodes error:", err);
@@ -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;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
+ {"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;AAgCpE,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;CAuB5B,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,CA4KpB"}
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.takeFactsWithinBudget = exports.classifyFacts = void 0;
4
4
  exports.handleCompaction = handleCompaction;
5
5
  exports.getCompactionContext = getCompactionContext;
6
+ const utils_js_1 = require("../utils.js");
6
7
  const context_js_1 = require("./context.js");
8
+ const constants_js_1 = require("./constants.js");
7
9
  const logger_js_1 = require("./logger.js");
8
- const DAY_MS = 24 * 60 * 60 * 1000;
9
10
  const DECISION_KEYWORDS = [
10
11
  "decided",
11
12
  "must",
@@ -22,19 +23,17 @@ const DECISION_KEYWORDS = [
22
23
  "design",
23
24
  "selected",
24
25
  ];
26
+ // Precompile keyword regex once, outside the fact-classification loop.
27
+ const DECISION_KEYWORD_REGEX = new RegExp(DECISION_KEYWORDS.map((kw) => `\\b${kw}\\b`).join("|"), "i");
25
28
  const classifyFacts = (facts, now) => {
26
29
  const decisions = [];
27
30
  const active = [];
28
31
  const background = [];
29
- const cutoff = now.getTime() - 7 * DAY_MS;
32
+ const cutoff = now.getTime() - 7 * constants_js_1.DAY_MS;
30
33
  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) {
34
+ const text = fact.fact;
35
+ // Task 7: use precompiled regex instead of building one per keyword per fact.
36
+ if (DECISION_KEYWORD_REGEX.test(text)) {
38
37
  decisions.push(fact);
39
38
  continue;
40
39
  }
@@ -106,7 +105,7 @@ async function getCompactionContext(params) {
106
105
  const projectFactsPromise = client.searchFacts({
107
106
  query: queryText,
108
107
  groupIds: [groupIds.project],
109
- maxFacts: 50,
108
+ maxFacts: constants_js_1.PROJECT_MAX_FACTS,
110
109
  });
111
110
  const projectNodesPromise = client.searchNodes({
112
111
  query: queryText,
@@ -128,26 +127,19 @@ async function getCompactionContext(params) {
128
127
  maxNodes: 10,
129
128
  })
130
129
  : Promise.resolve([]);
131
- const [projectFacts, projectNodes, userFacts, userNodes] = await Promise
132
- .all([
133
- projectFactsPromise,
134
- projectNodesPromise,
135
- userFactsPromise,
136
- userNodesPromise,
137
- ]);
130
+ const { projectContext, userContext, projectFacts, projectNodes, userFacts, userNodes, } = await (0, context_js_1.resolveProjectUserContext)({
131
+ projectFacts: projectFactsPromise,
132
+ projectNodes: projectNodesPromise,
133
+ userFacts: userFactsPromise,
134
+ userNodes: userNodesPromise,
135
+ });
138
136
  if (projectFacts.length === 0 && projectNodes.length === 0 &&
139
137
  userFacts.length === 0 && userNodes.length === 0) {
140
138
  return [];
141
139
  }
142
140
  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
- });
141
+ // Task 1: build section line-by-line; truncate at a line boundary to avoid
142
+ // cutting mid-tag or mid-line while still respecting the per-section budget.
151
143
  const buildSection = (header, facts, nodes, budget) => {
152
144
  const lines = [];
153
145
  lines.push(header);
@@ -179,7 +171,7 @@ async function getCompactionContext(params) {
179
171
  lines.push(...(0, context_js_1.formatNodeLines)(nodes));
180
172
  lines.push("</nodes>");
181
173
  }
182
- return lines.join("\n");
174
+ return (0, utils_js_1.truncateAtLineBoundary)(lines.join("\n"), budget);
183
175
  };
184
176
  const headerLines = [
185
177
  "<summary>",
@@ -204,20 +196,19 @@ async function getCompactionContext(params) {
204
196
  const userBudget = remainingBudget - projectBudget;
205
197
  const projectSection = buildSection('<memory source="project">', projectContext.facts, projectContext.nodes, projectBudget);
206
198
  const userSection = buildSection('<memory source="user">', userContext.facts, userContext.nodes, userBudget);
207
- const truncatedProject = projectSection.slice(0, projectBudget);
208
- const truncatedUser = userSection.slice(0, userBudget);
209
199
  const sections = [header];
210
- if (truncatedProject.trim()) {
211
- sections.push(truncatedProject);
200
+ if (projectSection.trim()) {
201
+ sections.push(projectSection);
212
202
  sections.push("</memory>");
213
203
  }
214
- if (truncatedUser.trim()) {
215
- sections.push(truncatedUser);
204
+ if (userSection.trim()) {
205
+ sections.push(userSection);
216
206
  sections.push("</memory>");
217
207
  }
218
208
  sections.push("</persistent_memory>");
219
209
  sections.push("</summary>");
220
- const content = sections.join("\n").slice(0, characterBudget);
210
+ // Final overall truncation at a line boundary.
211
+ const content = (0, utils_js_1.truncateAtLineBoundary)(sections.join("\n"), characterBudget);
221
212
  return [content];
222
213
  }
223
214
  catch (err) {
@@ -0,0 +1,7 @@
1
+ /** Milliseconds in one day. */
2
+ export declare const DAY_MS: number;
3
+ /** Default token context limit when the provider does not report one. */
4
+ export declare const DEFAULT_CONTEXT_LIMIT = 200000;
5
+ /** Maximum project-scope facts fetched per search query. */
6
+ export declare const PROJECT_MAX_FACTS = 50;
7
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/src/services/constants.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,eAAO,MAAM,MAAM,QAAsB,CAAC;AAE1C,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,SAAU,CAAC;AAE7C,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB,KAAK,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROJECT_MAX_FACTS = exports.DEFAULT_CONTEXT_LIMIT = exports.DAY_MS = void 0;
4
+ /** Milliseconds in one day. */
5
+ exports.DAY_MS = 24 * 60 * 60 * 1000;
6
+ /** Default token context limit when the provider does not report one. */
7
+ exports.DEFAULT_CONTEXT_LIMIT = 200_000;
8
+ /** Maximum project-scope facts fetched per search query. */
9
+ exports.PROJECT_MAX_FACTS = 50;
@@ -1,5 +1,5 @@
1
1
  import type { OpencodeClient } from "@opencode-ai/sdk";
2
- export declare function resolveContextLimit(providerID: string, modelID: string, client: OpencodeClient, directory: string): Promise<number>;
2
+ export declare function resolveContextLimit(providerID: string, modelID: string, client: OpencodeClient, directory: string, cache: Map<string, number>): Promise<number>;
3
3
  /**
4
4
  * Calculate the character budget for memory injection
5
5
  * (5% of context limit * 4 chars/token).
@@ -1 +1 @@
1
- {"version":3,"file":"context-limit.d.ts","sourceRoot":"","sources":["../../../src/src/services/context-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOvD,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAiCjB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErE"}
1
+ {"version":3,"file":"context-limit.d.ts","sourceRoot":"","sources":["../../../src/src/services/context-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAKvD,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACzB,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErE"}
@@ -2,31 +2,29 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveContextLimit = resolveContextLimit;
4
4
  exports.calculateInjectionBudget = calculateInjectionBudget;
5
+ const constants_js_1 = require("./constants.js");
5
6
  const logger_js_1 = require("./logger.js");
6
- const DEFAULT_CONTEXT_LIMIT = 200_000;
7
- const contextLimitCache = new Map();
8
- async function resolveContextLimit(providerID, modelID, client, directory) {
7
+ const sdk_normalize_js_1 = require("./sdk-normalize.js");
8
+ async function resolveContextLimit(providerID, modelID, client, directory, cache) {
9
9
  const modelKey = `${providerID}/${modelID}`;
10
- const cached = contextLimitCache.get(modelKey);
10
+ const cached = cache.get(modelKey);
11
11
  if (cached)
12
12
  return cached;
13
13
  try {
14
- const providers = await client.provider.list({
14
+ const response = await client.provider.list({
15
15
  query: { directory },
16
16
  });
17
- const list = providers.providers ?? [];
17
+ const list = (0, sdk_normalize_js_1.extractSdkProviders)(response);
18
18
  for (const provider of list) {
19
- const providerInfo = provider;
20
- if (providerInfo.id !== providerID)
19
+ if (provider.id !== providerID)
21
20
  continue;
22
- const models = providerInfo.models ?? [];
21
+ const models = provider.models ?? [];
23
22
  for (const model of models) {
24
- const modelInfo = model;
25
- if (modelInfo.id !== modelID)
23
+ if (model.id !== modelID)
26
24
  continue;
27
- const contextLimit = modelInfo.limit?.context;
25
+ const contextLimit = model.limit?.context;
28
26
  if (typeof contextLimit === "number" && contextLimit > 0) {
29
- contextLimitCache.set(modelKey, contextLimit);
27
+ cache.set(modelKey, contextLimit);
30
28
  return contextLimit;
31
29
  }
32
30
  }
@@ -35,8 +33,8 @@ async function resolveContextLimit(providerID, modelID, client, directory) {
35
33
  catch (err) {
36
34
  logger_js_1.logger.warn("Failed to fetch provider context limit", err);
37
35
  }
38
- contextLimitCache.set(modelKey, DEFAULT_CONTEXT_LIMIT);
39
- return DEFAULT_CONTEXT_LIMIT;
36
+ cache.set(modelKey, constants_js_1.DEFAULT_CONTEXT_LIMIT);
37
+ return constants_js_1.DEFAULT_CONTEXT_LIMIT;
40
38
  }
41
39
  /**
42
40
  * Calculate the character budget for memory injection
@@ -16,13 +16,33 @@ export declare const formatNodeLines: (nodes: GraphitiNode[]) => string[];
16
16
  export declare const deduplicateFactsByUuid: (facts: GraphitiFact[]) => GraphitiFact[];
17
17
  export declare const deduplicateNodesByUuid: (nodes: GraphitiNode[]) => GraphitiNode[];
18
18
  export declare const removeNodesReferencedByFacts: (facts: GraphitiFact[], nodes: GraphitiNode[]) => GraphitiNode[];
19
- export declare const deduplicateContext: (params: {
20
- facts: GraphitiFact[];
21
- nodes: GraphitiNode[];
22
- }) => {
23
- facts: GraphitiFact[];
24
- nodes: GraphitiNode[];
25
- };
19
+ /**
20
+ * Await four parallel fact/node promises, deduplicate each side, and return
21
+ * the resolved project and user contexts.
22
+ *
23
+ * Callers construct the promises themselves — this lets chat.ts seed the
24
+ * project-facts promise from an earlier drift-check fetch without issuing a
25
+ * duplicate network request.
26
+ */
27
+ export declare function resolveProjectUserContext(promises: {
28
+ projectFacts: Promise<GraphitiFact[]>;
29
+ projectNodes: Promise<GraphitiNode[]>;
30
+ userFacts: Promise<GraphitiFact[]>;
31
+ userNodes: Promise<GraphitiNode[]>;
32
+ }): Promise<{
33
+ projectContext: {
34
+ facts: GraphitiFact[];
35
+ nodes: GraphitiNode[];
36
+ };
37
+ userContext: {
38
+ facts: GraphitiFact[];
39
+ nodes: GraphitiNode[];
40
+ };
41
+ projectFacts: GraphitiFact[];
42
+ projectNodes: GraphitiNode[];
43
+ userFacts: GraphitiFact[];
44
+ userNodes: GraphitiNode[];
45
+ }>;
26
46
  /**
27
47
  * Format Graphiti facts and nodes into a user-facing context block.
28
48
  */
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/src/services/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIpE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,IAAI,GAAG,IAKjD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,YAAY,EAAE,KAAK,IAAI,KAAG,OAQ7D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,MAAM,YAAY,EAClB,KAAK,IAAI,EACT,eAAe,MAAM,KACpB,YAUF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,YAAY,EAAE,KAAG,YAAY,EAWtE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,YAAY,EAMd,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,YAAY,KAAG,MAMnD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,EAAE,KAAG,MAAM,EAK1D,CAAC;AAEL,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,OAAO,YAAY,EAAE,EACrB,OAAO,YAAY,EAAE,KACpB,YAAY,EAOd,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,QAAQ;IACzC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,KAAG;IAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAQjD,CAAC;AAEF;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EAAE,EACrB,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,CAAC,EAAE;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GACA,MAAM,CA6BR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAW1D"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/src/services/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGpE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,IAAI,GAAG,IAKjD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,YAAY,EAAE,KAAK,IAAI,KAAG,OAQ7D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,MAAM,YAAY,EAClB,KAAK,IAAI,EACT,eAAe,MAAM,KACpB,YAUF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,YAAY,EAAE,KAAG,YAAY,EAWtE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,YAAY,EAMd,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,YAAY,KAAG,MAMnD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,EAAE,KAAG,MAAM,EAK1D,CAAC;AAEL,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,OAAO,YAAY,EAAE,EACrB,OAAO,YAAY,EAAE,KACpB,YAAY,EAOd,CAAC;AAeF;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE;IACxD,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACtC,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACtC,SAAS,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CACpC,GAAG,OAAO,CAAC;IACV,cAAc,EAAE;QAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QAAC,KAAK,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IACjE,WAAW,EAAE;QAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QAAC,KAAK,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IAC9D,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,SAAS,EAAE,YAAY,EAAE,CAAC;CAC3B,CAAC,CAyBD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EAAE,EACrB,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,CAAC,EAAE;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GACA,MAAM,CA6BR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAW1D"}
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deduplicateContext = exports.removeNodesReferencedByFacts = exports.deduplicateNodesByUuid = exports.deduplicateFactsByUuid = exports.formatNodeLines = exports.formatFactLines = exports.formatFactLine = exports.filterAndAnnotateFacts = exports.sortFactsByRecency = exports.annotateStaleFact = exports.isFactInvalid = exports.parseDate = void 0;
3
+ exports.removeNodesReferencedByFacts = exports.deduplicateNodesByUuid = exports.deduplicateFactsByUuid = exports.formatNodeLines = exports.formatFactLines = exports.formatFactLine = exports.filterAndAnnotateFacts = exports.sortFactsByRecency = exports.annotateStaleFact = exports.isFactInvalid = exports.parseDate = void 0;
4
+ exports.resolveProjectUserContext = resolveProjectUserContext;
4
5
  exports.formatMemoryContext = formatMemoryContext;
5
6
  exports.extractVisibleUuids = extractVisibleUuids;
6
- const DAY_MS = 24 * 60 * 60 * 1000;
7
+ const constants_js_1 = require("./constants.js");
7
8
  const parseDate = (value) => {
8
9
  if (!value)
9
10
  return null;
@@ -27,7 +28,7 @@ const annotateStaleFact = (fact, now, factStaleDays) => {
27
28
  const validAt = (0, exports.parseDate)(fact.valid_at);
28
29
  if (!validAt)
29
30
  return fact;
30
- const ageDays = Math.floor((now.getTime() - validAt.getTime()) / DAY_MS);
31
+ const ageDays = Math.floor((now.getTime() - validAt.getTime()) / constants_js_1.DAY_MS);
31
32
  if (ageDays < 0)
32
33
  return fact;
33
34
  if (ageDays < factStaleDays)
@@ -122,7 +123,38 @@ const deduplicateContext = (params) => {
122
123
  const filteredNodes = (0, exports.removeNodesReferencedByFacts)(dedupedFacts, dedupedNodes);
123
124
  return { facts: dedupedFacts, nodes: filteredNodes };
124
125
  };
125
- exports.deduplicateContext = deduplicateContext;
126
+ /**
127
+ * Await four parallel fact/node promises, deduplicate each side, and return
128
+ * the resolved project and user contexts.
129
+ *
130
+ * Callers construct the promises themselves — this lets chat.ts seed the
131
+ * project-facts promise from an earlier drift-check fetch without issuing a
132
+ * duplicate network request.
133
+ */
134
+ async function resolveProjectUserContext(promises) {
135
+ const [projectFacts, projectNodes, userFacts, userNodes] = await Promise.all([
136
+ promises.projectFacts,
137
+ promises.projectNodes,
138
+ promises.userFacts,
139
+ promises.userNodes,
140
+ ]);
141
+ const projectContext = deduplicateContext({
142
+ facts: projectFacts,
143
+ nodes: projectNodes,
144
+ });
145
+ const userContext = deduplicateContext({
146
+ facts: userFacts,
147
+ nodes: userNodes,
148
+ });
149
+ return {
150
+ projectContext,
151
+ userContext,
152
+ projectFacts,
153
+ projectNodes,
154
+ userFacts,
155
+ userNodes,
156
+ };
157
+ }
126
158
  /**
127
159
  * Format Graphiti facts and nodes into a user-facing context block.
128
160
  */
@@ -13,12 +13,10 @@ exports.logger = {
13
13
  console.log(PREFIX, ...args);
14
14
  },
15
15
  warn: (...args) => {
16
- if (node_process_1.default.env.GRAPHITI_DEBUG)
17
- console.warn(PREFIX, ...args);
16
+ console.warn(PREFIX, ...args);
18
17
  },
19
18
  error: (...args) => {
20
- if (node_process_1.default.env.GRAPHITI_DEBUG)
21
- console.error(PREFIX, ...args);
19
+ console.error(PREFIX, ...args);
22
20
  },
23
21
  debug: (...args) => {
24
22
  if (node_process_1.default.env.GRAPHITI_DEBUG)