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.
- package/README.md +7 -1
- package/esm/_dnt.polyfills.d.ts +50 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +37 -0
- package/esm/mod.d.ts +1 -0
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +1 -0
- package/esm/src/config.d.ts +7 -1
- package/esm/src/config.d.ts.map +1 -1
- package/esm/src/config.js +17 -5
- package/esm/src/handlers/chat.d.ts.map +1 -1
- package/esm/src/handlers/chat.js +51 -42
- package/esm/src/handlers/event.d.ts +1 -1
- package/esm/src/handlers/event.d.ts.map +1 -1
- package/esm/src/handlers/event.js +42 -46
- package/esm/src/handlers/messages.d.ts.map +1 -1
- package/esm/src/handlers/messages.js +2 -3
- package/esm/src/index.js +2 -2
- package/esm/src/services/client.d.ts +7 -0
- package/esm/src/services/client.d.ts.map +1 -1
- package/esm/src/services/client.js +22 -24
- package/esm/src/services/compaction.d.ts.map +1 -1
- package/esm/src/services/compaction.js +24 -33
- package/esm/src/services/constants.d.ts +7 -0
- package/esm/src/services/constants.d.ts.map +1 -0
- package/esm/src/services/constants.js +6 -0
- package/esm/src/services/context-limit.d.ts +1 -1
- package/esm/src/services/context-limit.d.ts.map +1 -1
- package/esm/src/services/context-limit.js +12 -14
- package/esm/src/services/context.d.ts +27 -7
- package/esm/src/services/context.d.ts.map +1 -1
- package/esm/src/services/context.js +34 -2
- package/esm/src/services/logger.js +2 -4
- package/esm/src/services/sdk-normalize.d.ts +55 -0
- package/esm/src/services/sdk-normalize.d.ts.map +1 -0
- package/esm/src/services/sdk-normalize.js +61 -0
- package/esm/src/session.d.ts +4 -2
- package/esm/src/session.d.ts.map +1 -1
- package/esm/src/session.js +38 -34
- package/esm/src/types/index.d.ts +13 -14
- package/esm/src/types/index.d.ts.map +1 -1
- package/esm/src/utils.d.ts +6 -0
- package/esm/src/utils.d.ts.map +1 -1
- package/esm/src/utils.js +16 -2
- package/package.json +1 -1
- package/script/_dnt.polyfills.d.ts +50 -0
- package/script/_dnt.polyfills.d.ts.map +1 -0
- package/script/_dnt.polyfills.js +38 -0
- package/script/mod.d.ts +1 -0
- package/script/mod.d.ts.map +1 -1
- package/script/mod.js +1 -0
- package/script/src/config.d.ts +7 -1
- package/script/src/config.d.ts.map +1 -1
- package/script/src/config.js +20 -5
- package/script/src/handlers/chat.d.ts.map +1 -1
- package/script/src/handlers/chat.js +49 -40
- package/script/src/handlers/event.d.ts +1 -1
- package/script/src/handlers/event.d.ts.map +1 -1
- package/script/src/handlers/event.js +41 -45
- package/script/src/handlers/messages.d.ts.map +1 -1
- package/script/src/handlers/messages.js +2 -3
- package/script/src/index.js +2 -2
- package/script/src/services/client.d.ts +7 -0
- package/script/src/services/client.d.ts.map +1 -1
- package/script/src/services/client.js +22 -24
- package/script/src/services/compaction.d.ts.map +1 -1
- package/script/src/services/compaction.js +24 -33
- package/script/src/services/constants.d.ts +7 -0
- package/script/src/services/constants.d.ts.map +1 -0
- package/script/src/services/constants.js +9 -0
- package/script/src/services/context-limit.d.ts +1 -1
- package/script/src/services/context-limit.d.ts.map +1 -1
- package/script/src/services/context-limit.js +13 -15
- package/script/src/services/context.d.ts +27 -7
- package/script/src/services/context.d.ts.map +1 -1
- package/script/src/services/context.js +36 -4
- package/script/src/services/logger.js +2 -4
- package/script/src/services/sdk-normalize.d.ts +55 -0
- package/script/src/services/sdk-normalize.d.ts.map +1 -0
- package/script/src/services/sdk-normalize.js +67 -0
- package/script/src/session.d.ts +4 -2
- package/script/src/session.d.ts.map +1 -1
- package/script/src/session.js +38 -34
- package/script/src/types/index.d.ts +13 -14
- package/script/src/types/index.d.ts.map +1 -1
- package/script/src/utils.d.ts +6 -0
- package/script/src/utils.d.ts.map +1 -1
- package/script/src/utils.js +18 -3
package/README.md
CHANGED
|
@@ -99,7 +99,13 @@ automatically.
|
|
|
99
99
|
|
|
100
100
|
## Configuration
|
|
101
101
|
|
|
102
|
-
|
|
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
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
package/esm/src/config.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { GraphitiConfig } from "./types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Load Graphiti configuration from JSONC files with defaults applied.
|
|
4
|
+
*
|
|
5
|
+
* When `directory` is provided, the search starts from that directory (no
|
|
6
|
+
* upward traversal past it) so that a project-local `.graphitirc` or
|
|
7
|
+
* `package.json#graphiti` key takes precedence over any global/home config.
|
|
8
|
+
* If no config is found in the project directory the search falls back to a
|
|
9
|
+
* global search (home directory and OS-level config locations).
|
|
4
10
|
*/
|
|
5
|
-
export declare function loadConfig(): GraphitiConfig;
|
|
11
|
+
export declare function loadConfig(directory?: string): GraphitiConfig;
|
|
6
12
|
//# sourceMappingURL=config.d.ts.map
|
package/esm/src/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/src/config.ts"],"names":[],"mappings":"
|
|
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;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,cAAc,CAoB7D"}
|
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,25 @@ const GraphitiConfigSchema = z.object({
|
|
|
14
15
|
});
|
|
15
16
|
/**
|
|
16
17
|
* Load Graphiti configuration from JSONC files with defaults applied.
|
|
18
|
+
*
|
|
19
|
+
* When `directory` is provided, the search starts from that directory (no
|
|
20
|
+
* upward traversal past it) so that a project-local `.graphitirc` or
|
|
21
|
+
* `package.json#graphiti` key takes precedence over any global/home config.
|
|
22
|
+
* If no config is found in the project directory the search falls back to a
|
|
23
|
+
* global search (home directory and OS-level config locations).
|
|
17
24
|
*/
|
|
18
|
-
export function loadConfig() {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
export function loadConfig(directory) {
|
|
26
|
+
const result = cosmiconfigSync("graphiti", {
|
|
27
|
+
stopDir: os.homedir(),
|
|
28
|
+
mergeSearchPlaces: true,
|
|
29
|
+
cache: false,
|
|
30
|
+
}).search(directory) ??
|
|
31
|
+
cosmiconfigSync("graphiti", {
|
|
32
|
+
searchPlaces: [`${os.homedir()}/.graphitirc`],
|
|
33
|
+
}).search();
|
|
22
34
|
const merged = {
|
|
23
35
|
...DEFAULT_CONFIG,
|
|
24
|
-
...
|
|
36
|
+
...result?.config,
|
|
25
37
|
};
|
|
26
38
|
const parsed = GraphitiConfigSchema.safeParse(merged);
|
|
27
39
|
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;
|
|
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"}
|
package/esm/src/handlers/chat.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { calculateInjectionBudget } from "../services/context-limit.js";
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
117
|
+
const truncatedProject = truncateAtLineBoundary(projectContextString, projectBudget);
|
|
112
118
|
const truncatedUser = useUserScope
|
|
113
|
-
? userContextString
|
|
119
|
+
? truncateAtLineBoundary(userContextString, userBudget)
|
|
114
120
|
: "";
|
|
115
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
170
|
-
|
|
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
|
|
177
|
+
const fetched = await client.searchFacts({
|
|
174
178
|
query: messageText,
|
|
175
179
|
groupIds: [state.groupId],
|
|
176
|
-
maxFacts:
|
|
180
|
+
maxFacts: PROJECT_MAX_FACTS,
|
|
177
181
|
});
|
|
178
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
8
|
-
|
|
9
|
-
|
|
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 =
|
|
14
|
-
.
|
|
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 =
|
|
19
|
-
.
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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,
|
|
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 =
|
|
8
|
-
.
|
|
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,
|
|
@@ -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,
|
|
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"}
|