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
package/README.md CHANGED
@@ -99,7 +99,13 @@ automatically.
99
99
 
100
100
  ## Configuration
101
101
 
102
- Create a config file at `~/.config/opencode/graphiti.jsonc`:
102
+ Supported config locations, in lookup order:
103
+
104
+ 1. The provided project directory: `package.json#graphiti`, `.graphitirc`, and other standard `cosmiconfig` `graphiti` filenames
105
+ 2. Standard global/home `graphiti` config locations discovered by `cosmiconfig` (for example `~/.graphitirc`)
106
+ 3. Legacy fallback: `~/.config/opencode/.graphitirc`
107
+
108
+ Example `.graphitirc`:
103
109
 
104
110
  ```jsonc
105
111
  {
@@ -0,0 +1,50 @@
1
+ declare global {
2
+ interface Array<T> {
3
+ /**
4
+ * Returns the value of the last element in the array where predicate is true, and undefined
5
+ * otherwise.
6
+ * @param predicate find calls predicate once for each element of the array, in ascending
7
+ * order, until it finds one where predicate returns true. If such an element is found, find
8
+ * immediately returns that element value. Otherwise, find returns undefined.
9
+ * @param thisArg If provided, it will be used as the this value for each invocation of
10
+ * predicate. If it is not provided, undefined is used instead.
11
+ */
12
+ findLast<S extends T>(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
13
+ findLast(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
14
+ /**
15
+ * Returns the index of the last element in the array where predicate is true, and -1
16
+ * otherwise.
17
+ * @param predicate find calls predicate once for each element of the array, in ascending
18
+ * order, until it finds one where predicate returns true. If such an element is found,
19
+ * findIndex immediately returns that element index. Otherwise, findIndex returns -1.
20
+ * @param thisArg If provided, it will be used as the this value for each invocation of
21
+ * predicate. If it is not provided, undefined is used instead.
22
+ */
23
+ findLastIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number;
24
+ }
25
+ interface Uint8Array {
26
+ /**
27
+ * Returns the value of the last element in the array where predicate is true, and undefined
28
+ * otherwise.
29
+ * @param predicate findLast calls predicate once for each element of the array, in descending
30
+ * order, until it finds one where predicate returns true. If such an element is found, findLast
31
+ * immediately returns that element value. Otherwise, findLast returns undefined.
32
+ * @param thisArg If provided, it will be used as the this value for each invocation of
33
+ * predicate. If it is not provided, undefined is used instead.
34
+ */
35
+ findLast<S extends number>(predicate: (value: number, index: number, array: Uint8Array) => value is S, thisArg?: any): S | undefined;
36
+ findLast(predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number | undefined;
37
+ /**
38
+ * Returns the index of the last element in the array where predicate is true, and -1
39
+ * otherwise.
40
+ * @param predicate findLastIndex calls predicate once for each element of the array, in descending
41
+ * order, until it finds one where predicate returns true. If such an element is found,
42
+ * findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
43
+ * @param thisArg If provided, it will be used as the this value for each invocation of
44
+ * predicate. If it is not provided, undefined is used instead.
45
+ */
46
+ findLastIndex(predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number;
47
+ }
48
+ }
49
+ export {};
50
+ //# sourceMappingURL=_dnt.polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"AACA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,KAAK,CAAC,CAAC;QACf;;;;;;;;WAQG;QACH,QAAQ,CAAC,CAAC,SAAS,CAAC,EAClB,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC,EACxE,OAAO,CAAC,EAAE,GAAG,GACZ,CAAC,GAAG,SAAS,CAAC;QACjB,QAAQ,CACN,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,OAAO,EACzD,OAAO,CAAC,EAAE,GAAG,GACZ,CAAC,GAAG,SAAS,CAAC;QAEjB;;;;;;;;WAQG;QACH,aAAa,CACX,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,OAAO,EACzD,OAAO,CAAC,EAAE,GAAG,GACZ,MAAM,CAAC;KACX;IACD,UAAU,UAAU;QAClB;;;;;;;;WAQG;QACH,QAAQ,CAAC,CAAC,SAAS,MAAM,EACvB,SAAS,EAAE,CACP,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,UAAU,KAChB,KAAK,IAAI,CAAC,EACf,OAAO,CAAC,EAAE,GAAG,GACZ,CAAC,GAAG,SAAS,CAAC;QACjB,QAAQ,CACJ,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,EACvE,OAAO,CAAC,EAAE,GAAG,GACd,MAAM,GAAG,SAAS,CAAC;QAEtB;;;;;;;;WAQG;QACH,aAAa,CACT,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,EACvE,OAAO,CAAC,EAAE,GAAG,GACd,MAAM,CAAC;KACX;CACF;AA4CD,OAAO,EAAE,CAAC"}
@@ -0,0 +1,37 @@
1
+ function findLastIndex(self, callbackfn, that) {
2
+ const boundFunc = that === undefined ? callbackfn : callbackfn.bind(that);
3
+ let index = self.length - 1;
4
+ while (index >= 0) {
5
+ const result = boundFunc(self[index], index, self);
6
+ if (result) {
7
+ return index;
8
+ }
9
+ index--;
10
+ }
11
+ return -1;
12
+ }
13
+ function findLast(self, callbackfn, that) {
14
+ const index = self.findLastIndex(callbackfn, that);
15
+ return index === -1 ? undefined : self[index];
16
+ }
17
+ if (!Array.prototype.findLastIndex) {
18
+ Array.prototype.findLastIndex = function (callbackfn, that) {
19
+ return findLastIndex(this, callbackfn, that);
20
+ };
21
+ }
22
+ if (!Array.prototype.findLast) {
23
+ Array.prototype.findLast = function (callbackfn, that) {
24
+ return findLast(this, callbackfn, that);
25
+ };
26
+ }
27
+ if (!Uint8Array.prototype.findLastIndex) {
28
+ Uint8Array.prototype.findLastIndex = function (callbackfn, that) {
29
+ return findLastIndex(this, callbackfn, that);
30
+ };
31
+ }
32
+ if (!Uint8Array.prototype.findLast) {
33
+ Uint8Array.prototype.findLast = function (callbackfn, that) {
34
+ return findLast(this, callbackfn, that);
35
+ };
36
+ }
37
+ export {};
package/esm/mod.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ import "./_dnt.polyfills.js";
1
2
  export * from "./src/index.js";
2
3
  //# sourceMappingURL=mod.d.ts.map
package/esm/mod.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
package/esm/mod.js CHANGED
@@ -1 +1,2 @@
1
+ import "./_dnt.polyfills.js";
1
2
  export * from "./src/index.js";
@@ -1,6 +1,15 @@
1
1
  import type { GraphitiConfig } from "./types/index.js";
2
2
  /**
3
3
  * Load Graphiti configuration from JSONC files with defaults applied.
4
+ *
5
+ * Lookup order:
6
+ * 1. `directory` (if provided): standard cosmiconfig search starting from that
7
+ * directory (no upward traversal past it) — project-local `.graphitirc`,
8
+ * `package.json#graphiti`, etc.
9
+ * 2. Standard global/home cosmiconfig locations discovered by walking upward
10
+ * from CWD to the home directory (e.g. `~/.graphitirc`).
11
+ * 3. Legacy fallback: `~/.config/opencode/.graphitirc` — the path used by
12
+ * earlier versions of the plugin.
4
13
  */
