pi-forge 0.0.0 → 1.1.4
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 +21 -0
- package/README.md +48 -4
- package/bin/pi-forge.mjs +37 -0
- package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js +34 -0
- package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js.map +1 -0
- package/dist/client/assets/index-B-529kgJ.css +32 -0
- package/dist/client/assets/index-BzKzxXFs.js +392 -0
- package/dist/client/assets/index-BzKzxXFs.js.map +1 -0
- package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js +3 -0
- package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js.map +1 -0
- package/dist/client/icons/icon-192.png +0 -0
- package/dist/client/icons/icon-512.png +0 -0
- package/dist/client/icons/icon-maskable-512.png +0 -0
- package/dist/client/icons/icon.svg +9 -0
- package/dist/client/index.html +24 -0
- package/dist/client/manifest.webmanifest +1 -0
- package/dist/client/offline.html +142 -0
- package/dist/client/sw.js +3 -0
- package/dist/client/sw.js.map +1 -0
- package/dist/client/workbox-6d7155ed.js +3 -0
- package/dist/client/workbox-6d7155ed.js.map +1 -0
- package/dist/server/agent-resource-loader.js +126 -0
- package/dist/server/agent-resource-loader.js.map +1 -0
- package/dist/server/attachment-converters.js +96 -0
- package/dist/server/attachment-converters.js.map +1 -0
- package/dist/server/auth.js +209 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/compaction-history.js +106 -0
- package/dist/server/compaction-history.js.map +1 -0
- package/dist/server/concurrency.js +49 -0
- package/dist/server/concurrency.js.map +1 -0
- package/dist/server/config-export.js +220 -0
- package/dist/server/config-export.js.map +1 -0
- package/dist/server/config-manager.js +528 -0
- package/dist/server/config-manager.js.map +1 -0
- package/dist/server/config.js +326 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/conversion-worker.mjs +90 -0
- package/dist/server/diagnostics.js +137 -0
- package/dist/server/diagnostics.js.map +1 -0
- package/dist/server/extensions-discovery.js +147 -0
- package/dist/server/extensions-discovery.js.map +1 -0
- package/dist/server/file-manager.js +734 -0
- package/dist/server/file-manager.js.map +1 -0
- package/dist/server/file-references.js +215 -0
- package/dist/server/file-references.js.map +1 -0
- package/dist/server/file-searcher.js +385 -0
- package/dist/server/file-searcher.js.map +1 -0
- package/dist/server/git-runner.js +684 -0
- package/dist/server/git-runner.js.map +1 -0
- package/dist/server/index.js +468 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/mcp/config.js +133 -0
- package/dist/server/mcp/config.js.map +1 -0
- package/dist/server/mcp/manager.js +351 -0
- package/dist/server/mcp/manager.js.map +1 -0
- package/dist/server/mcp/tool-bridge.js +173 -0
- package/dist/server/mcp/tool-bridge.js.map +1 -0
- package/dist/server/project-manager.js +301 -0
- package/dist/server/project-manager.js.map +1 -0
- package/dist/server/pty-manager.js +354 -0
- package/dist/server/pty-manager.js.map +1 -0
- package/dist/server/routes/_schemas.js +73 -0
- package/dist/server/routes/_schemas.js.map +1 -0
- package/dist/server/routes/auth.js +164 -0
- package/dist/server/routes/auth.js.map +1 -0
- package/dist/server/routes/config.js +1163 -0
- package/dist/server/routes/config.js.map +1 -0
- package/dist/server/routes/control.js +464 -0
- package/dist/server/routes/control.js.map +1 -0
- package/dist/server/routes/exec.js +217 -0
- package/dist/server/routes/exec.js.map +1 -0
- package/dist/server/routes/files.js +847 -0
- package/dist/server/routes/files.js.map +1 -0
- package/dist/server/routes/git.js +837 -0
- package/dist/server/routes/git.js.map +1 -0
- package/dist/server/routes/health.js +97 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/mcp.js +300 -0
- package/dist/server/routes/mcp.js.map +1 -0
- package/dist/server/routes/projects.js +259 -0
- package/dist/server/routes/projects.js.map +1 -0
- package/dist/server/routes/prompt.js +496 -0
- package/dist/server/routes/prompt.js.map +1 -0
- package/dist/server/routes/sessions.js +783 -0
- package/dist/server/routes/sessions.js.map +1 -0
- package/dist/server/routes/stream.js +69 -0
- package/dist/server/routes/stream.js.map +1 -0
- package/dist/server/routes/terminal.js +335 -0
- package/dist/server/routes/terminal.js.map +1 -0
- package/dist/server/session-registry.js +1197 -0
- package/dist/server/session-registry.js.map +1 -0
- package/dist/server/skill-overrides.js +151 -0
- package/dist/server/skill-overrides.js.map +1 -0
- package/dist/server/skills-export.js +257 -0
- package/dist/server/skills-export.js.map +1 -0
- package/dist/server/sse-bridge.js +220 -0
- package/dist/server/sse-bridge.js.map +1 -0
- package/dist/server/tool-overrides.js +277 -0
- package/dist/server/tool-overrides.js.map +1 -0
- package/dist/server/turn-diff-builder.js +280 -0
- package/dist/server/turn-diff-builder.js.map +1 -0
- package/package.json +53 -12
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
/**
|
|
3
|
+
* Translate a single MCP tool advertised by a connected MCP server
|
|
4
|
+
* into a pi `ToolDefinition` the agent can call.
|
|
5
|
+
*
|
|
6
|
+
* The translated tool's name is namespaced as `<server>__<tool>` so
|
|
7
|
+
* multiple MCP servers can advertise the same tool name without
|
|
8
|
+
* colliding (e.g. two servers both exposing `search`). Pi enforces
|
|
9
|
+
* unique tool names at agent-init; the prefix guarantees uniqueness.
|
|
10
|
+
*
|
|
11
|
+
* `parameters` wraps the MCP tool's JSON Schema with `Type.Unsafe<...>`.
|
|
12
|
+
* Pi runs structural validation on tool-call arguments using whatever
|
|
13
|
+
* is in `parameters`, so the JSON Schema flows through directly.
|
|
14
|
+
*
|
|
15
|
+
* Tool execution forwards to `client.callTool({ name, arguments })`
|
|
16
|
+
* and converts the MCP `CallToolResult.content` array into pi's
|
|
17
|
+
* `(TextContent | ImageContent)[]` shape. Resource-link / unknown
|
|
18
|
+
* content blocks are stringified as JSON text rather than dropped, so
|
|
19
|
+
* the agent at least sees them.
|
|
20
|
+
*/
|
|
21
|
+
export function bridgeMcpTool(opts) {
|
|
22
|
+
const prefixedName = `${opts.serverName}__${opts.toolName}`;
|
|
23
|
+
const description = opts.description.length > 0
|
|
24
|
+
? opts.description
|
|
25
|
+
: `MCP tool '${opts.toolName}' from server '${opts.serverName}'.`;
|
|
26
|
+
return {
|
|
27
|
+
name: prefixedName,
|
|
28
|
+
label: `MCP: ${opts.serverName}/${opts.toolName}`,
|
|
29
|
+
description,
|
|
30
|
+
parameters: Type.Unsafe(opts.inputSchema),
|
|
31
|
+
async execute(_toolCallId, params, signal) {
|
|
32
|
+
const client = opts.getClient();
|
|
33
|
+
if (client === undefined) {
|
|
34
|
+
return errorResult(`MCP server '${opts.serverName}' is not connected. Re-enable it in Settings → MCP, or check the server logs.`);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const res = await client.callTool({
|
|
38
|
+
name: opts.toolName,
|
|
39
|
+
arguments: params ?? {},
|
|
40
|
+
}, undefined, signal !== undefined ? { signal } : undefined);
|
|
41
|
+
return mcpResultToAgentResult(res);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return errorResult(`MCP tool '${prefixedName}' threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function errorResult(message) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text: message }],
|
|
52
|
+
details: undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Map MCP `CallToolResult.content` to pi's content array shape.
|
|
57
|
+
* - `text` → `{ type: "text", text }`
|
|
58
|
+
* - `image` → `{ type: "image", data, mimeType }` (data is base64)
|
|
59
|
+
* - `resource` /
|
|
60
|
+
* `resource_link` / unknown → JSON-stringified into a text block.
|
|
61
|
+
*
|
|
62
|
+
* `isError: true` is preserved as a leading "[error]" prefix on the
|
|
63
|
+
* first text block so the agent sees something acted-upon rather
|
|
64
|
+
* than a silent dropped result.
|
|
65
|
+
*/
|
|
66
|
+
export function mcpResultToAgentResult(res) {
|
|
67
|
+
const r = (res ?? {});
|
|
68
|
+
const isError = r.isError === true;
|
|
69
|
+
const content = [];
|
|
70
|
+
const blocks = Array.isArray(r.content) ? r.content : [];
|
|
71
|
+
for (const block of blocks) {
|
|
72
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
73
|
+
content.push({ type: "text", text: block.text });
|
|
74
|
+
}
|
|
75
|
+
else if (block.type === "image" &&
|
|
76
|
+
typeof block.data === "string" &&
|
|
77
|
+
typeof block.mimeType === "string") {
|
|
78
|
+
content.push({ type: "image", data: block.data, mimeType: block.mimeType });
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// Resource links, audio (rare), or unknown future block types.
|
|
82
|
+
// Stringify so the agent at least gets the payload — a silent
|
|
83
|
+
// drop would look like a successful no-op.
|
|
84
|
+
content.push({
|
|
85
|
+
type: "text",
|
|
86
|
+
text: `[${String(block.type ?? "unknown")}] ${JSON.stringify(block)}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (content.length === 0) {
|
|
91
|
+
// Some MCP servers signal success with an empty content array;
|
|
92
|
+
// include structuredContent if present so the agent has something
|
|
93
|
+
// to work with.
|
|
94
|
+
if (r.structuredContent !== undefined) {
|
|
95
|
+
content.push({ type: "text", text: JSON.stringify(r.structuredContent) });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
content.push({ type: "text", text: isError ? "[error] (no detail)" : "(empty result)" });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (isError && content[0]?.type === "text") {
|
|
102
|
+
content[0] = { type: "text", text: `[error] ${content[0].text}` };
|
|
103
|
+
}
|
|
104
|
+
return { content: capTextContent(content), details: r.structuredContent ?? null };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Default cap on the total *text* size (across all text blocks) of an
|
|
108
|
+
* MCP tool result, in characters. ~100k chars ≈ ~25k tokens via the
|
|
109
|
+
* standard chars/4 heuristic the SDK uses elsewhere — large enough
|
|
110
|
+
* that almost no legitimate single tool call hits it, small enough
|
|
111
|
+
* that one accidental "list everything" doesn't blow a 200k context
|
|
112
|
+
* window. Image blocks are passed through untouched (truncating
|
|
113
|
+
* base64 mid-byte breaks the image; image tokens are
|
|
114
|
+
* provider-specific anyway and not measured here).
|
|
115
|
+
*
|
|
116
|
+
* Split: 60% head + 40% tail. Head usually carries summary / total /
|
|
117
|
+
* schema context that the agent needs to interpret the rest; tail
|
|
118
|
+
* usually has the most recent / most relevant items in time-ordered
|
|
119
|
+
* lists.
|
|
120
|
+
*
|
|
121
|
+
* Marker text is read by the agent and tells it (a) truncation
|
|
122
|
+
* happened, (b) by how much, (c) what to do about it. Imperative
|
|
123
|
+
* phrasing nudges the model to narrow scope rather than re-running
|
|
124
|
+
* the same call.
|
|
125
|
+
*
|
|
126
|
+
* No per-tool override yet — add when a real workload needs a higher
|
|
127
|
+
* or lower cap. Hardcoded constant is the deliberate first cut.
|
|
128
|
+
*/
|
|
129
|
+
export const MCP_TEXT_CAP_CHARS = 100_000;
|
|
130
|
+
export const MCP_TEXT_HEAD_RATIO = 0.6;
|
|
131
|
+
export function capTextContent(blocks) {
|
|
132
|
+
let totalText = 0;
|
|
133
|
+
for (const b of blocks) {
|
|
134
|
+
if (b.type === "text")
|
|
135
|
+
totalText += b.text.length;
|
|
136
|
+
}
|
|
137
|
+
if (totalText <= MCP_TEXT_CAP_CHARS)
|
|
138
|
+
return blocks;
|
|
139
|
+
// Flatten all text blocks into one head+tail string. Preserves
|
|
140
|
+
// image blocks in their original positions; drops in-between text
|
|
141
|
+
// separators in exchange for staying under the cap.
|
|
142
|
+
const flat = blocks
|
|
143
|
+
.filter((b) => b.type === "text")
|
|
144
|
+
.map((b) => b.text)
|
|
145
|
+
.join("\n\n");
|
|
146
|
+
const headLen = Math.floor(MCP_TEXT_CAP_CHARS * MCP_TEXT_HEAD_RATIO);
|
|
147
|
+
const tailLen = MCP_TEXT_CAP_CHARS - headLen;
|
|
148
|
+
const head = flat.slice(0, headLen);
|
|
149
|
+
const tail = flat.slice(flat.length - tailLen);
|
|
150
|
+
const omitted = flat.length - headLen - tailLen;
|
|
151
|
+
const marker = `\n\n[--- ${omitted.toLocaleString()} characters (~${Math.round(omitted / 4).toLocaleString()} tokens) ` +
|
|
152
|
+
`truncated to keep the result under the ${MCP_TEXT_CAP_CHARS.toLocaleString()}-char cap. ` +
|
|
153
|
+
`Refine the query (smaller scope, narrower filter, paginate if available) to see the missing middle. ---]\n\n`;
|
|
154
|
+
const truncatedText = head + marker + tail;
|
|
155
|
+
// Keep one text block with the truncated payload + every image
|
|
156
|
+
// block from the original (in its original relative order). Drop
|
|
157
|
+
// duplicate text blocks since they were already absorbed into
|
|
158
|
+
// `flat`.
|
|
159
|
+
const out = [];
|
|
160
|
+
let textInjected = false;
|
|
161
|
+
for (const b of blocks) {
|
|
162
|
+
if (b.type === "text") {
|
|
163
|
+
if (!textInjected) {
|
|
164
|
+
out.push({ type: "text", text: truncatedText });
|
|
165
|
+
textInjected = true;
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
out.push(b);
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=tool-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-bridge.js","sourceRoot":"","sources":["../../src/mcp/tool-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { config } from "./config.js";
|
|
5
|
+
import { clearProjectOverrides as clearProjectSkillOverrides } from "./skill-overrides.js";
|
|
6
|
+
/**
|
|
7
|
+
* Project ids are always `randomUUID()` output. Mirrors the same
|
|
8
|
+
* regex used in session-registry's `sessionDirFor()` validator.
|
|
9
|
+
* Codified here too because the cascade rm path builds a
|
|
10
|
+
* filesystem destination from the id and should reject anything
|
|
11
|
+
* that could escape `${SESSION_DIR}`.
|
|
12
|
+
*/
|
|
13
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
14
|
+
export class PathOutsideWorkspaceError extends Error {
|
|
15
|
+
constructor(path) {
|
|
16
|
+
super(`path outside workspace: ${path}`);
|
|
17
|
+
this.name = "PathOutsideWorkspaceError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class NotADirectoryError extends Error {
|
|
21
|
+
constructor(path) {
|
|
22
|
+
super(`not a directory: ${path}`);
|
|
23
|
+
this.name = "NotADirectoryError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class ProjectNotFoundError extends Error {
|
|
27
|
+
constructor(id) {
|
|
28
|
+
super(`project not found: ${id}`);
|
|
29
|
+
this.name = "ProjectNotFoundError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class InvalidNameError extends Error {
|
|
33
|
+
constructor(message = "invalid name") {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "InvalidNameError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export class InvalidDirectoryNameError extends Error {
|
|
39
|
+
constructor(message = "invalid directory name") {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "InvalidDirectoryNameError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class DuplicatePathError extends Error {
|
|
45
|
+
constructor(path) {
|
|
46
|
+
super(`a project already points at: ${path}`);
|
|
47
|
+
this.name = "DuplicatePathError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const PROJECTS_FILE = () => join(config.forgeDataDir, "projects.json");
|
|
51
|
+
/** True iff `target` is the same path as `root` or strictly inside it. */
|
|
52
|
+
export function isInsideWorkspace(target, root = config.workspacePath) {
|
|
53
|
+
const resolvedTarget = resolve(target);
|
|
54
|
+
const resolvedRoot = resolve(root);
|
|
55
|
+
if (resolvedTarget === resolvedRoot)
|
|
56
|
+
return true;
|
|
57
|
+
const rel = relative(resolvedRoot, resolvedTarget);
|
|
58
|
+
return rel.length > 0 && !rel.startsWith("..") && !rel.startsWith(`..${sep}`);
|
|
59
|
+
}
|
|
60
|
+
async function ensureConfigDir() {
|
|
61
|
+
await mkdir(config.forgeDataDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Run `fn` over each item with at most `limit` in flight at once. Order of
|
|
65
|
+
* results matches the input order, including `undefined` inputs (the fn is
|
|
66
|
+
* still invoked — the helper does not silently skip holes).
|
|
67
|
+
*
|
|
68
|
+
* Errors propagate via `Promise.all`: the first rejecting worker fails the
|
|
69
|
+
* whole call. Wrap `fn` in your own try/catch if you need partial results.
|
|
70
|
+
*/
|
|
71
|
+
async function mapBounded(items, limit, fn) {
|
|
72
|
+
const results = new Array(items.length);
|
|
73
|
+
let next = 0;
|
|
74
|
+
const worker = async () => {
|
|
75
|
+
while (true) {
|
|
76
|
+
const i = next++;
|
|
77
|
+
if (i >= items.length)
|
|
78
|
+
return;
|
|
79
|
+
results[i] = await fn(items[i], i);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const workers = [];
|
|
83
|
+
for (let w = 0; w < Math.min(limit, items.length); w++)
|
|
84
|
+
workers.push(worker());
|
|
85
|
+
await Promise.all(workers);
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Serialise all read-modify-write sequences over projects.json. Without this,
|
|
90
|
+
* two concurrent POST /projects requests can read the same baseline and race
|
|
91
|
+
* the rename(), losing one write. Single-process / single-tenant only — there
|
|
92
|
+
* is no file lock; we don't need cross-process safety.
|
|
93
|
+
*/
|
|
94
|
+
let projectsLock = Promise.resolve();
|
|
95
|
+
function withProjectsLock(fn) {
|
|
96
|
+
const next = projectsLock.then(fn, fn);
|
|
97
|
+
// Keep the chain alive but don't propagate failures into subsequent waiters.
|
|
98
|
+
projectsLock = next.catch(() => undefined);
|
|
99
|
+
return next;
|
|
100
|
+
}
|
|
101
|
+
export async function readProjects() {
|
|
102
|
+
await ensureConfigDir();
|
|
103
|
+
try {
|
|
104
|
+
const raw = await readFile(PROJECTS_FILE(), "utf8");
|
|
105
|
+
const parsed = JSON.parse(raw);
|
|
106
|
+
if (!Array.isArray(parsed))
|
|
107
|
+
return [];
|
|
108
|
+
return parsed.filter(isProject);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (err.code === "ENOENT")
|
|
112
|
+
return [];
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function isProject(v) {
|
|
117
|
+
if (typeof v !== "object" || v === null)
|
|
118
|
+
return false;
|
|
119
|
+
const r = v;
|
|
120
|
+
return (typeof r.id === "string" &&
|
|
121
|
+
typeof r.name === "string" &&
|
|
122
|
+
typeof r.path === "string" &&
|
|
123
|
+
typeof r.createdAt === "string");
|
|
124
|
+
}
|
|
125
|
+
async function writeProjects(projects) {
|
|
126
|
+
await ensureConfigDir();
|
|
127
|
+
const target = PROJECTS_FILE();
|
|
128
|
+
const tmp = `${target}.${randomUUID()}.tmp`;
|
|
129
|
+
await writeFile(tmp, JSON.stringify(projects, null, 2), "utf8");
|
|
130
|
+
try {
|
|
131
|
+
await rename(tmp, target);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
// See atomicWriteJson in config-manager.ts: same tmp-file leak
|
|
135
|
+
// cleanup. Without this, a rename failure (cross-fs, perms,
|
|
136
|
+
// target locked on Windows) leaves the .tmp orphan.
|
|
137
|
+
await unlink(tmp).catch(() => undefined);
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export async function createProject(name, path) {
|
|
142
|
+
const trimmedName = name.trim();
|
|
143
|
+
if (trimmedName.length === 0) {
|
|
144
|
+
throw new InvalidNameError("project name cannot be empty");
|
|
145
|
+
}
|
|
146
|
+
const lexicalPath = resolve(path);
|
|
147
|
+
// Realpath both sides before the inside-workspace check. The lexical
|
|
148
|
+
// check alone accepts a symlink under WORKSPACE_PATH that points
|
|
149
|
+
// OUTSIDE the realpath bound — e.g. `~/.pi-forge/workspace/external
|
|
150
|
+
// -> /etc` — and registers `/external` (the symlink target) as a
|
|
151
|
+
// legitimate project root. Subsequent file-manager ops would then
|
|
152
|
+
// realpath-bound to the symlink target, NOT to WORKSPACE_PATH.
|
|
153
|
+
// Catch here so a missing target throws NotADirectoryError below
|
|
154
|
+
// rather than the more confusing realpath ENOENT.
|
|
155
|
+
const realPath = await realpath(lexicalPath).catch(() => lexicalPath);
|
|
156
|
+
const realWorkspaceRoot = await realpath(config.workspacePath).catch(() => config.workspacePath);
|
|
157
|
+
if (!isInsideWorkspace(realPath, realWorkspaceRoot)) {
|
|
158
|
+
throw new PathOutsideWorkspaceError(realPath);
|
|
159
|
+
}
|
|
160
|
+
// Persist the canonical (real) path so future operations are
|
|
161
|
+
// consistent and `isInsideWorkspace` in file-manager and elsewhere
|
|
162
|
+
// sees the same shape.
|
|
163
|
+
const resolvedPath = realPath;
|
|
164
|
+
const st = await stat(resolvedPath).catch(() => undefined);
|
|
165
|
+
if (!st?.isDirectory()) {
|
|
166
|
+
throw new NotADirectoryError(resolvedPath);
|
|
167
|
+
}
|
|
168
|
+
return withProjectsLock(async () => {
|
|
169
|
+
const projects = await readProjects();
|
|
170
|
+
if (projects.some((p) => p.path === resolvedPath)) {
|
|
171
|
+
throw new DuplicatePathError(resolvedPath);
|
|
172
|
+
}
|
|
173
|
+
const project = {
|
|
174
|
+
id: randomUUID(),
|
|
175
|
+
name: trimmedName,
|
|
176
|
+
path: resolvedPath,
|
|
177
|
+
createdAt: new Date().toISOString(),
|
|
178
|
+
};
|
|
179
|
+
projects.push(project);
|
|
180
|
+
await writeProjects(projects);
|
|
181
|
+
return project;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
export async function renameProject(id, name) {
|
|
185
|
+
const trimmed = name.trim();
|
|
186
|
+
if (trimmed.length === 0) {
|
|
187
|
+
throw new InvalidNameError("project name cannot be empty");
|
|
188
|
+
}
|
|
189
|
+
return withProjectsLock(async () => {
|
|
190
|
+
const projects = await readProjects();
|
|
191
|
+
const idx = projects.findIndex((p) => p.id === id);
|
|
192
|
+
if (idx === -1)
|
|
193
|
+
throw new ProjectNotFoundError(id);
|
|
194
|
+
const existing = projects[idx];
|
|
195
|
+
if (existing === undefined)
|
|
196
|
+
throw new ProjectNotFoundError(id);
|
|
197
|
+
const updated = { ...existing, name: trimmed };
|
|
198
|
+
projects[idx] = updated;
|
|
199
|
+
await writeProjects(projects);
|
|
200
|
+
return updated;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const noopWarn = () => undefined;
|
|
204
|
+
export async function deleteProject(id, opts = {}) {
|
|
205
|
+
const warn = opts.logWarn ?? noopWarn;
|
|
206
|
+
let cascaded = false;
|
|
207
|
+
await withProjectsLock(async () => {
|
|
208
|
+
const projects = await readProjects();
|
|
209
|
+
const next = projects.filter((p) => p.id !== id);
|
|
210
|
+
if (next.length === projects.length)
|
|
211
|
+
throw new ProjectNotFoundError(id);
|
|
212
|
+
await writeProjects(next);
|
|
213
|
+
});
|
|
214
|
+
// Drop any per-project skill overrides for the deleted project.
|
|
215
|
+
// Best-effort — a failure here doesn't undo the deletion (project
|
|
216
|
+
// is already gone). The orphan would just be cosmetic in the UI's
|
|
217
|
+
// cascade view; it's harmless to read but pointless to keep.
|
|
218
|
+
await clearProjectSkillOverrides(id).catch((err) => {
|
|
219
|
+
warn({ err, id }, "skill-overrides cleanup failed");
|
|
220
|
+
});
|
|
221
|
+
if (opts.cascadeSessionDir === true) {
|
|
222
|
+
// Wipe the project's session directory. Best-effort — a missing
|
|
223
|
+
// dir (no sessions ever recorded) is not an error.
|
|
224
|
+
//
|
|
225
|
+
// SAFETY: validate the id is UUID-shaped (the only shape
|
|
226
|
+
// `createProject()` ever produces) BEFORE building the path.
|
|
227
|
+
// `rm({ recursive: true, force: true })` is destructive enough
|
|
228
|
+
// that any path-traversal in `id` would be catastrophic — a
|
|
229
|
+
// hypothetical `id === ".."` would resolve to the parent of
|
|
230
|
+
// `${SESSION_DIR}` and wipe it. Today the only id source is
|
|
231
|
+
// `randomUUID()`, but the validator codifies that invariant
|
|
232
|
+
// against any future code path that imports/restores ids from
|
|
233
|
+
// the wire or a manually-edited projects.json.
|
|
234
|
+
if (!UUID_RE.test(id)) {
|
|
235
|
+
// Should be unreachable — the project record we just deleted
|
|
236
|
+
// had this id, so it passed creation-time validation. Log and
|
|
237
|
+
// skip the cascade rather than rm something dangerous.
|
|
238
|
+
warn({ id }, "refusing cascade rm for non-UUID id");
|
|
239
|
+
return { cascaded };
|
|
240
|
+
}
|
|
241
|
+
const dir = join(config.sessionDir, id);
|
|
242
|
+
try {
|
|
243
|
+
await rm(dir, { recursive: true, force: true });
|
|
244
|
+
cascaded = true;
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
// Don't fail the delete itself if cascade cleanup fails — the
|
|
248
|
+
// project record is gone, the session files are just orphaned
|
|
249
|
+
// (the same state that exists when cascade isn't requested).
|
|
250
|
+
// But DO log so a permissions issue isn't silently invisible.
|
|
251
|
+
warn({ err, dir }, "cascade rm failed");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return { cascaded };
|
|
255
|
+
}
|
|
256
|
+
export async function getProject(id) {
|
|
257
|
+
const projects = await readProjects();
|
|
258
|
+
return projects.find((p) => p.id === id);
|
|
259
|
+
}
|
|
260
|
+
export async function browseDirectory(requested) {
|
|
261
|
+
const target = resolve(requested ?? config.workspacePath);
|
|
262
|
+
if (!isInsideWorkspace(target)) {
|
|
263
|
+
throw new PathOutsideWorkspaceError(target);
|
|
264
|
+
}
|
|
265
|
+
const st = await stat(target).catch(() => undefined);
|
|
266
|
+
if (!st?.isDirectory()) {
|
|
267
|
+
throw new NotADirectoryError(target);
|
|
268
|
+
}
|
|
269
|
+
const dirents = await readdir(target, { withFileTypes: true });
|
|
270
|
+
const dirEntries = dirents.filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
271
|
+
// Stat .git children with a bounded concurrency cap — unbounded Promise.all
|
|
272
|
+
// could exhaust the libuv FD pool on a node_modules-shaped tree (closes the
|
|
273
|
+
// Phase-10 deferred item). 16 concurrent stats is plenty for any realistic
|
|
274
|
+
// workspace and well below the default ulimit on macOS/Linux.
|
|
275
|
+
const entries = await mapBounded(dirEntries, 16, async (ent) => {
|
|
276
|
+
const childPath = join(target, ent.name);
|
|
277
|
+
const gitStat = await stat(join(childPath, ".git")).catch(() => undefined);
|
|
278
|
+
return { name: ent.name, path: childPath, isGitRepo: gitStat !== undefined };
|
|
279
|
+
});
|
|
280
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
281
|
+
const resolvedRoot = resolve(config.workspacePath);
|
|
282
|
+
const parentPath = target === resolvedRoot ? undefined : dirname(target);
|
|
283
|
+
return { path: target, parentPath, entries };
|
|
284
|
+
}
|
|
285
|
+
export async function createDirectory(parentPath, name) {
|
|
286
|
+
const trimmed = name.trim();
|
|
287
|
+
if (trimmed.length === 0 || trimmed.includes("/") || trimmed.includes("\\") || trimmed === "..") {
|
|
288
|
+
throw new InvalidDirectoryNameError();
|
|
289
|
+
}
|
|
290
|
+
const parent = resolve(parentPath);
|
|
291
|
+
if (!isInsideWorkspace(parent)) {
|
|
292
|
+
throw new PathOutsideWorkspaceError(parent);
|
|
293
|
+
}
|
|
294
|
+
const target = join(parent, trimmed);
|
|
295
|
+
if (!isInsideWorkspace(target)) {
|
|
296
|
+
throw new PathOutsideWorkspaceError(target);
|
|
297
|
+
}
|
|
298
|
+
await mkdir(target, { recursive: false });
|
|
299
|
+
return target;
|
|
300
|
+
}
|
|
301
|
+
//# sourceMappingURL=project-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-manager.js","sourceRoot":"","sources":["../src/project-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,MAAM,EACN,EAAE,EACF,IAAI,EACJ,MAAM,EACN,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,qBAAqB,IAAI,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAE3F;;;;;;GAMG;AACH,MAAM,OAAO,GAAG,iEAAiE,CAAC;AASlF,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAClD,YAAY,IAAY;QACtB,KAAK,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,IAAY;QACtB,KAAK,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,EAAU;QACpB,KAAK,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAO,GAAG,cAAc;QAClC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAClD,YAAY,OAAO,GAAG,wBAAwB;QAC5C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,IAAY;QACtB,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,MAAM,aAAa,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;AAE/E,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,OAAe,MAAM,CAAC,aAAa;IACnF,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,cAAc,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IACjD,MAAM,GAAG,GAAG,QAAQ,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IACnD,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;AAChF,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,MAAM,KAAK,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,UAAU,CACvB,KAAmB,EACnB,KAAa,EACb,EAA0C;IAE1C,MAAM,OAAO,GAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,MAAM,GAAG,KAAK,IAAmB,EAAE;QACvC,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAO;YAC9B,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,CAAM,EAAE,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC,CAAC;IACF,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/E,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,IAAI,YAAY,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;AACvD,SAAS,gBAAgB,CAAI,EAAoB;IAC/C,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACvC,6EAA6E;IAC7E,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,eAAe,EAAE,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,EAAE,MAAM,CAAC,CAAC;QACpD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAChE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,CAAU;IAC3B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,CAAC,GAAG,CAA4B,CAAC;IACvC,OAAO,CACL,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ;QACxB,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAC1B,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAC1B,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,CAChC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,QAAmB;IAC9C,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,UAAU,EAAE,MAAM,CAAC;IAC5C,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,+DAA+D;QAC/D,4DAA4D;QAC5D,oDAAoD;QACpD,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,IAAY;IAC5D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,gBAAgB,CAAC,8BAA8B,CAAC,CAAC;IAC7D,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,qEAAqE;IACrE,iEAAiE;IACjE,oEAAoE;IACpE,iEAAiE;IACjE,kEAAkE;IAClE,+DAA+D;IAC/D,iEAAiE;IACjE,kDAAkD;IAClD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC;IACtE,MAAM,iBAAiB,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACjG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,iBAAiB,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,yBAAyB,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IACD,6DAA6D;IAC7D,mEAAmE;IACnE,uBAAuB;IACvB,MAAM,YAAY,GAAG,QAAQ,CAAC;IAC9B,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC3D,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,CAAC;QACvB,MAAM,IAAI,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,gBAAgB,CAAC,KAAK,IAAI,EAAE;QACjC,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;QACtC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,kBAAkB,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,OAAO,GAAY;YACvB,EAAE,EAAE,UAAU,EAAE;YAChB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,YAAY;YAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QACF,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC9B,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAU,EAAE,IAAY;IAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,gBAAgB,CAAC,8BAA8B,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,gBAAgB,CAAC,KAAK,IAAI,EAAE;QACjC,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,QAAQ,KAAK,SAAS;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,CAAC,CAAC;QAC/D,MAAM,OAAO,GAAY,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QACxD,QAAQ,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;QACxB,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC9B,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC;AASD,MAAM,QAAQ,GAAW,GAAG,EAAE,CAAC,SAAS,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,EAAU,EACV,OAA0D,EAAE;IAE5D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC;IACtC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,gBAAgB,CAAC,KAAK,IAAI,EAAE;QAChC,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACjD,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;YAAE,MAAM,IAAI,oBAAoB,CAAC,EAAE,CAAC,CAAC;QACxE,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IACH,gEAAgE;IAChE,kEAAkE;IAClE,kEAAkE;IAClE,6DAA6D;IAC7D,MAAM,0BAA0B,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QAC1D,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,gCAAgC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IACH,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;QACpC,gEAAgE;QAChE,mDAAmD;QACnD,EAAE;QACF,yDAAyD;QACzD,6DAA6D;QAC7D,+DAA+D;QAC/D,4DAA4D;QAC5D,4DAA4D;QAC5D,4DAA4D;QAC5D,4DAA4D;QAC5D,8DAA8D;QAC9D,+CAA+C;QAC/C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACtB,6DAA6D;YAC7D,8DAA8D;YAC9D,uDAAuD;YACvD,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,qCAAqC,CAAC,CAAC;YACpD,OAAO,EAAE,QAAQ,EAAE,CAAC;QACtB,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChD,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,8DAA8D;YAC9D,8DAA8D;YAC9D,6DAA6D;YAC7D,8DAA8D;YAC9D,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,mBAAmB,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,EAAU;IACzC,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC3C,CAAC;AAeD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,SAA6B;IACjE,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,aAAa,CAAC,CAAC;IAC1D,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACrD,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,CAAC;QACvB,MAAM,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACrF,4EAA4E;IAC5E,4EAA4E;IAC5E,2EAA2E;IAC3E,8DAA8D;IAC9D,MAAM,OAAO,GAAkB,MAAM,UAAU,CAAC,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5E,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC3E,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,KAAK,SAAS,EAAE,CAAC;IAC/E,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACzE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAAkB,EAAE,IAAY;IACpE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QAChG,MAAM,IAAI,yBAAyB,EAAE,CAAC;IACxC,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACnC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
|