opencode-graphiti 0.1.9 → 0.1.11

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 +10 -1
  9. package/esm/src/config.d.ts.map +1 -1
  10. package/esm/src/config.js +30 -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 +8 -0
  20. package/esm/src/services/client.d.ts.map +1 -1
  21. package/esm/src/services/client.js +44 -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 +10 -1
  53. package/script/src/config.d.ts.map +1 -1
  54. package/script/src/config.js +33 -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 +8 -0
  64. package/script/src/services/client.d.ts.map +1 -1
  65. package/script/src/services/client.js +44 -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,
@@ -25,6 +25,7 @@ export declare class GraphitiClient {
25
25
  disconnect(): Promise<void>;
26
26
  private callTool;
27
27
  private isSessionExpired;
28
+ private isRequestTimeout;
28
29
  private reconnect;
29
30
  /**
30
31
  * Parse MCP tool results into JSON when possible.
@@ -41,6 +42,13 @@ export declare class GraphitiClient {
41
42
  source?: "text" | "json" | "message";
42
43
  sourceDescription?: string;
43
44
  }): Promise<void>;
45
+ /**
46
+ * Extract an array from a tool result that may be a bare array or a
47
+ * wrapped-array response object (`{ [key]: T[] }`).
48
+ * Returns the array when found, otherwise `null`.
49
+ * Public for testing.
50
+ */
51
+ parseWrappedArray<T>(result: unknown, wrappedKey: string): T[] | null;
44
52
  /**
45
53
  * Search Graphiti facts matching the provided query.
46
54
  */
@@ -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;IASxB,OAAO,CAAC,gBAAgB;YAgBV,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;IAkB3B;;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;IAkB3B;;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;IAmB9B;;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.
@@ -120,6 +121,16 @@ class GraphitiClient {
120
121
  "code" in err &&
121
122
  err.code === 404);
122
123
  }
124
+ isRequestTimeout(err) {
125
+ if (typeof err === "string") {
126
+ return /request timed out/i.test(err);
127
+ }
128
+ if (!err || typeof err !== "object")
129
+ return false;
130
+ const { code, message } = err;
131
+ return code === -32001 ||
132
+ (typeof message === "string" && /request timed out/i.test(message));
133
+ }
123
134
  async reconnect() {
124
135
  this.connected = false;
125
136
  try {
@@ -173,6 +184,22 @@ class GraphitiClient {
173
184
  });
174
185
  logger_js_1.logger.debug("Added episode:", params.name);
175
186
  }
187
+ /**
188
+ * Extract an array from a tool result that may be a bare array or a
189
+ * wrapped-array response object (`{ [key]: T[] }`).
190
+ * Returns the array when found, otherwise `null`.
191
+ * Public for testing.
192
+ */
193
+ parseWrappedArray(result, wrappedKey) {
194
+ if (Array.isArray(result))
195
+ return result;
196
+ if (result &&
197
+ typeof result === "object" &&
198
+ Array.isArray(result[wrappedKey])) {
199
+ return result[wrappedKey];
200
+ }
201
+ return null;
202
+ }
176
203
  /**
177
204
  * Search Graphiti facts matching the provided query.
178
205
  */
@@ -183,16 +210,13 @@ class GraphitiClient {
183
210
  group_ids: params.groupIds,
184
211
  max_facts: params.maxFacts || 10,
185
212
  });
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 [];
213
+ return this.parseWrappedArray(result, "facts") ?? [];
194
214
  }
195
215
  catch (err) {
216
+ if (this.isRequestTimeout(err)) {
217
+ logger_js_1.logger.warn("searchFacts request timed out; returning no facts");
218
+ return [];
219
+ }
196
220
  logger_js_1.logger.error("searchFacts error:", err);
197
221
  return [];
198
222
  }
@@ -207,16 +231,13 @@ class GraphitiClient {
207
231
  group_ids: params.groupIds,
208
232
  max_nodes: params.maxNodes || 10,
209
233
  });
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 [];
234
+ return this.parseWrappedArray(result, "nodes") ?? [];
218
235
  }
219
236
  catch (err) {
237
+ if (this.isRequestTimeout(err)) {
238
+ logger_js_1.logger.warn("searchNodes request timed out; returning no nodes");
239
+ return [];
240
+ }
220
241
  logger_js_1.logger.error("searchNodes error:", err);
221
242
  return [];
222
243
  }
@@ -230,16 +251,15 @@ class GraphitiClient {
230
251
  group_id: params.groupId,
231
252
  last_n: params.lastN,
232
253
  });
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 [];
254
+ const raw = this.parseWrappedArray(result, "episodes") ??
255
+ [];
256
+ return raw.map(sdk_normalize_js_1.normalizeEpisode);
241
257
  }
242
258
  catch (err) {
259
+ if (this.isRequestTimeout(err)) {
260
+ logger_js_1.logger.warn("getEpisodes request timed out; returning no episodes");
261
+ return [];
262
+ }
243
263
  logger_js_1.logger.error("getEpisodes error:", err);
244
264
  return [];
245
265
  }
@@ -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
  */