5
- export declare function loadConfig(): GraphitiConfig;
14
+ export declare function loadConfig(directory?: string): GraphitiConfig;
6
15
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAgBvD;;GAEG;AACH,wBAAgB,UAAU,IAAI,cAAc,CAc3C"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/src/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAgBvD;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,cAAc,CA8B7D"}
package/esm/src/config.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { cosmiconfigSync } from "cosmiconfig";
2
+ import os from "node:os";
2
3
  import * as z from "zod/mini";
3
4
  const DEFAULT_CONFIG = {
4
5
  endpoint: "http://localhost:8000/mcp",
@@ -14,14 +15,38 @@ const GraphitiConfigSchema = z.object({
14
15
  });
15
16
  /**
16
17
  * Load Graphiti configuration from JSONC files with defaults applied.
18
+ *
19
+ * Lookup order:
20
+ * 1. `directory` (if provided): standard cosmiconfig search starting from that
21
+ * directory (no upward traversal past it) — project-local `.graphitirc`,
22
+ * `package.json#graphiti`, etc.
23
+ * 2. Standard global/home cosmiconfig locations discovered by walking upward
24
+ * from CWD to the home directory (e.g. `~/.graphitirc`).
25
+ * 3. Legacy fallback: `~/.config/opencode/.graphitirc` — the path used by
26
+ * earlier versions of the plugin.
17
27
  */
18
- export function loadConfig() {
19
- const explorer = cosmiconfigSync("graphiti", { searchStrategy: "global" });
20
- const result = explorer.search();
21
- const candidate = result?.config ?? {};
28
+ export function loadConfig(directory) {
29
+ const explorer = cosmiconfigSync("graphiti", {
30
+ stopDir: os.homedir(),
31
+ mergeSearchPlaces: true,
32
+ cache: false,
33
+ });
34
+ // Step 1 & 2: project-local search (with directory arg) or CWD upward walk.
35
+ const result = explorer.search(directory) ??
36
+ (() => {
37
+ // Step 3: legacy fallback — load the fixed path explicitly so that
38
+ // cosmiconfig's search-place joining does not mangle absolute paths.
39
+ const legacyPath = `${os.homedir()}/.config/opencode/.graphitirc`;
40
+ try {
41
+ return cosmiconfigSync("graphiti", { cache: false }).load(legacyPath);
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ })();
22
47
  const merged = {
23
48
  ...DEFAULT_CONFIG,
24
- ...candidate,
49
+ ...result?.config,
25
50
  };
26
51
  const parsed = GraphitiConfigSchema.safeParse(merged);
27
52
  if (parsed.success) {
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAO5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;AAC1D,KAAK,gBAAgB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,iBAAiB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,IAkLvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBA6EzE"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAQ5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;AAC1D,KAAK,gBAAgB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,iBAAiB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAGxD,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,IAwMvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBA+EzE"}
@@ -1,17 +1,30 @@
1
1
  import { calculateInjectionBudget } from "../services/context-limit.js";
2
- import { deduplicateContext, formatMemoryContext, } from "../services/context.js";
2
+ import { PROJECT_MAX_FACTS } from "../services/constants.js";
3
+ import { formatMemoryContext, resolveProjectUserContext, } from "../services/context.js";
3
4
  import { logger } from "../services/logger.js";
4
- import { extractTextFromParts } from "../utils.js";
5
+ import { extractTextFromParts, truncateAtLineBoundary } from "../utils.js";
5
6
  /** Creates the `chat.message` hook handler. */
6
7
  export function createChatHandler(deps) {
7
8
  const { sessionManager, driftThreshold, factStaleDays, client } = deps;
8
- const searchAndCacheMemoryContext = async (state, messageText, useUserScope, characterBudget, seedFactUuids) => {
9
+ /**
10
+ * Fetch project facts (and optionally user facts/nodes) then build and cache
11
+ * the formatted memory context string.
12
+ *
13
+ * Task 8: When `seedProjectFacts` is supplied (from the drift check), those
14
+ * facts are used directly for the project scope so we avoid a redundant
15
+ * second searchFacts query.
16
+ */
17
+ const searchAndCacheMemoryContext = async (state, messageText, useUserScope, characterBudget, seedProjectFacts) => {
9
18
  const userGroupId = state.userGroupId;
10
- const projectFactsPromise = client.searchFacts({
11
- query: messageText,
12
- groupIds: [state.groupId],
13
- maxFacts: 50,
14
- });
19
+ // Task 8: reuse drift-check project facts when available; only issue a new
20
+ // project searchFacts call when we don't already have them.
21
+ const projectFactsPromise = seedProjectFacts != null
22
+ ? Promise.resolve(seedProjectFacts)
23
+ : client.searchFacts({
24
+ query: messageText,
25
+ groupIds: [state.groupId],
26
+ maxFacts: PROJECT_MAX_FACTS,
27
+ });
15
28
  const projectNodesPromise = client.searchNodes({
16
29
  query: messageText,
17
30
  groupIds: [state.groupId],
@@ -31,20 +44,11 @@ export function createChatHandler(deps) {
31
44
  maxNodes: 10,
32
45
  })
33
46
  : Promise.resolve([]);
34
- const [projectFacts, projectNodes, userFacts, userNodes] = await Promise
35
- .all([
36
- projectFactsPromise,
37
- projectNodesPromise,
38
- userFactsPromise,
39
- userNodesPromise,
40
- ]);
41
- const projectContext = deduplicateContext({
42
- facts: projectFacts,
43
- nodes: projectNodes,
44
- });
45
- const userContext = deduplicateContext({
46
- facts: userFacts,
47
- nodes: userNodes,
47
+ const { projectContext, userContext, projectFacts, projectNodes, userFacts, userNodes, } = await resolveProjectUserContext({
48
+ projectFacts: projectFactsPromise,
49
+ projectNodes: projectNodesPromise,
50
+ userFacts: userFactsPromise,
51
+ userNodes: userNodesPromise,
48
52
  });
49
53
  const visibleSet = new Set(state.visibleFactUuids ?? []);
50
54
  const beforeProjectFacts = projectContext.facts.length;
@@ -81,8 +85,7 @@ export function createChatHandler(deps) {
81
85
  });
82
86
  const snapshot = episodes
83
87
  .filter((episode) => {
84
- const description = episode.sourceDescription ??
85
- episode.source_description ?? "";
88
+ const description = episode.sourceDescription ?? "";
86
89
  return description === "session-snapshot";
87
90
  })
88
91
  .sort((a, b) => {
@@ -91,12 +94,14 @@ export function createChatHandler(deps) {
91
94
  return bTime - aTime;
92
95
  })[0];
93
96
  if (snapshot?.content) {
97
+ // Task 2: truncate snapshot at a line boundary.
94
98
  const snapshotBudget = Math.min(characterBudget, 1200);
99
+ const snapshotBody = truncateAtLineBoundary(snapshot.content, snapshotBudget);
95
100
  snapshotPrimer = [
96
101
  "## Session Snapshot",
97
102
  "> Most recent session snapshot; use to restore active strategy and open questions.",
98
103
  "",
99
- snapshot.content.slice(0, snapshotBudget),
104
+ snapshotBody,
100
105
  ].join("\n");
101
106
  }
102
107
  }
@@ -104,25 +109,27 @@ export function createChatHandler(deps) {
104
109
  logger.error("Failed to load session snapshot", { err });
105
110
  }
106
111
  }
112
+ // Task 2: truncate project/user context strings at line boundaries.
107
113
  const projectBudget = useUserScope
108
114
  ? Math.floor(characterBudget * 0.7)
109
115
  : characterBudget;
110
116
  const userBudget = characterBudget - projectBudget;
111
- const truncatedProject = projectContextString.slice(0, projectBudget);
117
+ const truncatedProject = truncateAtLineBoundary(projectContextString, projectBudget);
112
118
  const truncatedUser = useUserScope
113
- ? userContextString.slice(0, userBudget)
119
+ ? truncateAtLineBoundary(userContextString, userBudget)
114
120
  : "";
115
- const memoryContext = [snapshotPrimer, truncatedProject, truncatedUser]
121
+ // Task 2: final combined context also truncated at a line boundary.
122
+ const combined = [snapshotPrimer, truncatedProject, truncatedUser]
116
123
  .filter((section) => section.trim().length > 0)
117
- .join("\n\n")
118
- .slice(0, characterBudget);
124
+ .join("\n\n");
125
+ const memoryContext = truncateAtLineBoundary(combined, characterBudget);
119
126
  if (!memoryContext)
120
127
  return;
121
128
  const allFactUuids = [
122
129
  ...projectContext.facts.map((fact) => fact.uuid),
123
130
  ...userContext.facts.map((fact) => fact.uuid),
124
131
  ];
125
- const factUuids = seedFactUuids ?? Array.from(new Set(allFactUuids));
132
+ const factUuids = Array.from(new Set(allFactUuids));
126
133
  state.cachedMemoryContext = memoryContext;
127
134
  state.cachedFactUuids = factUuids;
128
135
  logger.info(`Cached ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes for user message injection`);
@@ -142,10 +149,6 @@ export function createChatHandler(deps) {
142
149
  return union === 0 ? 1 : intersection / union;
143
150
  };
144
151
  return async ({ sessionID }, output) => {
145
- if (await sessionManager.isSubagentSession(sessionID)) {
146
- logger.debug("Ignoring subagent chat message:", sessionID);
147
- return;
148
- }
149
152
  const { state, resolved } = await sessionManager.resolveSessionState(sessionID);
150
153
  if (!resolved) {
151
154
  logger.debug("Unable to resolve session for message:", { sessionID });
@@ -166,18 +169,20 @@ export function createChatHandler(deps) {
166
169
  messageLength: messageText.length,
167
170
  });
168
171
  const shouldInjectOnFirst = !state.injectedMemories;
169
- let shouldReinject = false;
170
- let currentFactUuids = null;
172
+ // Task 8: driftFacts from the drift check are passed into
173
+ // searchAndCacheMemoryContext so the project searchFacts is not repeated.
174
+ let driftProjectFacts = null;
171
175
  if (!shouldInjectOnFirst) {
172
176
  try {
173
- const driftFacts = await client.searchFacts({
177
+ const fetched = await client.searchFacts({
174
178
  query: messageText,
175
179
  groupIds: [state.groupId],
176
- maxFacts: 20,
180
+ maxFacts: PROJECT_MAX_FACTS,
177
181
  });
178
- currentFactUuids = driftFacts.map((fact) => fact.uuid);
182
+ driftProjectFacts = fetched;
183
+ const currentFactUuids = fetched.map((fact) => fact.uuid);
179
184
  const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
180
- shouldReinject = similarity < driftThreshold;
185
+ const shouldReinject = similarity < driftThreshold;
181
186
  if (!shouldReinject) {
182
187
  logger.debug("Skipping reinjection; similarity above threshold", {
183
188
  sessionID,
@@ -197,7 +202,11 @@ export function createChatHandler(deps) {
197
202
  try {
198
203
  const useUserScope = shouldInjectOnFirst;
199
204
  const characterBudget = calculateInjectionBudget(state.contextLimit);
200
- await searchAndCacheMemoryContext(state, messageText, useUserScope, characterBudget, currentFactUuids);
205
+ await searchAndCacheMemoryContext(state, messageText, useUserScope, characterBudget,
206
+ // Task 8: on reinjection, pass the drift facts so the project query is
207
+ // not duplicated. On first injection driftProjectFacts is null, which
208
+ // triggers a full maxFacts=PROJECT_MAX_FACTS project search.
209
+ driftProjectFacts ?? undefined);
201
210
  state.injectedMemories = true;
202
211
  }
203
212
  catch (err) {
@@ -9,9 +9,9 @@ export interface EventHandlerDeps {
9
9
  sessionManager: SessionManager;
10
10
  client: GraphitiClient;
11
11
  defaultGroupId: string;
12
+ defaultUserGroupId: string;
12
13
  sdkClient: OpencodeClient;
13
14
  directory: string;
14
- groupIdPrefix: string;
15
15
  }
16
16
  /** Creates the `event` hook handler. */
17
17
  export declare function createEventHandler(deps: EventHandlerDeps): ({ event }: EventInput) => Promise<void>;
@@ -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,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,IAqDzC,WAAW,UAAU,mBA4LpC"}
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,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wCAAwC;AACxC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IAkDzC,WAAW,UAAU,mBAiMpC"}
@@ -1,23 +1,20 @@
1
1
  import { handleCompaction } from "../services/compaction.js";
2
2
  import { resolveContextLimit } from "../services/context-limit.js";
3
3
  import { logger } from "../services/logger.js";
4
- import { isTextPart, makeUserGroupId } from "../utils.js";
4
+ import { isTextPart } from "../utils.js";
5
5
  /** Creates the `event` hook handler. */
6
6
  export function createEventHandler(deps) {
7
- const { sessionManager, client, defaultGroupId, sdkClient, directory, groupIdPrefix, } = deps;
8
- const defaultUserGroupId = makeUserGroupId(groupIdPrefix);
9
- /** Stores the last successfully saved snapshot body per session ID. */
10
- const lastSnapshotBody = new Map();
7
+ const { sessionManager, client, defaultGroupId, defaultUserGroupId, sdkClient, directory, } = deps;
8
+ /** Per-handler context-limit cache — no cross-instance sharing. */
9
+ const contextLimitCache = new Map();
11
10
  const buildSessionSnapshot = (sessionId, messages) => {
12
11
  const recentMessages = messages.slice(-12);
13
- const recentAssistant = [...recentMessages]
14
- .reverse()
15
- .find((message) => message.startsWith("Assistant:"))
12
+ const recentAssistant = recentMessages
13
+ .findLast((message) => message.startsWith("Assistant:"))
16
14
  ?.replace(/^Assistant:\s*/, "")
17
15
  .trim();
18
- const recentUser = [...recentMessages]
19
- .reverse()
20
- .find((message) => message.startsWith("User:"))
16
+ const recentUser = recentMessages
17
+ .findLast((message) => message.startsWith("User:"))
21
18
  ?.replace(/^User:\s*/, "")
22
19
  .trim();
23
20
  const questionRegex = /[^\n\r?]{3,200}\?/g;
@@ -57,19 +54,7 @@ export function createEventHandler(deps) {
57
54
  parentID: info.parentID,
58
55
  });
59
56
  if (isMain) {
60
- sessionManager.setState(sessionId, {
61
- groupId: defaultGroupId,
62
- userGroupId: defaultUserGroupId,
63
- injectedMemories: false,
64
- lastInjectionFactUuids: [],
65
- cachedMemoryContext: undefined,
66
- cachedFactUuids: undefined,
67
- visibleFactUuids: [],
68
- messageCount: 0,
69
- pendingMessages: [],
70
- contextLimit: 200_000,
71
- isMain,
72
- });
57
+ sessionManager.setState(sessionId, sessionManager.createDefaultState(defaultGroupId, defaultUserGroupId));
73
58
  }
74
59
  else {
75
60
  logger.debug("Ignoring subagent session:", sessionId);
@@ -112,25 +97,32 @@ export function createEventHandler(deps) {
112
97
  return;
113
98
  }
114
99
  try {
115
- const snapshotContent = buildSessionSnapshot(sessionId, state.pendingMessages);
116
- if (snapshotContent.trim()) {
117
- if (lastSnapshotBody.get(sessionId) === snapshotContent) {
118
- logger.debug("Skipping duplicate session snapshot", {
119
- sessionId,
120
- });
121
- }
122
- else {
123
- await client.addEpisode({
124
- name: `Snapshot: ${sessionId}`,
125
- episodeBody: snapshotContent,
126
- groupId: state.groupId,
127
- source: "text",
128
- sourceDescription: "session-snapshot",
129
- });
130
- lastSnapshotBody.set(sessionId, snapshotContent);
131
- logger.info("Saved session snapshot", { sessionId });
100
+ if (state.pendingMessages.length > 0) {
101
+ const snapshotContent = buildSessionSnapshot(sessionId, state.pendingMessages);
102
+ if (snapshotContent.trim()) {
103
+ if (state.lastSnapshotBody === snapshotContent) {
104
+ logger.debug("Skipping duplicate session snapshot", {
105
+ sessionId,
106
+ });
107
+ }
108
+ else {
109
+ await client.addEpisode({
110
+ name: `Snapshot: ${sessionId}`,
111
+ episodeBody: snapshotContent,
112
+ groupId: state.groupId,
113
+ source: "text",
114
+ sourceDescription: "session-snapshot",
115
+ });
116
+ state.lastSnapshotBody = snapshotContent;
117
+ logger.info("Saved session snapshot", { sessionId });
118
+ }
132
119
  }
133
120
  }
121
+ else {
122
+ logger.debug("Skipping idle snapshot: no pending messages", {
123
+ sessionId,
124
+ });
125
+ }
134
126
  }
135
127
  catch (err) {
136
128
  logger.error("Failed to save session snapshot", { sessionId, err });
@@ -172,11 +164,15 @@ export function createEventHandler(deps) {
172
164
  return;
173
165
  sessionManager.finalizeAssistantMessage(state, sessionId, info.id, "message.updated");
174
166
  if (info.tokens && info.providerID && info.modelID) {
175
- resolveContextLimit(info.providerID, info.modelID, sdkClient, directory)
176
- .then((limit) => {
177
- state.contextLimit = limit;
178
- })
179
- .catch((err) => logger.debug("Failed to resolve context limit", err));
167
+ // Fire-and-forget: update contextLimit asynchronously without
168
+ // blocking event responsiveness. The state update is eventually
169
+ // consistent — a missed update only affects injection budget sizing,
170
+ // not correctness. We snapshot `state` here; if the session is
171
+ // deleted before the promise resolves the write is a harmless no-op.
172
+ const capturedState = state;
173
+ resolveContextLimit(info.providerID, info.modelID, sdkClient, directory, contextLimitCache).then((limit) => {
174
+ capturedState.contextLimit = limit;
175
+ }).catch((err) => logger.debug("Failed to resolve context limit", err));
180
176
  }
181
177
  return;
182
178
  }
@@ -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"}
@@ -4,9 +4,8 @@ export function createMessagesHandler(deps) {
4
4
  const { sessionManager } = deps;
5
5
  // deno-lint-ignore require-await
6
6
  return async (_input, output) => {
7
- const lastUserEntry = [...output.messages]
8
- .reverse()
9
- .find((message) => message.info.role === "user");
7
+ const lastUserEntry = output.messages
8
+ .findLast((message) => message.info.role === "user");
10
9
  if (!lastUserEntry)
11
10
  return;
12
11
  const sessionID = lastUserEntry.info.sessionID;
package/esm/src/index.js CHANGED
@@ -11,7 +11,7 @@ import { makeGroupId, makeUserGroupId } from "./utils.js";
11
11
  * OpenCode plugin entry point for Graphiti memory integration.
12
12
  */
13
13
  export const graphiti = async (input) => {
14
- const config = loadConfig();
14
+ const config = loadConfig(input.directory);
15
15
  const client = new GraphitiClient(config.endpoint);
16
16
  const sdkClient = input.client;
17
17
  const connected = await client.connect();
@@ -28,9 +28,9 @@ export const graphiti = async (input) => {
28
28
  sessionManager,
29
29
  client,
30
30
  defaultGroupId,
31
+ defaultUserGroupId,
31
32
  sdkClient,
32
33
  directory: input.directory,
33
- groupIdPrefix: config.groupIdPrefix,
34
34
  }),
35
35
  "chat.message": createChatHandler({
36
36
  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"}