chivgent 0.10.0 → 0.11.0
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 +85 -1
- package/README.zh-CN.md +77 -1
- package/dist/cli-options.d.ts +6 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +26 -1
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +106 -2
- package/dist/cli.js.map +1 -1
- package/dist/extensions/api.d.ts +38 -0
- package/dist/extensions/api.d.ts.map +1 -0
- package/dist/extensions/api.js +2 -0
- package/dist/extensions/api.js.map +1 -0
- package/dist/extensions/decide-trust.d.ts +28 -0
- package/dist/extensions/decide-trust.d.ts.map +1 -0
- package/dist/extensions/decide-trust.js +96 -0
- package/dist/extensions/decide-trust.js.map +1 -0
- package/dist/extensions/discover.d.ts +19 -0
- package/dist/extensions/discover.d.ts.map +1 -0
- package/dist/extensions/discover.js +61 -0
- package/dist/extensions/discover.js.map +1 -0
- package/dist/extensions/loader.d.ts +28 -0
- package/dist/extensions/loader.d.ts.map +1 -0
- package/dist/extensions/loader.js +60 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/extensions/registry.d.ts +36 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/extensions/registry.js +115 -0
- package/dist/extensions/registry.js.map +1 -0
- package/dist/extensions/trust.d.ts +44 -0
- package/dist/extensions/trust.d.ts.map +1 -0
- package/dist/extensions/trust.js +129 -0
- package/dist/extensions/trust.js.map +1 -0
- package/dist/repl.d.ts +12 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +47 -2
- package/dist/repl.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { OutputStream } from "../render.js";
|
|
2
|
+
import { TrustStore } from "./trust.js";
|
|
3
|
+
export type TrustOutcome = {
|
|
4
|
+
readonly trusted: true;
|
|
5
|
+
readonly reason: "recorded" | "granted";
|
|
6
|
+
} | {
|
|
7
|
+
readonly trusted: false;
|
|
8
|
+
readonly reason: "recorded" | "declined" | "no-tty" | "deferred";
|
|
9
|
+
};
|
|
10
|
+
export interface DecideTrustOptions {
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly store: TrustStore;
|
|
13
|
+
readonly stderr: OutputStream;
|
|
14
|
+
/** Absent when there is nobody to ask, which is itself the answer: no. */
|
|
15
|
+
readonly input?: NodeJS.ReadableStream & {
|
|
16
|
+
isTTY?: boolean;
|
|
17
|
+
};
|
|
18
|
+
readonly listExtensions: () => Promise<readonly string[]>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Decides whether this project's extensions may be loaded.
|
|
22
|
+
*
|
|
23
|
+
* The decision happens before anything is imported, because importing is
|
|
24
|
+
* already execution. When there is nobody to ask, the answer is no: a CI job
|
|
25
|
+
* or a piped run must never silently execute code from a checkout.
|
|
26
|
+
*/
|
|
27
|
+
export declare function decideTrust(options: DecideTrustOptions): Promise<TrustOutcome>;
|
|
28
|
+
//# sourceMappingURL=decide-trust.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decide-trust.d.ts","sourceRoot":"","sources":["../../src/extensions/decide-trust.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAGL,UAAU,EAEX,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;CAAE,GACnE;IACE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;CAClE,CAAC;AAEN,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7D,QAAQ,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;CAC3D;AAqCD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAmEvB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { canonicalDirectory, hasProjectExtensions, TrustStore, } from "./trust.js";
|
|
4
|
+
import { projectExtensionsDirectory } from "./discover.js";
|
|
5
|
+
function describeConsequences(cwd, files) {
|
|
6
|
+
const listed = files.map((file) => ` ${path.relative(cwd, file)}`).join("\n");
|
|
7
|
+
return [
|
|
8
|
+
"This project ships chivgent extensions:",
|
|
9
|
+
listed,
|
|
10
|
+
"",
|
|
11
|
+
"Extensions run as code inside chivgent, with your permissions. They are not",
|
|
12
|
+
"limited by the workspace boundary and do not need --allow-shell to run",
|
|
13
|
+
"commands or read your environment. Trusting this project means letting its",
|
|
14
|
+
"authors run code as you.",
|
|
15
|
+
"",
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
18
|
+
async function ask(input, output, question) {
|
|
19
|
+
const readline = createInterface({
|
|
20
|
+
input,
|
|
21
|
+
output: output,
|
|
22
|
+
terminal: false,
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
return await new Promise((resolve) => {
|
|
26
|
+
output.write(question);
|
|
27
|
+
readline.once("line", (line) => resolve(line.trim().toLowerCase()));
|
|
28
|
+
readline.once("close", () => resolve(""));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
readline.close();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decides whether this project's extensions may be loaded.
|
|
37
|
+
*
|
|
38
|
+
* The decision happens before anything is imported, because importing is
|
|
39
|
+
* already execution. When there is nobody to ask, the answer is no: a CI job
|
|
40
|
+
* or a piped run must never silently execute code from a checkout.
|
|
41
|
+
*/
|
|
42
|
+
export async function decideTrust(options) {
|
|
43
|
+
if (!hasProjectExtensions(options.cwd)) {
|
|
44
|
+
// Nothing to gate: do not bother the user at all.
|
|
45
|
+
return { trusted: false, reason: "recorded" };
|
|
46
|
+
}
|
|
47
|
+
const existing = await options.store.lookup(options.cwd);
|
|
48
|
+
if (existing !== undefined) {
|
|
49
|
+
return existing.decision
|
|
50
|
+
? { trusted: true, reason: "recorded" }
|
|
51
|
+
: { trusted: false, reason: "recorded" };
|
|
52
|
+
}
|
|
53
|
+
const input = options.input;
|
|
54
|
+
if (input === undefined || input.isTTY !== true) {
|
|
55
|
+
options.stderr.write(`Skipping project extensions in ${projectExtensionsDirectory(options.cwd)}: ` +
|
|
56
|
+
"no decision recorded and no terminal to ask. Run chivgent here interactively once, " +
|
|
57
|
+
"or use --no-extensions to silence this.\n");
|
|
58
|
+
return { trusted: false, reason: "no-tty" };
|
|
59
|
+
}
|
|
60
|
+
const files = await options.listExtensions();
|
|
61
|
+
options.stderr.write(describeConsequences(options.cwd, files));
|
|
62
|
+
const parent = path.dirname(await canonicalDirectory(options.cwd));
|
|
63
|
+
const answer = await ask(input, options.stderr, ` [t] trust this project [p] trust ${parent} [n] never [o] not now: `);
|
|
64
|
+
const record = async (directory, decision) => {
|
|
65
|
+
try {
|
|
66
|
+
await options.store.set(directory, decision);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
70
|
+
options.stderr.write(`Could not record the trust decision: ${message}\n`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
switch (answer) {
|
|
74
|
+
case "t":
|
|
75
|
+
case "trust":
|
|
76
|
+
case "y":
|
|
77
|
+
case "yes":
|
|
78
|
+
await record(options.cwd, true);
|
|
79
|
+
return { trusted: true, reason: "granted" };
|
|
80
|
+
case "p":
|
|
81
|
+
case "parent":
|
|
82
|
+
await record(parent, true);
|
|
83
|
+
return { trusted: true, reason: "granted" };
|
|
84
|
+
case "n":
|
|
85
|
+
case "never":
|
|
86
|
+
case "no":
|
|
87
|
+
await record(options.cwd, false);
|
|
88
|
+
return { trusted: false, reason: "declined" };
|
|
89
|
+
default:
|
|
90
|
+
// "not now" and anything unrecognised both mean: do not run it, and ask
|
|
91
|
+
// again next time rather than recording a decision the user did not make.
|
|
92
|
+
options.stderr.write("Not loading project extensions this time.\n");
|
|
93
|
+
return { trusted: false, reason: "deferred" };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=decide-trust.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decide-trust.js","sourceRoot":"","sources":["../../src/extensions/decide-trust.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ExtensionOrigin = "user" | "project";
|
|
2
|
+
export interface DiscoveredExtension {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly origin: ExtensionOrigin;
|
|
5
|
+
}
|
|
6
|
+
export declare function userExtensionsDirectory(environment?: NodeJS.ProcessEnv): string;
|
|
7
|
+
export declare function projectExtensionsDirectory(cwd: string): string;
|
|
8
|
+
export interface DiscoverOptions {
|
|
9
|
+
readonly cwd: string;
|
|
10
|
+
/** Project extensions are only discovered once trust has been granted. */
|
|
11
|
+
readonly includeProject: boolean;
|
|
12
|
+
readonly environment?: NodeJS.ProcessEnv;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* User extensions come first so that a project cannot take a name the user
|
|
16
|
+
* already uses: the first registration of a name wins.
|
|
17
|
+
*/
|
|
18
|
+
export declare function discoverExtensions(options: DiscoverOptions): Promise<readonly DiscoveredExtension[]>;
|
|
19
|
+
//# sourceMappingURL=discover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/extensions/discover.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;CAClC;AAED,wBAAgB,uBAAuB,CACrC,WAAW,GAAE,MAAM,CAAC,UAAwB,GAC3C,MAAM,CAER;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9D;AAyCD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAC1C;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAWzC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { CONFIG_DIRECTORY, EXTENSIONS_DIRECTORY } from "./trust.js";
|
|
4
|
+
import { defaultSessionHome } from "../session-store.js";
|
|
5
|
+
export function userExtensionsDirectory(environment = process.env) {
|
|
6
|
+
return path.join(defaultSessionHome(environment), EXTENSIONS_DIRECTORY);
|
|
7
|
+
}
|
|
8
|
+
export function projectExtensionsDirectory(cwd) {
|
|
9
|
+
return path.join(cwd, CONFIG_DIRECTORY, EXTENSIONS_DIRECTORY);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Lists the extension entry points in one directory.
|
|
13
|
+
*
|
|
14
|
+
* Two shapes are recognised: `name.js`, and `name/index.js`. Nesting stops
|
|
15
|
+
* there. Anything more elaborate is a package, and packaging is not what this
|
|
16
|
+
* stage is about.
|
|
17
|
+
*/
|
|
18
|
+
async function discoverIn(directory, origin) {
|
|
19
|
+
let entries;
|
|
20
|
+
try {
|
|
21
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const found = [];
|
|
27
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
28
|
+
const entryPath = path.join(directory, entry.name);
|
|
29
|
+
if (entry.isFile() && entry.name.endsWith(".js")) {
|
|
30
|
+
found.push({ path: entryPath, origin });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
const index = path.join(entryPath, "index.js");
|
|
35
|
+
try {
|
|
36
|
+
if ((await stat(index)).isFile()) {
|
|
37
|
+
found.push({ path: index, origin });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// A directory without an index is not an extension.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return found;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* User extensions come first so that a project cannot take a name the user
|
|
49
|
+
* already uses: the first registration of a name wins.
|
|
50
|
+
*/
|
|
51
|
+
export async function discoverExtensions(options) {
|
|
52
|
+
const environment = options.environment ?? process.env;
|
|
53
|
+
const found = [
|
|
54
|
+
...(await discoverIn(userExtensionsDirectory(environment), "user")),
|
|
55
|
+
];
|
|
56
|
+
if (options.includeProject) {
|
|
57
|
+
found.push(...(await discoverIn(projectExtensionsDirectory(options.cwd), "project")));
|
|
58
|
+
}
|
|
59
|
+
return found;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=discover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/extensions/discover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AASzD,MAAM,UAAU,uBAAuB,CACrC,WAAW,GAAsB,OAAO,CAAC,GAAG;IAE5C,OAAO,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,oBAAoB,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,GAAW;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,EAAE,oBAAoB,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,UAAU,CACvB,SAAiB,EACjB,MAAuB;IAEvB,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAA0B,EAAE,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACzE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACjD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;YACxC,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;YAC/C,IAAI,CAAC;gBACH,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;oBACjC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;gBACtC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,oDAAoD;YACtD,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AASD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAwB;IAExB,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC;IACvD,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,MAAM,UAAU,CAAC,uBAAuB,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;KACpE,CAAC;IACF,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CACR,GAAG,CAAC,MAAM,UAAU,CAAC,0BAA0B,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,CAAC,CAC1E,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { DiscoveredExtension } from "./discover.js";
|
|
2
|
+
import { ExtensionRegistry, type RegistryOptions } from "./registry.js";
|
|
3
|
+
export interface LoadedExtension {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly origin: DiscoveredExtension["origin"];
|
|
6
|
+
}
|
|
7
|
+
export interface LoadResult {
|
|
8
|
+
readonly registry: ExtensionRegistry;
|
|
9
|
+
readonly loaded: readonly LoadedExtension[];
|
|
10
|
+
readonly failed: readonly {
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly reason: string;
|
|
13
|
+
}[];
|
|
14
|
+
}
|
|
15
|
+
export interface LoadOptions extends RegistryOptions {
|
|
16
|
+
/** Injectable so tests do not have to write modules to disk to test loading. */
|
|
17
|
+
readonly importModule?: (path: string) => Promise<unknown>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Imports each extension and lets it register.
|
|
21
|
+
*
|
|
22
|
+
* Importing a module runs its top-level code, so this is the point of no
|
|
23
|
+
* return: everything the caller wanted to gate must already have been decided.
|
|
24
|
+
* From here on the job is containment — one broken extension must leave
|
|
25
|
+
* chivgent working.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadExtensions(discovered: readonly DiscoveredExtension[], options: LoadOptions): Promise<LoadResult>;
|
|
28
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/extensions/loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGxE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAChF;AAED,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,gFAAgF;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5D;AAiBD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,SAAS,mBAAmB,EAAE,EAC1C,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CAuCrB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
import { ExtensionRegistry } from "./registry.js";
|
|
3
|
+
async function defaultImport(modulePath) {
|
|
4
|
+
// A file URL is required on Windows and harmless elsewhere.
|
|
5
|
+
return import(pathToFileURL(modulePath).href);
|
|
6
|
+
}
|
|
7
|
+
function readFactory(module) {
|
|
8
|
+
if (typeof module !== "object" || module === null) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const candidate = module.default;
|
|
12
|
+
return typeof candidate === "function"
|
|
13
|
+
? candidate
|
|
14
|
+
: undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Imports each extension and lets it register.
|
|
18
|
+
*
|
|
19
|
+
* Importing a module runs its top-level code, so this is the point of no
|
|
20
|
+
* return: everything the caller wanted to gate must already have been decided.
|
|
21
|
+
* From here on the job is containment — one broken extension must leave
|
|
22
|
+
* chivgent working.
|
|
23
|
+
*/
|
|
24
|
+
export async function loadExtensions(discovered, options) {
|
|
25
|
+
const registry = new ExtensionRegistry(options);
|
|
26
|
+
const importModule = options.importModule ?? defaultImport;
|
|
27
|
+
const loaded = [];
|
|
28
|
+
const failed = [];
|
|
29
|
+
for (const extension of discovered) {
|
|
30
|
+
let factory;
|
|
31
|
+
try {
|
|
32
|
+
factory = readFactory(await importModule(extension.path));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
36
|
+
failed.push({ path: extension.path, reason });
|
|
37
|
+
options.onWarning(`${extension.path}: failed to load (${reason}).`);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (factory === undefined) {
|
|
41
|
+
const reason = "no default export function";
|
|
42
|
+
failed.push({ path: extension.path, reason });
|
|
43
|
+
options.onWarning(`${extension.path}: ${reason}; an extension must export default a function.`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await factory(registry.apiFor(extension.path));
|
|
48
|
+
loaded.push({ path: extension.path, origin: extension.origin });
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
52
|
+
failed.push({ path: extension.path, reason });
|
|
53
|
+
// Whatever it registered before throwing stays: partial is better than
|
|
54
|
+
// discarding work that is already valid.
|
|
55
|
+
options.onWarning(`${extension.path}: threw while registering (${reason}).`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { registry, loaded, failed };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/extensions/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAwB,MAAM,eAAe,CAAC;AAmBxE,KAAK,UAAU,aAAa,CAAC,UAAkB;IAC7C,4DAA4D;IAC5D,OAAO,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,WAAW,CAAC,MAAe;IAClC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAClD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,SAAS,GAAI,MAAgC,CAAC,OAAO,CAAC;IAC5D,OAAO,OAAO,SAAS,KAAK,UAAU;QACpC,CAAC,CAAE,SAA8B;QACjC,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAA0C,EAC1C,OAAoB;IAEpB,MAAM,QAAQ,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,aAAa,CAAC;IAC3D,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,MAAM,GAAuC,EAAE,CAAC;IAEtD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,OAAqC,CAAC;QAC1C,IAAI,CAAC;YACH,OAAO,GAAG,WAAW,CAAC,MAAM,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC,IAAI,qBAAqB,MAAM,IAAI,CAAC,CAAC;YACpE,SAAS;QACX,CAAC;QAED,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,4BAA4B,CAAC;YAC5C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,SAAS,CACf,GAAG,SAAS,CAAC,IAAI,KAAK,MAAM,gDAAgD,CAC7E,CAAC;YACF,SAAS;QACX,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9C,uEAAuE;YACvE,yCAAyC;YACzC,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC,IAAI,8BAA8B,MAAM,IAAI,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AgentEvent } from "../events.js";
|
|
2
|
+
import type { ExtensionAPI, RegisteredCommand, RegisteredTool } from "./api.js";
|
|
3
|
+
export interface RegistryOptions {
|
|
4
|
+
/** Names an extension may not take, so a project cannot shadow read_file. */
|
|
5
|
+
readonly reservedToolNames: readonly string[];
|
|
6
|
+
readonly reservedCommandNames: readonly string[];
|
|
7
|
+
readonly onWarning: (message: string) => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Collects what extensions register.
|
|
11
|
+
*
|
|
12
|
+
* Every rejection is reported and survivable: a conflicting registration is
|
|
13
|
+
* dropped, the rest of the extension keeps whatever it registered before and
|
|
14
|
+
* after, and the built-in stays in place.
|
|
15
|
+
*/
|
|
16
|
+
export declare class ExtensionRegistry {
|
|
17
|
+
private readonly options;
|
|
18
|
+
private readonly tools;
|
|
19
|
+
private readonly commands;
|
|
20
|
+
private readonly listeners;
|
|
21
|
+
private readonly promptContributions;
|
|
22
|
+
constructor(options: RegistryOptions);
|
|
23
|
+
/** The API handed to one extension; `source` identifies it in diagnostics. */
|
|
24
|
+
apiFor(source: string): ExtensionAPI;
|
|
25
|
+
get registeredTools(): readonly RegisteredTool[];
|
|
26
|
+
get registeredCommands(): readonly RegisteredCommand[];
|
|
27
|
+
get systemPromptContributions(): readonly string[];
|
|
28
|
+
command(name: string): RegisteredCommand | undefined;
|
|
29
|
+
/** A single listener that fans one event out to every subscribed handler. */
|
|
30
|
+
get eventListener(): (event: AgentEvent) => void;
|
|
31
|
+
private addTool;
|
|
32
|
+
private addCommand;
|
|
33
|
+
private addListener;
|
|
34
|
+
private addPrompt;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/extensions/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,KAAK,EACV,YAAY,EAEZ,iBAAiB,EACjB,cAAc,EACf,MAAM,UAAU,CAAC;AAIlB,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAShB,OAAO,CAAC,QAAQ,CAAC,OAAO;IARpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IACjE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAGjB;IACT,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAgB;IAEpD,YAA6B,OAAO,EAAE,eAAe,EAAI;IAEzD,8EAA8E;IAC9E,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CASnC;IAED,IAAI,eAAe,IAAI,SAAS,cAAc,EAAE,CAE/C;IAED,IAAI,kBAAkB,IAAI,SAAS,iBAAiB,EAAE,CAErD;IAED,IAAI,yBAAyB,IAAI,SAAS,MAAM,EAAE,CAEjD;IAED,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAEnD;IAED,6EAA6E;IAC7E,IAAI,aAAa,IAAI,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAa/C;IAED,OAAO,CAAC,OAAO;IA2Bf,OAAO,CAAC,UAAU;IAoClB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,SAAS;CAOlB"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const COMMAND_NAME = /^[a-z][a-z0-9-]*$/;
|
|
2
|
+
/**
|
|
3
|
+
* Collects what extensions register.
|
|
4
|
+
*
|
|
5
|
+
* Every rejection is reported and survivable: a conflicting registration is
|
|
6
|
+
* dropped, the rest of the extension keeps whatever it registered before and
|
|
7
|
+
* after, and the built-in stays in place.
|
|
8
|
+
*/
|
|
9
|
+
export class ExtensionRegistry {
|
|
10
|
+
options;
|
|
11
|
+
tools = new Map();
|
|
12
|
+
commands = new Map();
|
|
13
|
+
listeners = [];
|
|
14
|
+
promptContributions = [];
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
/** The API handed to one extension; `source` identifies it in diagnostics. */
|
|
19
|
+
apiFor(source) {
|
|
20
|
+
return {
|
|
21
|
+
registerTool: (tool) => this.addTool(tool, source),
|
|
22
|
+
registerCommand: (name, command) => this.addCommand(name, command, source),
|
|
23
|
+
on: (type, handler) => this.addListener(type, handler, source),
|
|
24
|
+
contributeSystemPrompt: (text) => this.addPrompt(text, source),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
get registeredTools() {
|
|
28
|
+
return [...this.tools.values()];
|
|
29
|
+
}
|
|
30
|
+
get registeredCommands() {
|
|
31
|
+
return [...this.commands.values()];
|
|
32
|
+
}
|
|
33
|
+
get systemPromptContributions() {
|
|
34
|
+
return this.promptContributions;
|
|
35
|
+
}
|
|
36
|
+
command(name) {
|
|
37
|
+
return this.commands.get(name);
|
|
38
|
+
}
|
|
39
|
+
/** A single listener that fans one event out to every subscribed handler. */
|
|
40
|
+
get eventListener() {
|
|
41
|
+
return (event) => {
|
|
42
|
+
for (const listener of this.listeners) {
|
|
43
|
+
if (listener.type !== event.type) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
listener.handler(event);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// An extension must not change the outcome of the run it observes.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
addTool(tool, source) {
|
|
56
|
+
if (typeof tool !== "object" ||
|
|
57
|
+
tool === null ||
|
|
58
|
+
typeof tool.name !== "string" ||
|
|
59
|
+
tool.name.length === 0 ||
|
|
60
|
+
typeof tool.execute !== "function") {
|
|
61
|
+
this.options.onWarning(`${source}: registerTool needs a tool with a name and an execute function.`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (this.options.reservedToolNames.includes(tool.name)) {
|
|
65
|
+
this.options.onWarning(`${source}: cannot register the tool "${tool.name}"; that name is built in.`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const existing = this.tools.get(tool.name);
|
|
69
|
+
if (existing !== undefined) {
|
|
70
|
+
this.options.onWarning(`${source}: the tool "${tool.name}" is already registered by ${existing.source}.`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.tools.set(tool.name, { tool, source });
|
|
74
|
+
}
|
|
75
|
+
addCommand(name, command, source) {
|
|
76
|
+
if (typeof name !== "string" || !COMMAND_NAME.test(name)) {
|
|
77
|
+
this.options.onWarning(`${source}: "${String(name)}" is not a usable command name; use lowercase letters, digits and dashes.`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (typeof command !== "object" || command === null || typeof command.run !== "function") {
|
|
81
|
+
this.options.onWarning(`${source}: the command "${name}" needs a run function.`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (this.options.reservedCommandNames.includes(name)) {
|
|
85
|
+
this.options.onWarning(`${source}: cannot register "/${name}"; that command is built in.`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const existing = this.commands.get(name);
|
|
89
|
+
if (existing !== undefined) {
|
|
90
|
+
this.options.onWarning(`${source}: "/${name}" is already registered by ${existing.source}.`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.commands.set(name, {
|
|
94
|
+
name,
|
|
95
|
+
source,
|
|
96
|
+
description: typeof command.description === "string" ? command.description : "",
|
|
97
|
+
run: command.run.bind(command),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
addListener(type, handler, source) {
|
|
101
|
+
if (typeof handler !== "function") {
|
|
102
|
+
this.options.onWarning(`${source}: on("${String(type)}") needs a function.`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.listeners.push({ type, handler });
|
|
106
|
+
}
|
|
107
|
+
addPrompt(text, source) {
|
|
108
|
+
if (typeof text !== "string" || text.trim().length === 0) {
|
|
109
|
+
this.options.onWarning(`${source}: contributeSystemPrompt needs a non-empty string.`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.promptContributions.push(text.trim());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/extensions/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const CONFIG_DIRECTORY = ".chivgent";
|
|
2
|
+
export declare const EXTENSIONS_DIRECTORY = "extensions";
|
|
3
|
+
export declare class TrustFileError extends Error {
|
|
4
|
+
constructor(message: string, options?: {
|
|
5
|
+
cause?: unknown;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export declare function defaultTrustFile(environment?: NodeJS.ProcessEnv): string;
|
|
9
|
+
/**
|
|
10
|
+
* Resolves a directory the way the trust store keys it.
|
|
11
|
+
*
|
|
12
|
+
* Symlinks and `..` are resolved first so that two paths naming the same
|
|
13
|
+
* directory cannot produce two entries, one of which the user never sees.
|
|
14
|
+
*/
|
|
15
|
+
export declare function canonicalDirectory(directory: string): Promise<string>;
|
|
16
|
+
export type TrustDecision = boolean;
|
|
17
|
+
export interface TrustEntry {
|
|
18
|
+
/** The directory the decision was recorded against, which may be an ancestor. */
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly decision: TrustDecision;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Records whether a project directory may contribute code to chivgent.
|
|
24
|
+
*
|
|
25
|
+
* Decisions are looked up by nearest ancestor, so trusting `~/work` covers
|
|
26
|
+
* every repository under it. A monorepo does not ask once per package.
|
|
27
|
+
*/
|
|
28
|
+
export declare class TrustStore {
|
|
29
|
+
private readonly filePath;
|
|
30
|
+
constructor(filePath?: string);
|
|
31
|
+
get location(): string;
|
|
32
|
+
lookup(directory: string): Promise<TrustEntry | undefined>;
|
|
33
|
+
set(directory: string, decision: TrustDecision): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Removes the decision that covers this directory, including an ancestor's,
|
|
36
|
+
* so that forgetting actually makes chivgent ask again.
|
|
37
|
+
*/
|
|
38
|
+
forget(directory: string): Promise<string | undefined>;
|
|
39
|
+
private read;
|
|
40
|
+
private write;
|
|
41
|
+
}
|
|
42
|
+
/** True when the project directory carries anything trust has to gate. */
|
|
43
|
+
export declare function hasProjectExtensions(cwd: string): boolean;
|
|
44
|
+
//# sourceMappingURL=trust.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust.d.ts","sourceRoot":"","sources":["../../src/extensions/trust.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAC5C,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAGjD,qBAAa,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAGzD;CACF;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,GAAE,MAAM,CAAC,UAAwB,GAC3C,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS3E;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC;AAEpC,MAAM,WAAW,UAAU;IACzB,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;CAClC;AAED;;;;;GAKG;AACH,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAArC,YAA6B,QAAQ,GAAE,MAA2B,EAAI;IAEtE,IAAI,QAAQ,IAAI,MAAM,CAErB;IAEK,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAc/D;IAEK,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAKnE;IAED;;;OAGG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAS3D;YAEa,IAAI;YAyCJ,KAAK;CAcpB;AAED,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEzD"}
|