gnosys 5.5.0 → 5.6.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 +44 -0
- package/dist/cli.js +204 -18
- package/dist/cli.js.map +1 -1
- package/dist/lib/chat/choose.d.ts +75 -0
- package/dist/lib/chat/choose.d.ts.map +1 -0
- package/dist/lib/chat/choose.js +146 -0
- package/dist/lib/chat/choose.js.map +1 -0
- package/dist/lib/chat/commands.d.ts +96 -0
- package/dist/lib/chat/commands.d.ts.map +1 -0
- package/dist/lib/chat/commands.js +367 -0
- package/dist/lib/chat/commands.js.map +1 -0
- package/dist/lib/chat/focus.d.ts +70 -0
- package/dist/lib/chat/focus.d.ts.map +1 -0
- package/dist/lib/chat/focus.js +120 -0
- package/dist/lib/chat/focus.js.map +1 -0
- package/dist/lib/chat/index.d.ts +32 -0
- package/dist/lib/chat/index.d.ts.map +1 -0
- package/dist/lib/chat/index.js +151 -0
- package/dist/lib/chat/index.js.map +1 -0
- package/dist/lib/chat/intent.d.ts +100 -0
- package/dist/lib/chat/intent.d.ts.map +1 -0
- package/dist/lib/chat/intent.js +192 -0
- package/dist/lib/chat/intent.js.map +1 -0
- package/dist/lib/chat/llmTurn.d.ts +37 -0
- package/dist/lib/chat/llmTurn.d.ts.map +1 -0
- package/dist/lib/chat/llmTurn.js +61 -0
- package/dist/lib/chat/llmTurn.js.map +1 -0
- package/dist/lib/chat/recall.d.ts +58 -0
- package/dist/lib/chat/recall.d.ts.map +1 -0
- package/dist/lib/chat/recall.js +109 -0
- package/dist/lib/chat/recall.js.map +1 -0
- package/dist/lib/chat/render.d.ts +30 -0
- package/dist/lib/chat/render.d.ts.map +1 -0
- package/dist/lib/chat/render.js +737 -0
- package/dist/lib/chat/render.js.map +1 -0
- package/dist/lib/chat/session.d.ts +121 -0
- package/dist/lib/chat/session.d.ts.map +1 -0
- package/dist/lib/chat/session.js +148 -0
- package/dist/lib/chat/session.js.map +1 -0
- package/dist/lib/chat/types.d.ts +42 -0
- package/dist/lib/chat/types.d.ts.map +1 -0
- package/dist/lib/chat/types.js +6 -0
- package/dist/lib/chat/types.js.map +1 -0
- package/dist/lib/chat/write.d.ts +66 -0
- package/dist/lib/chat/write.d.ts.map +1 -0
- package/dist/lib/chat/write.js +203 -0
- package/dist/lib/chat/write.js.map +1 -0
- package/dist/lib/db.d.ts +3 -1
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +18 -2
- package/dist/lib/db.js.map +1 -1
- package/dist/lib/exportProject.d.ts +51 -0
- package/dist/lib/exportProject.d.ts.map +1 -0
- package/dist/lib/exportProject.js +72 -0
- package/dist/lib/exportProject.js.map +1 -0
- package/dist/lib/importProject.d.ts +35 -0
- package/dist/lib/importProject.d.ts.map +1 -0
- package/dist/lib/importProject.js +135 -0
- package/dist/lib/importProject.js.map +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat orchestrator — the entry point invoked by `gnosys chat`.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 responsibilities:
|
|
5
|
+
* - Start or resume a session (writes start event to log)
|
|
6
|
+
* - Resolve project context (auto-detect from cwd)
|
|
7
|
+
* - Pick the LLM provider/model
|
|
8
|
+
* - Mount the ink ChatApp
|
|
9
|
+
* - On exit, flush a session_end event
|
|
10
|
+
*/
|
|
11
|
+
import { GnosysDB } from "../db.js";
|
|
12
|
+
import { startSession, appendEvent, readSession, listSessions, searchSessions, } from "./session.js";
|
|
13
|
+
import { resolveTaskModel } from "../config.js";
|
|
14
|
+
/** Reconstruct the conversation buffer from session log events. */
|
|
15
|
+
export function bufferFromEvents(events) {
|
|
16
|
+
const turns = [];
|
|
17
|
+
for (const e of events) {
|
|
18
|
+
if (e.type === "user") {
|
|
19
|
+
turns.push({ role: "user", text: e.text, ts: e.ts });
|
|
20
|
+
}
|
|
21
|
+
else if (e.type === "assistant") {
|
|
22
|
+
turns.push({
|
|
23
|
+
role: "assistant",
|
|
24
|
+
text: e.text,
|
|
25
|
+
ts: e.ts,
|
|
26
|
+
provider: e.provider,
|
|
27
|
+
model: e.model,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return turns;
|
|
32
|
+
}
|
|
33
|
+
/** Detect the project ID from cwd, returns null if no registered project. */
|
|
34
|
+
function detectProject() {
|
|
35
|
+
try {
|
|
36
|
+
const db = GnosysDB.openCentral();
|
|
37
|
+
if (!db.isAvailable()) {
|
|
38
|
+
db.close();
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const proj = db.getProjectByDirectory(process.cwd());
|
|
42
|
+
db.close();
|
|
43
|
+
if (!proj)
|
|
44
|
+
return null;
|
|
45
|
+
return { id: proj.id, name: proj.name };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Start an interactive chat. Mounts the ink TUI, blocks until /quit or Ctrl-C.
|
|
53
|
+
* Lazily imports React/ink so non-chat CLI commands aren't paying for them.
|
|
54
|
+
*/
|
|
55
|
+
export async function startChat(opts) {
|
|
56
|
+
const { default: React } = await import("react");
|
|
57
|
+
const { render } = await import("ink");
|
|
58
|
+
const { ChatApp } = await import("./render.js");
|
|
59
|
+
const project = opts.projectId
|
|
60
|
+
? { id: opts.projectId, name: opts.projectId.slice(0, 8) }
|
|
61
|
+
: detectProject();
|
|
62
|
+
// Resolve provider/model from config.synthesis (the default chat task)
|
|
63
|
+
const synth = resolveTaskModel(opts.config, "synthesis");
|
|
64
|
+
const provider = opts.providerName ?? synth.provider;
|
|
65
|
+
const model = opts.modelName ?? synth.model;
|
|
66
|
+
// Resume existing session or start a new one
|
|
67
|
+
let sessionId;
|
|
68
|
+
let initialBuffer = [];
|
|
69
|
+
if (opts.resume) {
|
|
70
|
+
const events = readSession(opts.resume);
|
|
71
|
+
if (events.length === 0) {
|
|
72
|
+
console.error(`Session not found: ${opts.resume}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
sessionId = opts.resume;
|
|
76
|
+
initialBuffer = bufferFromEvents(events);
|
|
77
|
+
appendEvent(sessionId, {
|
|
78
|
+
type: "session_start",
|
|
79
|
+
ts: new Date().toISOString(),
|
|
80
|
+
project_id: project?.id,
|
|
81
|
+
provider,
|
|
82
|
+
model,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
sessionId = startSession({
|
|
87
|
+
project_id: project?.id,
|
|
88
|
+
provider,
|
|
89
|
+
model,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const initialHeader = {
|
|
93
|
+
sessionId,
|
|
94
|
+
projectName: project?.name,
|
|
95
|
+
provider,
|
|
96
|
+
model,
|
|
97
|
+
tokensIn: 0,
|
|
98
|
+
tokensOut: 0,
|
|
99
|
+
};
|
|
100
|
+
const { waitUntilExit } = render(React.createElement(ChatApp, {
|
|
101
|
+
initialHeader,
|
|
102
|
+
initialBuffer,
|
|
103
|
+
config: opts.config,
|
|
104
|
+
projectId: project?.id ?? null,
|
|
105
|
+
}));
|
|
106
|
+
await waitUntilExit();
|
|
107
|
+
}
|
|
108
|
+
/** Print recent sessions to stdout (for `gnosys chat --list`). */
|
|
109
|
+
export function printSessionList(limit = 20) {
|
|
110
|
+
const sessions = listSessions().slice(0, limit);
|
|
111
|
+
if (sessions.length === 0) {
|
|
112
|
+
console.log("No chat sessions yet.");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
console.log(`Recent ${sessions.length} session(s):`);
|
|
116
|
+
for (const s of sessions) {
|
|
117
|
+
const proj = s.project_id ? s.project_id.slice(0, 8) : "—";
|
|
118
|
+
const sizeKb = (s.size_bytes / 1024).toFixed(1);
|
|
119
|
+
console.log(` ${s.id} ${s.last_active.slice(0, 19)} proj=${proj} turns=${s.turns} ${sizeKb}KB`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Print search results across all session logs (for `gnosys chat --search`). */
|
|
123
|
+
export function printSearchResults(query, limit = 30) {
|
|
124
|
+
const matches = searchSessions(query, limit);
|
|
125
|
+
if (matches.length === 0) {
|
|
126
|
+
console.log(`No matches for: ${query}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
console.log(`${matches.length} match(es):`);
|
|
130
|
+
for (const m of matches) {
|
|
131
|
+
const text = (() => {
|
|
132
|
+
const e = m.event;
|
|
133
|
+
switch (e.type) {
|
|
134
|
+
case "user":
|
|
135
|
+
case "assistant":
|
|
136
|
+
return e.text;
|
|
137
|
+
case "command":
|
|
138
|
+
return `${e.name} ${e.args.join(" ")}`;
|
|
139
|
+
case "focus":
|
|
140
|
+
return e.topic;
|
|
141
|
+
case "recall":
|
|
142
|
+
return e.query;
|
|
143
|
+
default:
|
|
144
|
+
return "";
|
|
145
|
+
}
|
|
146
|
+
})();
|
|
147
|
+
const preview = text.length > 100 ? text.slice(0, 97) + "..." : text;
|
|
148
|
+
console.log(` ${m.sessionId.slice(0, 12)}… [${m.event.type}] ${preview}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/chat/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free-text intent detection — converts conversational phrasings into the
|
|
3
|
+
* matching slash command. Lets the user write "let's focus on the auth
|
|
4
|
+
* refactor" instead of `/focus auth refactor`.
|
|
5
|
+
*
|
|
6
|
+
* Hybrid classifier:
|
|
7
|
+
* 1. Pattern match (regex) — instant, free, covers ~80% of intents
|
|
8
|
+
* 2. LLM classifier fallback — only when pattern returns null AND the
|
|
9
|
+
* message has imperative-y signals
|
|
10
|
+
* 3. Confirm-before-destructive — render layer prompts [Y/n/edit] for
|
|
11
|
+
* high-impact intents (focus/branch/quit/clear)
|
|
12
|
+
*
|
|
13
|
+
* Phase 5 wires patterns whose target commands already exist (Phases 2–4).
|
|
14
|
+
* /focus and /branch (Phase 7) are added in their own phase.
|
|
15
|
+
*/
|
|
16
|
+
import { GnosysConfig } from "../config.js";
|
|
17
|
+
export type InferredIntent = {
|
|
18
|
+
command: "/pin";
|
|
19
|
+
args: string[];
|
|
20
|
+
confidence: "high" | "medium";
|
|
21
|
+
matchedPattern?: string;
|
|
22
|
+
} | {
|
|
23
|
+
command: "/unpin";
|
|
24
|
+
args: string[];
|
|
25
|
+
confidence: "high" | "medium";
|
|
26
|
+
matchedPattern?: string;
|
|
27
|
+
} | {
|
|
28
|
+
command: "/remember";
|
|
29
|
+
args: string[];
|
|
30
|
+
confidence: "high" | "medium";
|
|
31
|
+
matchedPattern?: string;
|
|
32
|
+
} | {
|
|
33
|
+
command: "/save-turn";
|
|
34
|
+
args: string[];
|
|
35
|
+
confidence: "high" | "medium";
|
|
36
|
+
matchedPattern?: string;
|
|
37
|
+
} | {
|
|
38
|
+
command: "/recall";
|
|
39
|
+
args: string[];
|
|
40
|
+
confidence: "high" | "medium";
|
|
41
|
+
matchedPattern?: string;
|
|
42
|
+
} | {
|
|
43
|
+
command: "/reinforce";
|
|
44
|
+
args: string[];
|
|
45
|
+
confidence: "high" | "medium";
|
|
46
|
+
matchedPattern?: string;
|
|
47
|
+
} | {
|
|
48
|
+
command: "/attach";
|
|
49
|
+
args: string[];
|
|
50
|
+
confidence: "high" | "medium";
|
|
51
|
+
matchedPattern?: string;
|
|
52
|
+
} | {
|
|
53
|
+
command: "/quit";
|
|
54
|
+
args: string[];
|
|
55
|
+
confidence: "high" | "medium";
|
|
56
|
+
matchedPattern?: string;
|
|
57
|
+
};
|
|
58
|
+
export interface PatternRule {
|
|
59
|
+
/** Regex to match. Capture group 1 is the args text (joined as `args[0]` if present). */
|
|
60
|
+
pattern: RegExp;
|
|
61
|
+
command: InferredIntent["command"];
|
|
62
|
+
/** Whether this is a destructive action that needs confirm-before-fire. */
|
|
63
|
+
destructive: boolean;
|
|
64
|
+
/** Human-readable description shown in the confirm prompt. */
|
|
65
|
+
description: string;
|
|
66
|
+
}
|
|
67
|
+
export declare const PATTERNS: PatternRule[];
|
|
68
|
+
/** Try to match a user input against the pattern rules. Returns null if no pattern fires. */
|
|
69
|
+
export declare function matchPattern(userInput: string): InferredIntent | null;
|
|
70
|
+
/** True when the input has imperative-y signals — a hint that LLM classification might pay off. */
|
|
71
|
+
export declare function hasImperativeSignal(userInput: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Optional: ask a cheap LLM to classify the intent.
|
|
74
|
+
* Returns null when the LLM is unavailable or the response can't be parsed.
|
|
75
|
+
*/
|
|
76
|
+
export declare function classifyWithLLM(config: GnosysConfig, userInput: string): Promise<InferredIntent | null>;
|
|
77
|
+
/** Whether the inferred command is destructive enough to warrant a confirm prompt. */
|
|
78
|
+
export declare function isDestructive(command: InferredIntent["command"]): boolean;
|
|
79
|
+
/** Render a [Y/n/edit] prompt label for the inferred intent. */
|
|
80
|
+
export declare function describeIntent(intent: InferredIntent): string;
|
|
81
|
+
/**
|
|
82
|
+
* Hybrid classifier — returns the best-guess intent given user input,
|
|
83
|
+
* conversation context, and an optional LLM config for fallback.
|
|
84
|
+
*
|
|
85
|
+
* Strategy:
|
|
86
|
+
* 1. Pattern match first (free, fast)
|
|
87
|
+
* 2. If no pattern AND there's an imperative signal AND we have a config → ask the LLM
|
|
88
|
+
* 3. Otherwise return null (treat as a normal chat turn)
|
|
89
|
+
*/
|
|
90
|
+
export declare function inferIntent(userInput: string, config: GnosysConfig | null): Promise<InferredIntent | null>;
|
|
91
|
+
export interface IntentAcceptanceLog {
|
|
92
|
+
/** Map of pattern source → number of times the user accepted that pattern. */
|
|
93
|
+
acceptCounts: Map<string, number>;
|
|
94
|
+
}
|
|
95
|
+
export declare function newAcceptanceLog(): IntentAcceptanceLog;
|
|
96
|
+
/** Has this pattern been accepted enough times to skip confirmation? */
|
|
97
|
+
export declare function shouldAutoAccept(log: IntentAcceptanceLog, matchedPattern: string | undefined): boolean;
|
|
98
|
+
/** Record a confirmed acceptance of a pattern. */
|
|
99
|
+
export declare function recordAcceptance(log: IntentAcceptanceLog, matchedPattern: string | undefined): void;
|
|
100
|
+
//# sourceMappingURL=intent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/intent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Free-text intent detection — converts conversational phrasings into the
|
|
3
|
+
* matching slash command. Lets the user write "let's focus on the auth
|
|
4
|
+
* refactor" instead of `/focus auth refactor`.
|
|
5
|
+
*
|
|
6
|
+
* Hybrid classifier:
|
|
7
|
+
* 1. Pattern match (regex) — instant, free, covers ~80% of intents
|
|
8
|
+
* 2. LLM classifier fallback — only when pattern returns null AND the
|
|
9
|
+
* message has imperative-y signals
|
|
10
|
+
* 3. Confirm-before-destructive — render layer prompts [Y/n/edit] for
|
|
11
|
+
* high-impact intents (focus/branch/quit/clear)
|
|
12
|
+
*
|
|
13
|
+
* Phase 5 wires patterns whose target commands already exist (Phases 2–4).
|
|
14
|
+
* /focus and /branch (Phase 7) are added in their own phase.
|
|
15
|
+
*/
|
|
16
|
+
import { getLLMProvider } from "../llm.js";
|
|
17
|
+
// Patterns are ordered most specific → most general. First match wins.
|
|
18
|
+
export const PATTERNS = [
|
|
19
|
+
// Quit/exit
|
|
20
|
+
{
|
|
21
|
+
pattern: /^\s*(?:thanks[,.\s]*)?(?:that(?:'s| is) all|i'?m done|goodbye|bye|quit|exit)\s*[.!]?\s*$/i,
|
|
22
|
+
command: "/quit",
|
|
23
|
+
destructive: true,
|
|
24
|
+
description: "exit chat",
|
|
25
|
+
},
|
|
26
|
+
// Save the last exchange — must come BEFORE /remember (which also matches "save ...")
|
|
27
|
+
{
|
|
28
|
+
pattern: /^\s*(?:let'?s\s+)?save\s+(?:that|this|the)\s+(?:exchange|turn|answer)\s*\.?\s*$/i,
|
|
29
|
+
command: "/save-turn",
|
|
30
|
+
destructive: false,
|
|
31
|
+
description: "save the last exchange",
|
|
32
|
+
},
|
|
33
|
+
// Save / remember (decision-language)
|
|
34
|
+
{
|
|
35
|
+
pattern: /^\s*(?:please\s+)?(?:remember|note|save)\s+(?:that\s+|this[:\s]+)?(.+?)\s*$/i,
|
|
36
|
+
command: "/remember",
|
|
37
|
+
destructive: false,
|
|
38
|
+
description: "save as a memory",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
pattern: /^\s*(?:let'?s\s+)?(?:commit|note this[:\s]+)\s+(?:down\s+)?(.+?)\s*$/i,
|
|
42
|
+
command: "/remember",
|
|
43
|
+
destructive: false,
|
|
44
|
+
description: "save as a memory",
|
|
45
|
+
},
|
|
46
|
+
// Recall / lookup
|
|
47
|
+
{
|
|
48
|
+
pattern: /^\s*(?:what (?:did|do) we (?:decide|say|note)\s+(?:about\s+)?|look up\s+|find me (?:the\s+)?)(.+?)\s*\??$/i,
|
|
49
|
+
command: "/recall",
|
|
50
|
+
destructive: false,
|
|
51
|
+
description: "preview recall",
|
|
52
|
+
},
|
|
53
|
+
// Pin / unpin
|
|
54
|
+
{
|
|
55
|
+
pattern: /^\s*(?:pin|keep)\s+(?:this\s+|that\s+)?([\w-]+(?:-[A-Z0-9]+)?)\s*$/i,
|
|
56
|
+
command: "/pin",
|
|
57
|
+
destructive: false,
|
|
58
|
+
description: "pin a memory",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
pattern: /^\s*unpin\s+([\w-]+(?:-[A-Z0-9]+)?)\s*$/i,
|
|
62
|
+
command: "/unpin",
|
|
63
|
+
destructive: false,
|
|
64
|
+
description: "unpin a memory",
|
|
65
|
+
},
|
|
66
|
+
// Reinforce — explicit positive feedback
|
|
67
|
+
{
|
|
68
|
+
pattern: /^\s*(?:that('s| was)?\s+(?:helpful|useful|perfect|great)|(?:helpful|perfect|great|exactly|spot on)|good answer)[.!\s]*$/i,
|
|
69
|
+
command: "/reinforce",
|
|
70
|
+
destructive: false,
|
|
71
|
+
description: "reinforce the most recent cited memory",
|
|
72
|
+
},
|
|
73
|
+
// Attach — when the user pastes a path that looks like a file
|
|
74
|
+
{
|
|
75
|
+
pattern: /^\s*(?:attach|ingest|see this file:?)\s+(\S+)\s*$/i,
|
|
76
|
+
command: "/attach",
|
|
77
|
+
destructive: false,
|
|
78
|
+
description: "ingest a file",
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
/** Try to match a user input against the pattern rules. Returns null if no pattern fires. */
|
|
82
|
+
export function matchPattern(userInput) {
|
|
83
|
+
for (const rule of PATTERNS) {
|
|
84
|
+
const m = userInput.match(rule.pattern);
|
|
85
|
+
if (m) {
|
|
86
|
+
const captured = m[1]?.trim();
|
|
87
|
+
const args = captured ? [captured] : [];
|
|
88
|
+
return {
|
|
89
|
+
command: rule.command,
|
|
90
|
+
args,
|
|
91
|
+
confidence: "high",
|
|
92
|
+
matchedPattern: rule.pattern.source,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/** True when the input has imperative-y signals — a hint that LLM classification might pay off. */
|
|
99
|
+
export function hasImperativeSignal(userInput) {
|
|
100
|
+
const trimmed = userInput.trim().toLowerCase();
|
|
101
|
+
// Starts with a verb or "let's" / "should we" — a hint of intent
|
|
102
|
+
return /^(let'?s |should we |can you |please |go (?:ahead and )?|now |next[,:.\s])/i.test(trimmed);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Optional: ask a cheap LLM to classify the intent.
|
|
106
|
+
* Returns null when the LLM is unavailable or the response can't be parsed.
|
|
107
|
+
*/
|
|
108
|
+
export async function classifyWithLLM(config, userInput) {
|
|
109
|
+
try {
|
|
110
|
+
const provider = getLLMProvider(config, "structuring");
|
|
111
|
+
const prompt = `You are a router. Map this user message to ONE of these chat commands, or "none" if it's just a normal chat turn.
|
|
112
|
+
|
|
113
|
+
Commands:
|
|
114
|
+
/pin <id> pin a memory by ID (the user named a specific memory)
|
|
115
|
+
/unpin <id> unpin a memory by ID
|
|
116
|
+
/remember <text> save free text as a new memory
|
|
117
|
+
/save-turn save the last user+assistant exchange
|
|
118
|
+
/recall <query> preview what would be recalled for this query
|
|
119
|
+
/reinforce <id> mark a recalled memory as useful
|
|
120
|
+
/attach <path> ingest a file
|
|
121
|
+
/quit exit chat
|
|
122
|
+
|
|
123
|
+
Output STRICT JSON only:
|
|
124
|
+
{"command": "/<name>" | "none", "args": ["..."], "confidence": "high" | "medium" | "low"}
|
|
125
|
+
|
|
126
|
+
User message: ${userInput}`;
|
|
127
|
+
const raw = await provider.generate(prompt, { maxTokens: 200 });
|
|
128
|
+
const trimmed = raw.replace(/^```(?:json)?\s*|\s*```$/g, "").trim();
|
|
129
|
+
const parsed = JSON.parse(trimmed);
|
|
130
|
+
if (!parsed.command || parsed.command === "none")
|
|
131
|
+
return null;
|
|
132
|
+
const validCmds = ["/pin", "/unpin", "/remember", "/save-turn", "/recall", "/reinforce", "/attach", "/quit"];
|
|
133
|
+
if (!validCmds.includes(parsed.command))
|
|
134
|
+
return null;
|
|
135
|
+
if (parsed.confidence === "low")
|
|
136
|
+
return null;
|
|
137
|
+
return {
|
|
138
|
+
command: parsed.command,
|
|
139
|
+
args: parsed.args ?? [],
|
|
140
|
+
confidence: parsed.confidence ?? "medium",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** Whether the inferred command is destructive enough to warrant a confirm prompt. */
|
|
148
|
+
export function isDestructive(command) {
|
|
149
|
+
return command === "/quit";
|
|
150
|
+
}
|
|
151
|
+
/** Render a [Y/n/edit] prompt label for the inferred intent. */
|
|
152
|
+
export function describeIntent(intent) {
|
|
153
|
+
const argsStr = intent.args.length > 0 ? ` ${intent.args.join(" ")}` : "";
|
|
154
|
+
return `${intent.command}${argsStr}`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Hybrid classifier — returns the best-guess intent given user input,
|
|
158
|
+
* conversation context, and an optional LLM config for fallback.
|
|
159
|
+
*
|
|
160
|
+
* Strategy:
|
|
161
|
+
* 1. Pattern match first (free, fast)
|
|
162
|
+
* 2. If no pattern AND there's an imperative signal AND we have a config → ask the LLM
|
|
163
|
+
* 3. Otherwise return null (treat as a normal chat turn)
|
|
164
|
+
*/
|
|
165
|
+
export async function inferIntent(userInput, config) {
|
|
166
|
+
const fromPattern = matchPattern(userInput);
|
|
167
|
+
if (fromPattern)
|
|
168
|
+
return fromPattern;
|
|
169
|
+
if (!config)
|
|
170
|
+
return null;
|
|
171
|
+
if (!hasImperativeSignal(userInput))
|
|
172
|
+
return null;
|
|
173
|
+
return classifyWithLLM(config, userInput);
|
|
174
|
+
}
|
|
175
|
+
export function newAcceptanceLog() {
|
|
176
|
+
return { acceptCounts: new Map() };
|
|
177
|
+
}
|
|
178
|
+
const AUTO_ACCEPT_THRESHOLD = 5;
|
|
179
|
+
/** Has this pattern been accepted enough times to skip confirmation? */
|
|
180
|
+
export function shouldAutoAccept(log, matchedPattern) {
|
|
181
|
+
if (!matchedPattern)
|
|
182
|
+
return false;
|
|
183
|
+
const count = log.acceptCounts.get(matchedPattern) ?? 0;
|
|
184
|
+
return count >= AUTO_ACCEPT_THRESHOLD;
|
|
185
|
+
}
|
|
186
|
+
/** Record a confirmed acceptance of a pattern. */
|
|
187
|
+
export function recordAcceptance(log, matchedPattern) {
|
|
188
|
+
if (!matchedPattern)
|
|
189
|
+
return;
|
|
190
|
+
log.acceptCounts.set(matchedPattern, (log.acceptCounts.get(matchedPattern) ?? 0) + 1);
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=intent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent.js","sourceRoot":"","sources":["../../../src/lib/chat/intent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run one chat turn against the LLM with streaming output.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 keeps this minimal — no recall, no system prompt customization.
|
|
5
|
+
* Phase 3 adds recall integration; Phase 5 adds intent classification;
|
|
6
|
+
* Phase 6 adds gnosys-choose protocol; Phase 7 adds focus-aware system prompt.
|
|
7
|
+
*/
|
|
8
|
+
import { GnosysConfig } from "../config.js";
|
|
9
|
+
import { LLMProvider } from "../llm.js";
|
|
10
|
+
import { LLMProviderName } from "../config.js";
|
|
11
|
+
import { Turn } from "./types.js";
|
|
12
|
+
import { RecalledMemory } from "./recall.js";
|
|
13
|
+
export interface LLMTurnOptions {
|
|
14
|
+
/** Conversation buffer to send (will be formatted into a single prompt). */
|
|
15
|
+
buffer: Turn[];
|
|
16
|
+
/** New user input to append before sending. */
|
|
17
|
+
userInput: string;
|
|
18
|
+
/** Token-level streaming callback. */
|
|
19
|
+
onToken: (token: string) => void;
|
|
20
|
+
/** Recalled memories to inject into the system prompt. Empty disables recall. */
|
|
21
|
+
recalled?: RecalledMemory[];
|
|
22
|
+
}
|
|
23
|
+
export interface LLMTurnResult {
|
|
24
|
+
text: string;
|
|
25
|
+
provider: string;
|
|
26
|
+
model: string;
|
|
27
|
+
/** Memory IDs surfaced in the system prompt for this turn (used for citations). */
|
|
28
|
+
recalledIds: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run a single turn. Streams via opts.onToken. Returns the full assistant
|
|
32
|
+
* response plus the IDs of any memories injected as context.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runTurn(config: GnosysConfig, opts: LLMTurnOptions): Promise<LLMTurnResult>;
|
|
35
|
+
/** Build a provider for /provider switching mid-session. */
|
|
36
|
+
export declare function buildProvider(config: GnosysConfig, providerName: LLMProviderName, model?: string): LLMProvider;
|
|
37
|
+
//# sourceMappingURL=llmTurn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llmTurn.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/llmTurn.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAoB,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAkC,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,cAAc,EAAyB,MAAM,aAAa,CAAC;AAGpE,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,MAAM,EAAE,IAAI,EAAE,CAAC;IACf,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,iFAAiF;IACjF,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAsBD;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,aAAa,CAAC,CA0BxB;AAED,4DAA4D;AAC5D,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,eAAe,EAC7B,KAAK,CAAC,EAAE,MAAM,GACb,WAAW,CAGb"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run one chat turn against the LLM with streaming output.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 keeps this minimal — no recall, no system prompt customization.
|
|
5
|
+
* Phase 3 adds recall integration; Phase 5 adds intent classification;
|
|
6
|
+
* Phase 6 adds gnosys-choose protocol; Phase 7 adds focus-aware system prompt.
|
|
7
|
+
*/
|
|
8
|
+
import { getProviderModel } from "../config.js";
|
|
9
|
+
import { getLLMProvider, createProvider } from "../llm.js";
|
|
10
|
+
import { formatRecallForPrompt } from "./recall.js";
|
|
11
|
+
import { CHOOSE_SYSTEM_PROMPT_ADDENDUM } from "./choose.js";
|
|
12
|
+
const BASE_SYSTEM_PROMPT = `You are an assistant inside the Gnosys terminal chat — a memory-aware REPL. The user has persistent memory across sessions; relevant memories are injected as <memory id="..."> blocks before their question. Cite memory IDs in square brackets like [deci-037] when you use them. Be concise and direct. Markdown renders.${CHOOSE_SYSTEM_PROMPT_ADDENDUM}`;
|
|
13
|
+
function composeSystemPrompt(recalled) {
|
|
14
|
+
if (!recalled || recalled.length === 0)
|
|
15
|
+
return BASE_SYSTEM_PROMPT;
|
|
16
|
+
return `${BASE_SYSTEM_PROMPT}\n\n${formatRecallForPrompt(recalled)}`;
|
|
17
|
+
}
|
|
18
|
+
/** Format the conversation buffer + new input into a single prompt string. */
|
|
19
|
+
function buildPrompt(buffer, userInput) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
for (const turn of buffer) {
|
|
22
|
+
if (turn.role === "user")
|
|
23
|
+
lines.push(`User: ${turn.text}`);
|
|
24
|
+
else if (turn.role === "assistant")
|
|
25
|
+
lines.push(`Assistant: ${turn.text}`);
|
|
26
|
+
// System turns are not replayed into the prompt (they're TUI-side notices)
|
|
27
|
+
}
|
|
28
|
+
lines.push(`User: ${userInput}`);
|
|
29
|
+
lines.push(`Assistant:`);
|
|
30
|
+
return lines.join("\n\n");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Run a single turn. Streams via opts.onToken. Returns the full assistant
|
|
34
|
+
* response plus the IDs of any memories injected as context.
|
|
35
|
+
*/
|
|
36
|
+
export async function runTurn(config, opts) {
|
|
37
|
+
// Phase 2: use the synthesis provider (suitable for free-form chat).
|
|
38
|
+
// Future phases may route to a "chat" task type if added to config.
|
|
39
|
+
const provider = getLLMProvider(config, "synthesis");
|
|
40
|
+
const prompt = buildPrompt(opts.buffer, opts.userInput);
|
|
41
|
+
const system = composeSystemPrompt(opts.recalled);
|
|
42
|
+
let full = "";
|
|
43
|
+
await provider.generate(prompt, { system, stream: true, maxTokens: 4096 }, {
|
|
44
|
+
onToken: (token) => {
|
|
45
|
+
full += token;
|
|
46
|
+
opts.onToken(token);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
text: full,
|
|
51
|
+
provider: provider.name,
|
|
52
|
+
model: provider.model,
|
|
53
|
+
recalledIds: (opts.recalled ?? []).map((m) => m.id),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Build a provider for /provider switching mid-session. */
|
|
57
|
+
export function buildProvider(config, providerName, model) {
|
|
58
|
+
const resolvedModel = model ?? getProviderModel(config, providerName);
|
|
59
|
+
return createProvider(providerName, resolvedModel, config);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=llmTurn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llmTurn.js","sourceRoot":"","sources":["../../../src/lib/chat/llmTurn.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAgB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAe,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGxE,OAAO,EAAkB,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAqB5D,MAAM,kBAAkB,GAAG,+TAA+T,6BAA6B,EAAE,CAAC;AAE1X,SAAS,mBAAmB,CAAC,QAAsC;IACjE,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,kBAAkB,CAAC;IAClE,OAAO,GAAG,kBAAkB,OAAO,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC;AACvE,CAAC;AAED,8EAA8E;AAC9E,SAAS,WAAW,CAAC,MAAc,EAAE,SAAiB;IACpD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;aACtD,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1E,2EAA2E;IAC7E,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,SAAS,SAAS,EAAE,CAAC,CAAC;IACjC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACzB,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,MAAoB,EACpB,IAAoB;IAEpB,qEAAqE;IACrE,oEAAoE;IACpE,MAAM,QAAQ,GAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAElE,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAElD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,MAAM,QAAQ,CAAC,QAAQ,CACrB,MAAM,EACN,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EACzC;QACE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACjB,IAAI,IAAI,KAAK,CAAC;YACd,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;KACF,CACF,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,IAAI;QACV,QAAQ,EAAE,QAAQ,CAAC,IAAI;QACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,WAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpD,CAAC;AACJ,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,aAAa,CAC3B,MAAoB,EACpB,YAA6B,EAC7B,KAAc;IAEd,MAAM,aAAa,GAAG,KAAK,IAAI,gBAAgB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACtE,OAAO,cAAc,CAAC,YAAY,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat-side memory recall.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the existing federated search to produce a set of memories suitable
|
|
5
|
+
* for injection into the LLM system prompt. Always includes pinned memories
|
|
6
|
+
* regardless of search relevance.
|
|
7
|
+
*/
|
|
8
|
+
import { GnosysDB } from "../db.js";
|
|
9
|
+
import { Turn } from "./types.js";
|
|
10
|
+
export type RecallScope = "project" | "user" | "global" | "federated";
|
|
11
|
+
export interface RecallOptions {
|
|
12
|
+
/** Search query — usually the latest user input plus a tail of conversation. */
|
|
13
|
+
query: string;
|
|
14
|
+
/** Recall scope. "federated" means search across all scopes with tier boosting. */
|
|
15
|
+
scope: RecallScope;
|
|
16
|
+
/** Active project ID (used by federated search for tier boosting). Null when no project context. */
|
|
17
|
+
projectId: string | null;
|
|
18
|
+
/** Confidence threshold — drop memories below this. 0 disables. */
|
|
19
|
+
threshold: number;
|
|
20
|
+
/** Pinned memory IDs — always included, ranked first. */
|
|
21
|
+
pinnedIds: string[];
|
|
22
|
+
/** Max non-pinned results (default 5). */
|
|
23
|
+
limit?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface RecalledMemory {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
content: string;
|
|
29
|
+
category: string;
|
|
30
|
+
scope: string;
|
|
31
|
+
confidence: number;
|
|
32
|
+
/** True when included because it's pinned (not because the query matched it). */
|
|
33
|
+
pinned: boolean;
|
|
34
|
+
/** Federated search score; 0 for pinned memories that didn't match the query. */
|
|
35
|
+
score: number;
|
|
36
|
+
}
|
|
37
|
+
export interface RecallResult {
|
|
38
|
+
memories: RecalledMemory[];
|
|
39
|
+
/** The query string actually run (may differ from input — e.g. trimmed). */
|
|
40
|
+
query: string;
|
|
41
|
+
/** Total candidates considered before threshold/limit. */
|
|
42
|
+
considered: number;
|
|
43
|
+
}
|
|
44
|
+
/** Build a search query from the current user input + a few recent turns for context. */
|
|
45
|
+
export declare function buildRecallQuery(userInput: string, buffer: Turn[]): string;
|
|
46
|
+
/**
|
|
47
|
+
* Run recall for one chat turn. Returns pinned memories first (always),
|
|
48
|
+
* then top federated matches respecting scope and threshold.
|
|
49
|
+
*/
|
|
50
|
+
export declare function runRecall(db: GnosysDB, opts: RecallOptions): RecallResult;
|
|
51
|
+
/**
|
|
52
|
+
* Render recalled memories as a system-prompt block. Pinned memories are
|
|
53
|
+
* marked so the model knows the user has explicitly anchored them.
|
|
54
|
+
*/
|
|
55
|
+
export declare function formatRecallForPrompt(memories: RecalledMemory[]): string;
|
|
56
|
+
/** Reinforce a memory: bump modified date so it's surfaced as recent. */
|
|
57
|
+
export declare function reinforceMemory(db: GnosysDB, memoryId: string): boolean;
|
|
58
|
+
//# sourceMappingURL=recall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/recall.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAY,MAAM,UAAU,CAAC;AAE9C,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEtE,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,KAAK,EAAE,WAAW,CAAC;IACnB,oGAAoG;IACpG,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,MAAM,EAAE,OAAO,CAAC;IAChB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAQ1E;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,GAAG,YAAY,CA4DzE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAUxE;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CASvE"}
|