fixo-cli 1.0.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/LICENSE +201 -0
- package/README.md +530 -0
- package/dist/agent/agent-client.d.ts +108 -0
- package/dist/agent/agent-client.d.ts.map +1 -0
- package/dist/agent/agent-client.js +1247 -0
- package/dist/agent/agent-client.js.map +1 -0
- package/dist/agent/agent-pool.d.ts +20 -0
- package/dist/agent/agent-pool.d.ts.map +1 -0
- package/dist/agent/agent-pool.js +217 -0
- package/dist/agent/agent-pool.js.map +1 -0
- package/dist/agent/background-awareness.d.ts +55 -0
- package/dist/agent/background-awareness.d.ts.map +1 -0
- package/dist/agent/background-awareness.js +104 -0
- package/dist/agent/background-awareness.js.map +1 -0
- package/dist/agent/command-parser.d.ts +33 -0
- package/dist/agent/command-parser.d.ts.map +1 -0
- package/dist/agent/command-parser.js +120 -0
- package/dist/agent/command-parser.js.map +1 -0
- package/dist/agent/context-budget.d.ts +91 -0
- package/dist/agent/context-budget.d.ts.map +1 -0
- package/dist/agent/context-budget.js +219 -0
- package/dist/agent/context-budget.js.map +1 -0
- package/dist/agent/conversation.d.ts +190 -0
- package/dist/agent/conversation.d.ts.map +1 -0
- package/dist/agent/conversation.js +547 -0
- package/dist/agent/conversation.js.map +1 -0
- package/dist/agent/hooks.d.ts +72 -0
- package/dist/agent/hooks.d.ts.map +1 -0
- package/dist/agent/hooks.js +214 -0
- package/dist/agent/hooks.js.map +1 -0
- package/dist/agent/mcp-bridge.d.ts +13 -0
- package/dist/agent/mcp-bridge.d.ts.map +1 -0
- package/dist/agent/mcp-bridge.js +86 -0
- package/dist/agent/mcp-bridge.js.map +1 -0
- package/dist/agent/mcp-client.d.ts +24 -0
- package/dist/agent/mcp-client.d.ts.map +1 -0
- package/dist/agent/mcp-client.js +146 -0
- package/dist/agent/mcp-client.js.map +1 -0
- package/dist/agent/mcp-manager.d.ts +13 -0
- package/dist/agent/mcp-manager.d.ts.map +1 -0
- package/dist/agent/mcp-manager.js +84 -0
- package/dist/agent/mcp-manager.js.map +1 -0
- package/dist/agent/mcp-registry.d.ts +45 -0
- package/dist/agent/mcp-registry.d.ts.map +1 -0
- package/dist/agent/mcp-registry.js +98 -0
- package/dist/agent/mcp-registry.js.map +1 -0
- package/dist/agent/orchestrator.d.ts +14 -0
- package/dist/agent/orchestrator.d.ts.map +1 -0
- package/dist/agent/orchestrator.js +118 -0
- package/dist/agent/orchestrator.js.map +1 -0
- package/dist/agent/parser-adapter.d.ts +120 -0
- package/dist/agent/parser-adapter.d.ts.map +1 -0
- package/dist/agent/parser-adapter.js +265 -0
- package/dist/agent/parser-adapter.js.map +1 -0
- package/dist/agent/parsers/imports.d.ts +11 -0
- package/dist/agent/parsers/imports.d.ts.map +1 -0
- package/dist/agent/parsers/imports.js +94 -0
- package/dist/agent/parsers/imports.js.map +1 -0
- package/dist/agent/parsers/shell.d.ts +23 -0
- package/dist/agent/parsers/shell.d.ts.map +1 -0
- package/dist/agent/parsers/shell.js +200 -0
- package/dist/agent/parsers/shell.js.map +1 -0
- package/dist/agent/parsers/symbols.d.ts +17 -0
- package/dist/agent/parsers/symbols.d.ts.map +1 -0
- package/dist/agent/parsers/symbols.js +103 -0
- package/dist/agent/parsers/symbols.js.map +1 -0
- package/dist/agent/permissions.d.ts +65 -0
- package/dist/agent/permissions.d.ts.map +1 -0
- package/dist/agent/permissions.js +219 -0
- package/dist/agent/permissions.js.map +1 -0
- package/dist/agent/predictive-gate.d.ts +69 -0
- package/dist/agent/predictive-gate.d.ts.map +1 -0
- package/dist/agent/predictive-gate.js +128 -0
- package/dist/agent/predictive-gate.js.map +1 -0
- package/dist/agent/provider-cooldown.d.ts +144 -0
- package/dist/agent/provider-cooldown.d.ts.map +1 -0
- package/dist/agent/provider-cooldown.js +300 -0
- package/dist/agent/provider-cooldown.js.map +1 -0
- package/dist/agent/providers-manager.d.ts +109 -0
- package/dist/agent/providers-manager.d.ts.map +1 -0
- package/dist/agent/providers-manager.js +464 -0
- package/dist/agent/providers-manager.js.map +1 -0
- package/dist/agent/repo-map.d.ts +6 -0
- package/dist/agent/repo-map.d.ts.map +1 -0
- package/dist/agent/repo-map.js +221 -0
- package/dist/agent/repo-map.js.map +1 -0
- package/dist/agent/retry.d.ts +103 -0
- package/dist/agent/retry.d.ts.map +1 -0
- package/dist/agent/retry.js +276 -0
- package/dist/agent/retry.js.map +1 -0
- package/dist/agent/search/index.d.ts +61 -0
- package/dist/agent/search/index.d.ts.map +1 -0
- package/dist/agent/search/index.js +314 -0
- package/dist/agent/search/index.js.map +1 -0
- package/dist/agent/single-agent.d.ts +76 -0
- package/dist/agent/single-agent.d.ts.map +1 -0
- package/dist/agent/single-agent.js +697 -0
- package/dist/agent/single-agent.js.map +1 -0
- package/dist/agent/skills.d.ts +22 -0
- package/dist/agent/skills.d.ts.map +1 -0
- package/dist/agent/skills.js +139 -0
- package/dist/agent/skills.js.map +1 -0
- package/dist/agent/stream-glue.d.ts +85 -0
- package/dist/agent/stream-glue.d.ts.map +1 -0
- package/dist/agent/stream-glue.js +120 -0
- package/dist/agent/stream-glue.js.map +1 -0
- package/dist/agent/subagent.d.ts +72 -0
- package/dist/agent/subagent.d.ts.map +1 -0
- package/dist/agent/subagent.js +193 -0
- package/dist/agent/subagent.js.map +1 -0
- package/dist/agent/telemetry.d.ts +192 -0
- package/dist/agent/telemetry.d.ts.map +1 -0
- package/dist/agent/telemetry.js +400 -0
- package/dist/agent/telemetry.js.map +1 -0
- package/dist/agent/tokenizer.d.ts +42 -0
- package/dist/agent/tokenizer.d.ts.map +1 -0
- package/dist/agent/tokenizer.js +107 -0
- package/dist/agent/tokenizer.js.map +1 -0
- package/dist/agent/tool-executor.d.ts +289 -0
- package/dist/agent/tool-executor.d.ts.map +1 -0
- package/dist/agent/tool-executor.js +2519 -0
- package/dist/agent/tool-executor.js.map +1 -0
- package/dist/agent/web-impl.d.ts +2 -0
- package/dist/agent/web-impl.d.ts.map +1 -0
- package/dist/agent/web-impl.js +34 -0
- package/dist/agent/web-impl.js.map +1 -0
- package/dist/agent/web.d.ts +8 -0
- package/dist/agent/web.d.ts.map +1 -0
- package/dist/agent/web.js +8 -0
- package/dist/agent/web.js.map +1 -0
- package/dist/agent/worker-agent.d.ts +27 -0
- package/dist/agent/worker-agent.d.ts.map +1 -0
- package/dist/agent/worker-agent.js +503 -0
- package/dist/agent/worker-agent.js.map +1 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +138 -0
- package/dist/config.js.map +1 -0
- package/dist/context/fixo-md-watcher.d.ts +42 -0
- package/dist/context/fixo-md-watcher.d.ts.map +1 -0
- package/dist/context/fixo-md-watcher.js +126 -0
- package/dist/context/fixo-md-watcher.js.map +1 -0
- package/dist/context/fixo-md.d.ts +50 -0
- package/dist/context/fixo-md.d.ts.map +1 -0
- package/dist/context/fixo-md.js +118 -0
- package/dist/context/fixo-md.js.map +1 -0
- package/dist/context/todo.d.ts +65 -0
- package/dist/context/todo.d.ts.map +1 -0
- package/dist/context/todo.js +194 -0
- package/dist/context/todo.js.map +1 -0
- package/dist/git/git-manager.d.ts +33 -0
- package/dist/git/git-manager.d.ts.map +1 -0
- package/dist/git/git-manager.js +293 -0
- package/dist/git/git-manager.js.map +1 -0
- package/dist/git/git-ops.d.ts +10 -0
- package/dist/git/git-ops.d.ts.map +1 -0
- package/dist/git/git-ops.js +131 -0
- package/dist/git/git-ops.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +352 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +30 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +273 -0
- package/dist/indexer.js.map +1 -0
- package/dist/lsp/lsp-client.d.ts +24 -0
- package/dist/lsp/lsp-client.d.ts.map +1 -0
- package/dist/lsp/lsp-client.js +205 -0
- package/dist/lsp/lsp-client.js.map +1 -0
- package/dist/lsp/lsp-manager.d.ts +17 -0
- package/dist/lsp/lsp-manager.d.ts.map +1 -0
- package/dist/lsp/lsp-manager.js +154 -0
- package/dist/lsp/lsp-manager.js.map +1 -0
- package/dist/lsp/lsp-pre-save.d.ts +137 -0
- package/dist/lsp/lsp-pre-save.d.ts.map +1 -0
- package/dist/lsp/lsp-pre-save.js +245 -0
- package/dist/lsp/lsp-pre-save.js.map +1 -0
- package/dist/lsp/syntax-fallback.d.ts +83 -0
- package/dist/lsp/syntax-fallback.d.ts.map +1 -0
- package/dist/lsp/syntax-fallback.js +275 -0
- package/dist/lsp/syntax-fallback.js.map +1 -0
- package/dist/model-outcomes.d.ts +12 -0
- package/dist/model-outcomes.d.ts.map +1 -0
- package/dist/model-outcomes.js +46 -0
- package/dist/model-outcomes.js.map +1 -0
- package/dist/planner.d.ts +32 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +163 -0
- package/dist/planner.js.map +1 -0
- package/dist/project-memory.d.ts +29 -0
- package/dist/project-memory.d.ts.map +1 -0
- package/dist/project-memory.js +349 -0
- package/dist/project-memory.js.map +1 -0
- package/dist/review.d.ts +2 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +61 -0
- package/dist/review.js.map +1 -0
- package/dist/runtime/background-jobs.d.ts +97 -0
- package/dist/runtime/background-jobs.d.ts.map +1 -0
- package/dist/runtime/background-jobs.js +331 -0
- package/dist/runtime/background-jobs.js.map +1 -0
- package/dist/runtime/credential-vault.d.ts +124 -0
- package/dist/runtime/credential-vault.d.ts.map +1 -0
- package/dist/runtime/credential-vault.js +184 -0
- package/dist/runtime/credential-vault.js.map +1 -0
- package/dist/runtime/loop-trap.d.ts +197 -0
- package/dist/runtime/loop-trap.d.ts.map +1 -0
- package/dist/runtime/loop-trap.js +420 -0
- package/dist/runtime/loop-trap.js.map +1 -0
- package/dist/runtime/policy.d.ts +15 -0
- package/dist/runtime/policy.d.ts.map +1 -0
- package/dist/runtime/policy.js +60 -0
- package/dist/runtime/policy.js.map +1 -0
- package/dist/runtime/redaction.d.ts +66 -0
- package/dist/runtime/redaction.d.ts.map +1 -0
- package/dist/runtime/redaction.js +155 -0
- package/dist/runtime/redaction.js.map +1 -0
- package/dist/runtime/session-snapshots.d.ts +76 -0
- package/dist/runtime/session-snapshots.d.ts.map +1 -0
- package/dist/runtime/session-snapshots.js +166 -0
- package/dist/runtime/session-snapshots.js.map +1 -0
- package/dist/runtime/staging.d.ts +205 -0
- package/dist/runtime/staging.d.ts.map +1 -0
- package/dist/runtime/staging.js +526 -0
- package/dist/runtime/staging.js.map +1 -0
- package/dist/runtime/task-session.d.ts +95 -0
- package/dist/runtime/task-session.d.ts.map +1 -0
- package/dist/runtime/task-session.js +263 -0
- package/dist/runtime/task-session.js.map +1 -0
- package/dist/runtime/worktree.d.ts +55 -0
- package/dist/runtime/worktree.d.ts.map +1 -0
- package/dist/runtime/worktree.js +175 -0
- package/dist/runtime/worktree.js.map +1 -0
- package/dist/setup-wizard.d.ts +8 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +73 -0
- package/dist/setup-wizard.js.map +1 -0
- package/dist/shared/content.d.ts +43 -0
- package/dist/shared/content.d.ts.map +1 -0
- package/dist/shared/content.js +61 -0
- package/dist/shared/content.js.map +1 -0
- package/dist/shared/types.d.ts +217 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +3 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/test-runner.d.ts +5 -0
- package/dist/test-runner.d.ts.map +1 -0
- package/dist/test-runner.js +42 -0
- package/dist/test-runner.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/ascii.d.ts +23 -0
- package/dist/ui/ascii.d.ts.map +1 -0
- package/dist/ui/ascii.js +45 -0
- package/dist/ui/ascii.js.map +1 -0
- package/dist/ui/colors.d.ts +111 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +166 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/image-attach.d.ts +27 -0
- package/dist/ui/image-attach.d.ts.map +1 -0
- package/dist/ui/image-attach.js +100 -0
- package/dist/ui/image-attach.js.map +1 -0
- package/dist/ui/index.d.ts +18 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +18 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/markdown-stream.d.ts +91 -0
- package/dist/ui/markdown-stream.d.ts.map +1 -0
- package/dist/ui/markdown-stream.js +524 -0
- package/dist/ui/markdown-stream.js.map +1 -0
- package/dist/ui/plan-renderer.d.ts +36 -0
- package/dist/ui/plan-renderer.d.ts.map +1 -0
- package/dist/ui/plan-renderer.js +79 -0
- package/dist/ui/plan-renderer.js.map +1 -0
- package/dist/ui/prompt.d.ts +11 -0
- package/dist/ui/prompt.d.ts.map +1 -0
- package/dist/ui/prompt.js +1960 -0
- package/dist/ui/prompt.js.map +1 -0
- package/dist/ui/render-primitives.d.ts +117 -0
- package/dist/ui/render-primitives.d.ts.map +1 -0
- package/dist/ui/render-primitives.js +322 -0
- package/dist/ui/render-primitives.js.map +1 -0
- package/dist/ui/render.d.ts +133 -0
- package/dist/ui/render.d.ts.map +1 -0
- package/dist/ui/render.js +547 -0
- package/dist/ui/render.js.map +1 -0
- package/dist/ui/session-header.d.ts +30 -0
- package/dist/ui/session-header.d.ts.map +1 -0
- package/dist/ui/session-header.js +74 -0
- package/dist/ui/session-header.js.map +1 -0
- package/dist/workspace-guard.d.ts +68 -0
- package/dist/workspace-guard.d.ts.map +1 -0
- package/dist/workspace-guard.js +168 -0
- package/dist/workspace-guard.js.map +1 -0
- package/dist/workspace-lock.d.ts +27 -0
- package/dist/workspace-lock.d.ts.map +1 -0
- package/dist/workspace-lock.js +95 -0
- package/dist/workspace-lock.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* symbols.ts — Per-language symbol extraction using pure regex.
|
|
3
|
+
*
|
|
4
|
+
* Output is intentionally limited to the first `MAX_SYMBOLS_PER_FILE`
|
|
5
|
+
* matches so we never blow up the LLM's context window on a single
|
|
6
|
+
* oversized file. Coordinates are 1-based, line-inclusive, end-inclusive
|
|
7
|
+
* to match the LSP convention.
|
|
8
|
+
*/
|
|
9
|
+
const MAX_SYMBOLS_PER_FILE = 100;
|
|
10
|
+
const JS_TS_PATTERNS = [
|
|
11
|
+
// export class Foo / class Foo
|
|
12
|
+
{ re: /^[ \t]*(?:export\s+(?:default\s+)?|abstract\s+)?class\s+([A-Za-z_$][\w$]*)/gm, kind: 'class' },
|
|
13
|
+
// export interface Foo
|
|
14
|
+
{ re: /^[ \t]*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/gm, kind: 'interface' },
|
|
15
|
+
// export type Foo
|
|
16
|
+
{ re: /^[ \t]*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)/gm, kind: 'type' },
|
|
17
|
+
// export enum Foo
|
|
18
|
+
{ re: /^[ \t]*(?:export\s+)?enum\s+([A-Za-z_$][\w$]*)/gm, kind: 'enum' },
|
|
19
|
+
// function foo(...)
|
|
20
|
+
{
|
|
21
|
+
re: /^[ \t]*(?:export\s+(?:default\s+)?)?(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/gm,
|
|
22
|
+
kind: 'function',
|
|
23
|
+
},
|
|
24
|
+
// const/let/var foo = ...
|
|
25
|
+
{
|
|
26
|
+
re: /^[ \t]*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)/gm,
|
|
27
|
+
kind: 'const',
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
const PYTHON_PATTERNS = [
|
|
31
|
+
{ re: /^[ \t]*class\s+([A-Za-z_][\w]*)/gm, kind: 'class' },
|
|
32
|
+
{ re: /^[ \t]*(?:async\s+)?def\s+([A-Za-z_][\w]*)/gm, kind: 'function' },
|
|
33
|
+
];
|
|
34
|
+
const GO_PATTERNS = [
|
|
35
|
+
{ re: /^[ \t]*type\s+([A-Za-z_][\w]*)\s+struct/gm, kind: 'type' },
|
|
36
|
+
{ re: /^[ \t]*type\s+([A-Za-z_][\w]*)\s+interface/gm, kind: 'interface' },
|
|
37
|
+
{ re: /^[ \t]*func\s+(?:\([^)]*\)\s+)?([A-Za-z_][\w]*)/gm, kind: 'function' },
|
|
38
|
+
{ re: /^[ \t]*(?:var|const)\s+([A-Za-z_][\w]*)/gm, kind: 'const' },
|
|
39
|
+
];
|
|
40
|
+
const RUST_PATTERNS = [
|
|
41
|
+
{ re: /^[ \t]*(?:pub\s+)?(?:async\s+)?fn\s+([A-Za-z_][\w]*)/gm, kind: 'function' },
|
|
42
|
+
{ re: /^[ \t]*(?:pub\s+)?struct\s+([A-Za-z_][\w]*)/gm, kind: 'type' },
|
|
43
|
+
{ re: /^[ \t]*(?:pub\s+)?enum\s+([A-Za-z_][\w]*)/gm, kind: 'enum' },
|
|
44
|
+
{ re: /^[ \t]*(?:pub\s+)?trait\s+([A-Za-z_][\w]*)/gm, kind: 'interface' },
|
|
45
|
+
];
|
|
46
|
+
const GENERIC_PATTERNS = [
|
|
47
|
+
{
|
|
48
|
+
re: /\b(?:export\s+)?(?:class|function|interface|type|const|let|var|enum)\s+([A-Za-z_$][\w$]*)/g,
|
|
49
|
+
kind: 'unknown',
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
const PATTERNS = {
|
|
53
|
+
typescript: JS_TS_PATTERNS,
|
|
54
|
+
javascript: JS_TS_PATTERNS,
|
|
55
|
+
python: PYTHON_PATTERNS,
|
|
56
|
+
go: GO_PATTERNS,
|
|
57
|
+
rust: RUST_PATTERNS,
|
|
58
|
+
bash: [],
|
|
59
|
+
json: [],
|
|
60
|
+
markdown: [],
|
|
61
|
+
generic: GENERIC_PATTERNS,
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Extracts symbol declarations from `source`. The result is capped at
|
|
65
|
+
* `MAX_SYMBOLS_PER_FILE` entries and deduped by name; the first
|
|
66
|
+
* occurrence wins so an `export class Foo` is preferred over a later
|
|
67
|
+
* `class Foo` reference.
|
|
68
|
+
*/
|
|
69
|
+
export function extractSymbols(source, language) {
|
|
70
|
+
const patterns = PATTERNS[language] ?? GENERIC_PATTERNS;
|
|
71
|
+
if (patterns.length === 0)
|
|
72
|
+
return [];
|
|
73
|
+
const out = [];
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
const lines = source.split('\n');
|
|
76
|
+
const totalLines = lines.length;
|
|
77
|
+
for (const { re, kind } of patterns) {
|
|
78
|
+
re.lastIndex = 0;
|
|
79
|
+
let match;
|
|
80
|
+
while ((match = re.exec(source)) !== null) {
|
|
81
|
+
if (out.length >= MAX_SYMBOLS_PER_FILE)
|
|
82
|
+
return out;
|
|
83
|
+
const name = match[1];
|
|
84
|
+
if (!name || seen.has(name))
|
|
85
|
+
continue;
|
|
86
|
+
seen.add(name);
|
|
87
|
+
const startIndex = match.index;
|
|
88
|
+
const line = indexToLine(source, startIndex);
|
|
89
|
+
const exported = /^\s*export\b/m.test(match[0]);
|
|
90
|
+
out.push({ name, kind, line, endLine: Math.min(totalLines, line + 5), exported });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
function indexToLine(source, index) {
|
|
96
|
+
let line = 1;
|
|
97
|
+
for (let i = 0; i < index && i < source.length; i++) {
|
|
98
|
+
if (source.charCodeAt(i) === 10)
|
|
99
|
+
line++;
|
|
100
|
+
}
|
|
101
|
+
return line;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=symbols.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../../src/agent/parsers/symbols.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type PolicyProfile } from '../runtime/policy.js';
|
|
2
|
+
export type PermissionDecision = 'allow' | 'ask' | 'deny';
|
|
3
|
+
export interface PermissionRule {
|
|
4
|
+
/** Glob pattern in the form `Tool(pattern)`. */
|
|
5
|
+
pattern: string;
|
|
6
|
+
decision: PermissionDecision;
|
|
7
|
+
/** Optional human-readable reason (shown when denying). */
|
|
8
|
+
reason?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PermissionsFile {
|
|
11
|
+
version: 1;
|
|
12
|
+
rules: PermissionRule[];
|
|
13
|
+
}
|
|
14
|
+
export interface PermissionCheckResult {
|
|
15
|
+
decision: PermissionDecision;
|
|
16
|
+
reason: string;
|
|
17
|
+
matchedRule: string | null;
|
|
18
|
+
source: 'rule' | 'default-ask' | 'fallback-policy';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Tools introduced in Phases 1–3. When the user runs one of
|
|
22
|
+
* these with no matching rule, we force `ask` (rather than
|
|
23
|
+
* letting `decidePolicy` quietly return `allow`).
|
|
24
|
+
*/
|
|
25
|
+
export declare const NEW_TOOL_DEFAULT_ASK: ReadonlySet<string>;
|
|
26
|
+
/** Read `.fixo/permissions.json` from cwd. */
|
|
27
|
+
export declare function loadPermissionsFile(cwd: string): PermissionsFile | null;
|
|
28
|
+
/** Save the permissions file. */
|
|
29
|
+
export declare function savePermissionsFile(cwd: string, file: PermissionsFile): {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
error?: string;
|
|
32
|
+
};
|
|
33
|
+
/** Get the permissions file path (test-only introspection). */
|
|
34
|
+
export declare function getPermissionsPath(cwd: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Convert a glob pattern to a RegExp anchored at both ends.
|
|
37
|
+
* - `*` matches any chars except `/`
|
|
38
|
+
* - `**` matches any chars including `/`
|
|
39
|
+
* - `?` matches any single char except `/`
|
|
40
|
+
*/
|
|
41
|
+
export declare function globToRegExp(glob: string): RegExp;
|
|
42
|
+
/** Parse `Tool(pattern)` into `{tool, argPattern}`. */
|
|
43
|
+
export declare function parseRulePattern(pattern: string): {
|
|
44
|
+
tool: string;
|
|
45
|
+
argPattern: string | null;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Build the input string that the rule's argPattern matches
|
|
49
|
+
* against. We pick a sensible string projection of args
|
|
50
|
+
* for the tool at hand. For most tools, that's the joined
|
|
51
|
+
* string of all arg values. This is intentionally simple —
|
|
52
|
+
* it lets users write rules like Edit(src/dstar/star.ts)
|
|
53
|
+
* and have them work.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildArgString(tool: string, args: Record<string, unknown>): string;
|
|
56
|
+
/** Match a tool call against the rule list (first match wins). */
|
|
57
|
+
export declare function matchPermission(tool: string, args: Record<string, unknown>, rules: PermissionRule[]): PermissionRule | null;
|
|
58
|
+
/**
|
|
59
|
+
* Top-level check: try a rule match, then fall back to
|
|
60
|
+
* `decidePolicy` for the legacy policy logic, then apply
|
|
61
|
+
* the default-ask rule for new tools. Returns a unified
|
|
62
|
+
* `PermissionCheckResult`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function checkPermission(tool: string, args: Record<string, unknown>, cwd: string, profile: PolicyProfile): PermissionCheckResult;
|
|
65
|
+
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/agent/permissions.ts"],"names":[],"mappings":"AAuBA,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,iBAAiB,CAAC;CACpD;AAID;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAOnD,CAAC;AAEH,8CAA8C;AAC9C,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAUvE;AAED,iCAAiC;AACjC,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,eAAe,GACpB;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CASjC;AAED,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA6BjD;AAED,uDAAuD;AACvD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAI7F;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAclF;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,EAAE,cAAc,EAAE,GACtB,cAAc,GAAG,IAAI,CAevB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,aAAa,GACrB,qBAAqB,CAqCvB"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* permissions.ts — Pattern-based permission rules.
|
|
3
|
+
*
|
|
4
|
+
* Phase 3.5 spec:
|
|
5
|
+
* - Each rule is a glob-style pattern over
|
|
6
|
+
* `Tool(arg-glob)`. Examples:
|
|
7
|
+
* Bash(npm test:star) — any run_command with the
|
|
8
|
+
* "npm test..." shell text.
|
|
9
|
+
* Edit(src/dstar/star.ts) — any tool whose args.path
|
|
10
|
+
* matches the glob.
|
|
11
|
+
* - First-match-wins: rules are evaluated top-to-bottom;
|
|
12
|
+
* the first match returns the decision.
|
|
13
|
+
* - Decision values: allow (auto), ask (default — prompt
|
|
14
|
+
* the user), deny (block).
|
|
15
|
+
* - Fallback: when no rule matches, call
|
|
16
|
+
* decidePolicy(profile, action, detail, allowRules)
|
|
17
|
+
* from runtime/policy.ts.
|
|
18
|
+
* - Storage: <cwd>/.fixo/permissions.json.
|
|
19
|
+
* - Default rules: every tool introduced in Phases 1–3
|
|
20
|
+
* defaults to ask if no rule is configured.
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { decidePolicy, } from '../runtime/policy.js';
|
|
25
|
+
const PERMISSIONS_PATH_FOR = (cwd) => path.join(cwd, '.fixo', 'permissions.json');
|
|
26
|
+
/**
|
|
27
|
+
* Tools introduced in Phases 1–3. When the user runs one of
|
|
28
|
+
* these with no matching rule, we force `ask` (rather than
|
|
29
|
+
* letting `decidePolicy` quietly return `allow`).
|
|
30
|
+
*/
|
|
31
|
+
export const NEW_TOOL_DEFAULT_ASK = new Set([
|
|
32
|
+
'str_replace',
|
|
33
|
+
'glob_files',
|
|
34
|
+
'todo_write',
|
|
35
|
+
'run_command_async',
|
|
36
|
+
'poll_command_status',
|
|
37
|
+
'kill_command',
|
|
38
|
+
]);
|
|
39
|
+
/** Read `.fixo/permissions.json` from cwd. */
|
|
40
|
+
export function loadPermissionsFile(cwd) {
|
|
41
|
+
const p = PERMISSIONS_PATH_FOR(cwd);
|
|
42
|
+
if (!fs.existsSync(p))
|
|
43
|
+
return null;
|
|
44
|
+
try {
|
|
45
|
+
const raw = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
46
|
+
if (raw.version !== 1 || !Array.isArray(raw.rules))
|
|
47
|
+
return null;
|
|
48
|
+
return raw;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Save the permissions file. */
|
|
55
|
+
export function savePermissionsFile(cwd, file) {
|
|
56
|
+
const p = PERMISSIONS_PATH_FOR(cwd);
|
|
57
|
+
try {
|
|
58
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
59
|
+
fs.writeFileSync(p, JSON.stringify(file, null, 2));
|
|
60
|
+
return { ok: true };
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return { ok: false, error: err.message };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Get the permissions file path (test-only introspection). */
|
|
67
|
+
export function getPermissionsPath(cwd) {
|
|
68
|
+
return PERMISSIONS_PATH_FOR(cwd);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Convert a glob pattern to a RegExp anchored at both ends.
|
|
72
|
+
* - `*` matches any chars except `/`
|
|
73
|
+
* - `**` matches any chars including `/`
|
|
74
|
+
* - `?` matches any single char except `/`
|
|
75
|
+
*/
|
|
76
|
+
export function globToRegExp(glob) {
|
|
77
|
+
let out = '^';
|
|
78
|
+
for (let i = 0; i < glob.length; i++) {
|
|
79
|
+
const c = glob[i];
|
|
80
|
+
if (c === '*') {
|
|
81
|
+
if (glob[i + 1] === '*') {
|
|
82
|
+
// dstar: matches zero or more path segments. If a
|
|
83
|
+
// slash follows, the slash is optional (so the
|
|
84
|
+
// pattern matches a/b/c as well as a).
|
|
85
|
+
if (glob[i + 2] === '/') {
|
|
86
|
+
out += '(?:.*/)?';
|
|
87
|
+
i += 2;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
out += '.*';
|
|
91
|
+
i += 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
out += '[^/]*';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (c === '?') {
|
|
99
|
+
out += '[^/]';
|
|
100
|
+
}
|
|
101
|
+
else if ('.+()[]{}|^$\\'.includes(c)) {
|
|
102
|
+
out += '\\' + c;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
out += c;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
out += '$';
|
|
109
|
+
return new RegExp(out);
|
|
110
|
+
}
|
|
111
|
+
/** Parse `Tool(pattern)` into `{tool, argPattern}`. */
|
|
112
|
+
export function parseRulePattern(pattern) {
|
|
113
|
+
const m = /^([A-Za-z_][A-Za-z0-9_]*)(?:\((.+)\))?$/.exec(pattern);
|
|
114
|
+
if (!m)
|
|
115
|
+
return { tool: pattern, argPattern: null };
|
|
116
|
+
return { tool: m[1], argPattern: m[2] ?? null };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build the input string that the rule's argPattern matches
|
|
120
|
+
* against. We pick a sensible string projection of args
|
|
121
|
+
* for the tool at hand. For most tools, that's the joined
|
|
122
|
+
* string of all arg values. This is intentionally simple —
|
|
123
|
+
* it lets users write rules like Edit(src/dstar/star.ts)
|
|
124
|
+
* and have them work.
|
|
125
|
+
*/
|
|
126
|
+
export function buildArgString(tool, args) {
|
|
127
|
+
// For path-shaped tools, prefer the `path` / `filePath` /
|
|
128
|
+
// `cmd` field so globs land where users expect.
|
|
129
|
+
const pathField = typeof args.path === 'string' ? args.path :
|
|
130
|
+
typeof args.filePath === 'string' ? args.filePath :
|
|
131
|
+
typeof args.cmd === 'string' ? args.cmd :
|
|
132
|
+
typeof args.command === 'string' ? args.command :
|
|
133
|
+
null;
|
|
134
|
+
if (pathField !== null)
|
|
135
|
+
return pathField;
|
|
136
|
+
return Object.values(args)
|
|
137
|
+
.filter(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')
|
|
138
|
+
.map(v => String(v))
|
|
139
|
+
.join(' ');
|
|
140
|
+
}
|
|
141
|
+
/** Match a tool call against the rule list (first match wins). */
|
|
142
|
+
export function matchPermission(tool, args, rules) {
|
|
143
|
+
for (const rule of rules) {
|
|
144
|
+
const { tool: ruleTool, argPattern } = parseRulePattern(rule.pattern);
|
|
145
|
+
if (ruleTool !== tool)
|
|
146
|
+
continue;
|
|
147
|
+
if (argPattern === null)
|
|
148
|
+
return rule;
|
|
149
|
+
const argString = buildArgString(tool, args);
|
|
150
|
+
try {
|
|
151
|
+
const re = globToRegExp(argPattern);
|
|
152
|
+
if (re.test(argString))
|
|
153
|
+
return rule;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Malformed glob — skip the rule rather than crashing.
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Top-level check: try a rule match, then fall back to
|
|
164
|
+
* `decidePolicy` for the legacy policy logic, then apply
|
|
165
|
+
* the default-ask rule for new tools. Returns a unified
|
|
166
|
+
* `PermissionCheckResult`.
|
|
167
|
+
*/
|
|
168
|
+
export function checkPermission(tool, args, cwd, profile) {
|
|
169
|
+
const file = loadPermissionsFile(cwd);
|
|
170
|
+
const rules = file?.rules ?? [];
|
|
171
|
+
// 1. First-match-wins rule check.
|
|
172
|
+
const match = matchPermission(tool, args, rules);
|
|
173
|
+
if (match) {
|
|
174
|
+
return {
|
|
175
|
+
decision: match.decision,
|
|
176
|
+
reason: match.reason ?? `${tool} matched rule ${match.pattern}`,
|
|
177
|
+
matchedRule: match.pattern,
|
|
178
|
+
source: 'rule',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// 2. Default-ask for tools introduced in Phases 1–3.
|
|
182
|
+
if (NEW_TOOL_DEFAULT_ASK.has(tool)) {
|
|
183
|
+
return {
|
|
184
|
+
decision: 'ask',
|
|
185
|
+
reason: `${tool} is a new tool with no rule — default-ask applies`,
|
|
186
|
+
matchedRule: null,
|
|
187
|
+
source: 'default-ask',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// 3. Fallback to legacy decidePolicy.
|
|
191
|
+
const action = tool === 'run_command' || tool === 'run_command_async' ? 'command' :
|
|
192
|
+
MUTATION_TOOLS.has(tool) ? 'write' :
|
|
193
|
+
'read';
|
|
194
|
+
const detail = buildArgString(tool, args);
|
|
195
|
+
const policyDecision = decidePolicy(profile, action, detail);
|
|
196
|
+
return {
|
|
197
|
+
decision: policyDecision.allowed
|
|
198
|
+
? (policyDecision.needsConfirmation ? 'ask' : 'allow')
|
|
199
|
+
: 'deny',
|
|
200
|
+
reason: policyDecision.reason,
|
|
201
|
+
matchedRule: null,
|
|
202
|
+
source: 'fallback-policy',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const MUTATION_TOOLS = new Set([
|
|
206
|
+
'write_file',
|
|
207
|
+
'apply_patch',
|
|
208
|
+
'replace_range',
|
|
209
|
+
'insert_after',
|
|
210
|
+
'rename_file',
|
|
211
|
+
'delete_file',
|
|
212
|
+
'create_branch',
|
|
213
|
+
'commit_changes',
|
|
214
|
+
'push_branch',
|
|
215
|
+
'create_pull_request',
|
|
216
|
+
'str_replace',
|
|
217
|
+
'todo_write',
|
|
218
|
+
]);
|
|
219
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/agent/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAGL,YAAY,GACb,MAAM,sBAAsB,CAAC;AAwB9B,MAAM,oBAAoB,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC;AAE1F;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC;IAC/D,aAAa;IACb,YAAY;IACZ,YAAY;IACZ,mBAAmB;IACnB,qBAAqB;IACrB,cAAc;CACf,CAAC,CAAC;AAEH,8CAA8C;AAC9C,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,MAAM,CAAC,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAoB,CAAC;QACvE,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChE,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,mBAAmB,CACjC,GAAW,EACX,IAAqB;IAErB,MAAM,CAAC,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACnD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,OAAO,oBAAoB,CAAC,GAAG,CAAC,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,GAAG,GAAG,GAAG,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBACxB,kDAAkD;gBAClD,+CAA+C;gBAC/C,uCAAuC;gBACvC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBACxB,GAAG,IAAI,UAAU,CAAC;oBAClB,CAAC,IAAI,CAAC,CAAC;gBACT,CAAC;qBAAM,CAAC;oBACN,GAAG,IAAI,IAAI,CAAC;oBACZ,CAAC,IAAI,CAAC,CAAC;gBACT,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,GAAG,IAAI,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACrB,GAAG,IAAI,MAAM,CAAC;QAChB,CAAC;aAAM,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,GAAG,IAAI,IAAI,GAAG,CAAC,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,GAAG,IAAI,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IACD,GAAG,IAAI,GAAG,CAAC;IACX,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,CAAC,GAAG,yCAAyC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClE,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IACnD,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;AAClD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAA6B;IACxE,0DAA0D;IAC1D,gDAAgD;IAChD,MAAM,SAAS,GACb,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACzC,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBACjD,IAAI,CAAC;IACP,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACzC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;SACvB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS,CAAC;SACrF,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;SACnB,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,IAA6B,EAC7B,KAAuB;IAEvB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtE,IAAI,QAAQ,KAAK,IAAI;YAAE,SAAS;QAChC,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACrC,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;YACpC,IAAI,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC;gBAAE,OAAO,IAAI,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;YACvD,SAAS;QACX,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,IAA6B,EAC7B,GAAW,EACX,OAAsB;IAEtB,MAAM,IAAI,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;IAChC,kCAAkC;IAClC,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACjD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO;YACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,IAAI,iBAAiB,KAAK,CAAC,OAAO,EAAE;YAC/D,WAAW,EAAE,KAAK,CAAC,OAAO;YAC1B,MAAM,EAAE,MAAM;SACf,CAAC;IACJ,CAAC;IACD,qDAAqD;IACrD,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,MAAM,EAAE,GAAG,IAAI,mDAAmD;YAClE,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,aAAa;SACtB,CAAC;IACJ,CAAC;IACD,sCAAsC;IACtC,MAAM,MAAM,GACV,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,mBAAmB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACpE,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACpC,MAAM,CAAC;IACT,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1C,MAAM,cAAc,GAAmB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7E,OAAO;QACL,QAAQ,EAAE,cAAc,CAAC,OAAO;YAC9B,CAAC,CAAC,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;YACtD,CAAC,CAAC,MAAM;QACV,MAAM,EAAE,cAAc,CAAC,MAAM;QAC7B,WAAW,EAAE,IAAI;QACjB,MAAM,EAAE,iBAAiB;KAC1B,CAAC;AACJ,CAAC;AAED,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC;IAClD,YAAY;IACZ,aAAa;IACb,eAAe;IACf,cAAc;IACd,aAAa;IACb,aAAa;IACb,eAAe;IACf,gBAAgB;IAChB,aAAa;IACb,qBAAqB;IACrB,aAAa;IACb,YAAY;CACb,CAAC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* predictive-gate.ts — token-aware predictive read gate.
|
|
3
|
+
*
|
|
4
|
+
* Phase 4 spec:
|
|
5
|
+
* - The existing 15 KiB / 350-line byte gate in `executeReadFile`
|
|
6
|
+
* is a cheap pre-filter. It does not know about the *current*
|
|
7
|
+
* conversation pressure, so a series of small reads can still
|
|
8
|
+
* blow the window.
|
|
9
|
+
* - This module adds a second tier: before we read a file, we
|
|
10
|
+
* estimate its token cost via `gpt-tokenizer` and compare the
|
|
11
|
+
* projected total (conversation + file) against the model's
|
|
12
|
+
* effective input limit. If the projected total exceeds the
|
|
13
|
+
* configured pct (default 85%), we defer and return a
|
|
14
|
+
* directive telling the model to use the surgical alternative.
|
|
15
|
+
*
|
|
16
|
+
* Layering:
|
|
17
|
+
* read_file dispatch
|
|
18
|
+
* ├── existing byte gate (cheapest)
|
|
19
|
+
* └── predictive gate (this module — fires only when byte gate passes)
|
|
20
|
+
*
|
|
21
|
+
* Fail-open: any error in encoding or model resolution allows the
|
|
22
|
+
* read to proceed. The gate is an optimisation, never a correctness
|
|
23
|
+
* boundary, so availability beats precision.
|
|
24
|
+
*/
|
|
25
|
+
/** Default fraction of the model window we will let the next read fill. */
|
|
26
|
+
export declare const DEFAULT_PREDICTIVE_BUDGET_PCT = 0.85;
|
|
27
|
+
export interface ReadEstimate {
|
|
28
|
+
/** File size in bytes (0 if the file is missing). */
|
|
29
|
+
readonly bytes: number;
|
|
30
|
+
/** Estimated token cost of reading the full file. */
|
|
31
|
+
readonly projectedTokens: number;
|
|
32
|
+
/** True if the file could be tokenised exactly (small enough to fit in sample). */
|
|
33
|
+
readonly exact: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface DeferDecision {
|
|
36
|
+
/** True if the caller should NOT read the file. */
|
|
37
|
+
readonly defer: boolean;
|
|
38
|
+
/** Human-readable explanation. */
|
|
39
|
+
readonly reason: string;
|
|
40
|
+
/** Projected total tokens (conversation + read). */
|
|
41
|
+
readonly projectedTotal: number;
|
|
42
|
+
/** Effective hard cap used for the decision. */
|
|
43
|
+
readonly hardCap: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Estimate the token cost of reading a file. The function never
|
|
47
|
+
* throws — on any error it returns `{ bytes: 0, projectedTokens: 0,
|
|
48
|
+
* exact: false }` and the caller treats that as "no signal, fall
|
|
49
|
+
* through."
|
|
50
|
+
*/
|
|
51
|
+
export declare function estimateReadCost(absolutePath: string, model: string | undefined | null): ReadEstimate;
|
|
52
|
+
/**
|
|
53
|
+
* Decide whether the projected read would push the conversation
|
|
54
|
+
* over the model's effective budget.
|
|
55
|
+
*
|
|
56
|
+
* `conversationTokens` is the count *before* this read. `model` is
|
|
57
|
+
* used to look up the per-model context limit (see
|
|
58
|
+
* `resolveModelContextLimit`). `budgetPct` lets callers tune the
|
|
59
|
+
* threshold; passing `1.0` effectively disables the gate.
|
|
60
|
+
*/
|
|
61
|
+
export declare function shouldDeferRead(estimate: ReadEstimate, conversationTokens: number, model: string | undefined | null, budgetPct?: number): DeferDecision;
|
|
62
|
+
/**
|
|
63
|
+
* Format the synthetic directive returned to the model when the
|
|
64
|
+
* gate fires. The shape matches the existing
|
|
65
|
+
* `[Context-Budget Guard]` directive used by the byte gate so the
|
|
66
|
+
* model's existing routing behaviour applies unchanged.
|
|
67
|
+
*/
|
|
68
|
+
export declare function formatPredictiveGateDirective(relativePath: string, estimate: ReadEstimate, decision: DeferDecision): string;
|
|
69
|
+
//# sourceMappingURL=predictive-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"predictive-gate.d.ts","sourceRoot":"","sources":["../../src/agent/predictive-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAYH,2EAA2E;AAC3E,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAC/B,YAAY,CAoCd;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,YAAY,EACtB,kBAAkB,EAAE,MAAM,EAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAChC,SAAS,GAAE,MAAsC,GAChD,aAAa,CAkBf;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,aAAa,GACtB,MAAM,CASR"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* predictive-gate.ts — token-aware predictive read gate.
|
|
3
|
+
*
|
|
4
|
+
* Phase 4 spec:
|
|
5
|
+
* - The existing 15 KiB / 350-line byte gate in `executeReadFile`
|
|
6
|
+
* is a cheap pre-filter. It does not know about the *current*
|
|
7
|
+
* conversation pressure, so a series of small reads can still
|
|
8
|
+
* blow the window.
|
|
9
|
+
* - This module adds a second tier: before we read a file, we
|
|
10
|
+
* estimate its token cost via `gpt-tokenizer` and compare the
|
|
11
|
+
* projected total (conversation + file) against the model's
|
|
12
|
+
* effective input limit. If the projected total exceeds the
|
|
13
|
+
* configured pct (default 85%), we defer and return a
|
|
14
|
+
* directive telling the model to use the surgical alternative.
|
|
15
|
+
*
|
|
16
|
+
* Layering:
|
|
17
|
+
* read_file dispatch
|
|
18
|
+
* ├── existing byte gate (cheapest)
|
|
19
|
+
* └── predictive gate (this module — fires only when byte gate passes)
|
|
20
|
+
*
|
|
21
|
+
* Fail-open: any error in encoding or model resolution allows the
|
|
22
|
+
* read to proceed. The gate is an optimisation, never a correctness
|
|
23
|
+
* boundary, so availability beats precision.
|
|
24
|
+
*/
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import { countTokens } from './tokenizer.js';
|
|
27
|
+
import { resolveModelContextLimit } from './conversation.js';
|
|
28
|
+
/** ~4 bytes per token is the industry-standard rough estimate for English text. */
|
|
29
|
+
const FALLBACK_BYTES_PER_TOKEN = 4;
|
|
30
|
+
/** Cap the sample we tokenise to keep cold reads predictable. */
|
|
31
|
+
const SAMPLE_BYTES = 16 * 1024;
|
|
32
|
+
/** Default fraction of the model window we will let the next read fill. */
|
|
33
|
+
export const DEFAULT_PREDICTIVE_BUDGET_PCT = 0.85;
|
|
34
|
+
/**
|
|
35
|
+
* Estimate the token cost of reading a file. The function never
|
|
36
|
+
* throws — on any error it returns `{ bytes: 0, projectedTokens: 0,
|
|
37
|
+
* exact: false }` and the caller treats that as "no signal, fall
|
|
38
|
+
* through."
|
|
39
|
+
*/
|
|
40
|
+
export function estimateReadCost(absolutePath, model) {
|
|
41
|
+
try {
|
|
42
|
+
const stat = fs.statSync(absolutePath);
|
|
43
|
+
if (!stat.isFile())
|
|
44
|
+
return { bytes: 0, projectedTokens: 0, exact: false };
|
|
45
|
+
const bytes = stat.size;
|
|
46
|
+
if (bytes === 0)
|
|
47
|
+
return { bytes: 0, projectedTokens: 0, exact: true };
|
|
48
|
+
if (bytes <= SAMPLE_BYTES) {
|
|
49
|
+
// Small enough to tokenise exactly.
|
|
50
|
+
const text = fs.readFileSync(absolutePath, 'utf-8');
|
|
51
|
+
try {
|
|
52
|
+
const projectedTokens = countTokens(text, model);
|
|
53
|
+
return { bytes, projectedTokens, exact: true };
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return { bytes, projectedTokens: Math.ceil(bytes / FALLBACK_BYTES_PER_TOKEN), exact: false };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Larger file: tokenise the first SAMPLE_BYTES and extrapolate.
|
|
60
|
+
const fd = fs.openSync(absolutePath, 'r');
|
|
61
|
+
try {
|
|
62
|
+
const buf = Buffer.alloc(SAMPLE_BYTES);
|
|
63
|
+
const read = fs.readSync(fd, buf, 0, SAMPLE_BYTES, 0);
|
|
64
|
+
const sample = buf.subarray(0, read).toString('utf-8');
|
|
65
|
+
const sampleTokens = countTokens(sample, model);
|
|
66
|
+
const tokensPerByte = sampleTokens / Math.max(read, 1);
|
|
67
|
+
const projectedTokens = Math.ceil(tokensPerByte * bytes);
|
|
68
|
+
return { bytes, projectedTokens, exact: false };
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return { bytes, projectedTokens: Math.ceil(bytes / FALLBACK_BYTES_PER_TOKEN), exact: false };
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
try {
|
|
75
|
+
fs.closeSync(fd);
|
|
76
|
+
}
|
|
77
|
+
catch { /* ignore */ }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return { bytes: 0, projectedTokens: 0, exact: false };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Decide whether the projected read would push the conversation
|
|
86
|
+
* over the model's effective budget.
|
|
87
|
+
*
|
|
88
|
+
* `conversationTokens` is the count *before* this read. `model` is
|
|
89
|
+
* used to look up the per-model context limit (see
|
|
90
|
+
* `resolveModelContextLimit`). `budgetPct` lets callers tune the
|
|
91
|
+
* threshold; passing `1.0` effectively disables the gate.
|
|
92
|
+
*/
|
|
93
|
+
export function shouldDeferRead(estimate, conversationTokens, model, budgetPct = DEFAULT_PREDICTIVE_BUDGET_PCT) {
|
|
94
|
+
const modelLimit = resolveModelContextLimit(model);
|
|
95
|
+
// budgetPct is sanitised: clamp to (0, 1].
|
|
96
|
+
const pct = !Number.isFinite(budgetPct) || budgetPct <= 0
|
|
97
|
+
? DEFAULT_PREDICTIVE_BUDGET_PCT
|
|
98
|
+
: Math.min(budgetPct, 1);
|
|
99
|
+
const hardCap = Math.floor(modelLimit * pct);
|
|
100
|
+
const projectedTotal = Math.max(0, conversationTokens) + Math.max(0, estimate.projectedTokens);
|
|
101
|
+
if (projectedTotal <= hardCap) {
|
|
102
|
+
return { defer: false, reason: 'within budget', projectedTotal, hardCap };
|
|
103
|
+
}
|
|
104
|
+
const overshoot = projectedTotal - hardCap;
|
|
105
|
+
return {
|
|
106
|
+
defer: true,
|
|
107
|
+
reason: `predicted ${projectedTotal.toLocaleString()} tokens would exceed budget ${hardCap.toLocaleString()} by ${overshoot.toLocaleString()}`,
|
|
108
|
+
projectedTotal,
|
|
109
|
+
hardCap,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Format the synthetic directive returned to the model when the
|
|
114
|
+
* gate fires. The shape matches the existing
|
|
115
|
+
* `[Context-Budget Guard]` directive used by the byte gate so the
|
|
116
|
+
* model's existing routing behaviour applies unchanged.
|
|
117
|
+
*/
|
|
118
|
+
export function formatPredictiveGateDirective(relativePath, estimate, decision) {
|
|
119
|
+
return [
|
|
120
|
+
`[Context-Budget Guard] read_file ${relativePath} deferred.`,
|
|
121
|
+
` bytes=${estimate.bytes.toLocaleString()} projected_tokens=${estimate.projectedTokens.toLocaleString()}${estimate.exact ? '' : ' (estimated)'}`,
|
|
122
|
+
` ${decision.reason}.`,
|
|
123
|
+
` → Use extract_symbols or extract_imports to scan the file's surface, then`,
|
|
124
|
+
` read_file with explicit lines, or use str_replace to edit without`,
|
|
125
|
+
` pulling the full file into context.`,
|
|
126
|
+
].join('\n');
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=predictive-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"predictive-gate.js","sourceRoot":"","sources":["../../src/agent/predictive-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,mFAAmF;AACnF,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAEnC,iEAAiE;AACjE,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC;AAE/B,2EAA2E;AAC3E,MAAM,CAAC,MAAM,6BAA6B,GAAG,IAAI,CAAC;AAsBlD;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAAoB,EACpB,KAAgC;IAEhC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;QACxB,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAEtE,IAAI,KAAK,IAAI,YAAY,EAAE,CAAC;YAC1B,oCAAoC;YACpC,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACpD,IAAI,CAAC;gBACH,MAAM,eAAe,GAAG,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBACjD,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACjD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,wBAAwB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YAC/F,CAAC;QACH,CAAC;QAED,gEAAgE;QAChE,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;YACtD,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACvD,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAChD,MAAM,aAAa,GAAG,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACvD,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,CAAC;YACzD,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,wBAAwB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC/F,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBAAC,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACxD,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAsB,EACtB,kBAA0B,EAC1B,KAAgC,EAChC,YAAoB,6BAA6B;IAEjD,MAAM,UAAU,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;IACnD,2CAA2C;IAC3C,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC;QACvD,CAAC,CAAC,6BAA6B;QAC/B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC;IAC7C,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC/F,IAAI,cAAc,IAAI,OAAO,EAAE,CAAC;QAC9B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;IAC5E,CAAC;IACD,MAAM,SAAS,GAAG,cAAc,GAAG,OAAO,CAAC;IAC3C,OAAO;QACL,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,aAAa,cAAc,CAAC,cAAc,EAAE,+BAA+B,OAAO,CAAC,cAAc,EAAE,OAAO,SAAS,CAAC,cAAc,EAAE,EAAE;QAC9I,cAAc;QACd,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,6BAA6B,CAC3C,YAAoB,EACpB,QAAsB,EACtB,QAAuB;IAEvB,OAAO;QACL,oCAAoC,YAAY,YAAY;QAC5D,WAAW,QAAQ,CAAC,KAAK,CAAC,cAAc,EAAE,qBAAqB,QAAQ,CAAC,eAAe,CAAC,cAAc,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE;QACjJ,KAAK,QAAQ,CAAC,MAAM,GAAG;QACvB,6EAA6E;QAC7E,uEAAuE;QACvE,yCAAyC;KAC1C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|