taskflow-core 0.1.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/dist/agents/analyst.md +30 -0
- package/dist/agents/critic.md +31 -0
- package/dist/agents/doc-writer.md +43 -0
- package/dist/agents/executor-code.md +36 -0
- package/dist/agents/executor-fast.md +26 -0
- package/dist/agents/executor-ui.md +35 -0
- package/dist/agents/executor.md +29 -0
- package/dist/agents/final-arbiter.md +29 -0
- package/dist/agents/plan-arbiter.md +35 -0
- package/dist/agents/planner.md +30 -0
- package/dist/agents/recover.md +28 -0
- package/dist/agents/reviewer.md +37 -0
- package/dist/agents/risk-reviewer.md +37 -0
- package/dist/agents/scout.md +51 -0
- package/dist/agents/security-reviewer.md +39 -0
- package/dist/agents/test-engineer.md +31 -0
- package/dist/agents/verifier.md +29 -0
- package/dist/agents/visual-explorer.md +32 -0
- package/dist/agents.d.ts +55 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +265 -0
- package/dist/agents.js.map +1 -0
- package/dist/cache.d.ts +57 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +256 -0
- package/dist/cache.js.map +1 -0
- package/dist/compile.d.ts +37 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +324 -0
- package/dist/compile.js.map +1 -0
- package/dist/context-store.d.ts +142 -0
- package/dist/context-store.d.ts.map +1 -0
- package/dist/context-store.js +383 -0
- package/dist/context-store.js.map +1 -0
- package/dist/detached-runner.d.ts +12 -0
- package/dist/detached-runner.d.ts.map +1 -0
- package/dist/detached-runner.js +68 -0
- package/dist/detached-runner.js.map +1 -0
- package/dist/flowir/hash.d.ts +51 -0
- package/dist/flowir/hash.d.ts.map +1 -0
- package/dist/flowir/hash.js +90 -0
- package/dist/flowir/hash.js.map +1 -0
- package/dist/flowir/index.d.ts +43 -0
- package/dist/flowir/index.d.ts.map +1 -0
- package/dist/flowir/index.js +62 -0
- package/dist/flowir/index.js.map +1 -0
- package/dist/flowir/meta.d.ts +103 -0
- package/dist/flowir/meta.d.ts.map +1 -0
- package/dist/flowir/meta.js +19 -0
- package/dist/flowir/meta.js.map +1 -0
- package/dist/flowir/phasefp.d.ts +55 -0
- package/dist/flowir/phasefp.d.ts.map +1 -0
- package/dist/flowir/phasefp.js +123 -0
- package/dist/flowir/phasefp.js.map +1 -0
- package/dist/flowir/translate.d.ts +37 -0
- package/dist/flowir/translate.d.ts.map +1 -0
- package/dist/flowir/translate.js +137 -0
- package/dist/flowir/translate.js.map +1 -0
- package/dist/frontmatter.d.ts +21 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +107 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/host/runner-types.d.ts +98 -0
- package/dist/host/runner-types.d.ts.map +1 -0
- package/dist/host/runner-types.js +21 -0
- package/dist/host/runner-types.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolate.d.ts +52 -0
- package/dist/interpolate.d.ts.map +1 -0
- package/dist/interpolate.js +418 -0
- package/dist/interpolate.js.map +1 -0
- package/dist/paths.d.ts +23 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +43 -0
- package/dist/paths.js.map +1 -0
- package/dist/runner-core.d.ts +49 -0
- package/dist/runner-core.d.ts.map +1 -0
- package/dist/runner-core.js +208 -0
- package/dist/runner-core.js.map +1 -0
- package/dist/runtime.d.ts +234 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +2284 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema.d.ts +257 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +795 -0
- package/dist/schema.js.map +1 -0
- package/dist/stale.d.ts +72 -0
- package/dist/stale.d.ts.map +1 -0
- package/dist/stale.js +179 -0
- package/dist/stale.js.map +1 -0
- package/dist/store.d.ts +237 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +761 -0
- package/dist/store.js.map +1 -0
- package/dist/typebox-helpers.d.ts +15 -0
- package/dist/typebox-helpers.d.ts.map +1 -0
- package/dist/typebox-helpers.js +19 -0
- package/dist/typebox-helpers.js.map +1 -0
- package/dist/usage.d.ts +21 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +33 -0
- package/dist/usage.js.map +1 -0
- package/dist/verify.d.ts +38 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +324 -0
- package/dist/verify.js.map +1 -0
- package/dist/workspace.d.ts +64 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +178 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Context Tree — the file-based blackboard + supervision-tree store.
|
|
3
|
+
*
|
|
4
|
+
* This module is the IPC substrate that lets isolated subagent processes share
|
|
5
|
+
* context with each other (a horizontal blackboard) and report results upward
|
|
6
|
+
* so a parent can react (a vertical supervision tree). It deliberately reuses
|
|
7
|
+
* the SAME atomic-write + file-lock primitives as the run store (`store.ts`),
|
|
8
|
+
* so it inherits the project's "all file ops are atomic" invariant for free.
|
|
9
|
+
*
|
|
10
|
+
* On-disk layout, rooted at PI_TASKFLOW_CTX_DIR (one directory per run):
|
|
11
|
+
*
|
|
12
|
+
* <ctxDir>/
|
|
13
|
+
* ├── tree.json the node tree (who spawned whom + status)
|
|
14
|
+
* ├── tree.json.lock lock guarding tree.json RMW cycles
|
|
15
|
+
* ├── findings/
|
|
16
|
+
* │ ├── <nodeId>.json findings written by one node (last-write-wins per key)
|
|
17
|
+
* │ └── <nodeId>.json.lock
|
|
18
|
+
* ├── reports/
|
|
19
|
+
* │ └── <nodeId>.json a node's upward report ({summary, structured?})
|
|
20
|
+
* └── pending/
|
|
21
|
+
* └── <nodeId>-<seq>.json a ctx_spawn intent the runtime will pick up
|
|
22
|
+
*
|
|
23
|
+
* Why per-node findings files (not one shared findings.json): sibling subagents
|
|
24
|
+
* run concurrently. Giving each node its OWN file means concurrent writers never
|
|
25
|
+
* contend on the same lock — a node only locks its own file. A reader unions the
|
|
26
|
+
* relevant nodes' files (its ancestors + completed siblings). This is the same
|
|
27
|
+
* "shard by writer" trick the run index uses to avoid a global write bottleneck.
|
|
28
|
+
*/
|
|
29
|
+
import * as crypto from "node:crypto";
|
|
30
|
+
import * as fs from "node:fs";
|
|
31
|
+
import * as path from "node:path";
|
|
32
|
+
import { validateRunId, withLock, writeFileAtomic } from "./store.js";
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Guards (size + key charset). A subagent is untrusted input from the LLM's
|
|
35
|
+
// point of view; cap what it can write so a runaway tool call can't fill the
|
|
36
|
+
// disk or smuggle a path-traversal key.
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/** Max bytes for a single findings value (after JSON.stringify). */
|
|
39
|
+
export const MAX_VALUE_BYTES = 256 * 1024; // 256 KB
|
|
40
|
+
/** Max bytes for a single report summary string. */
|
|
41
|
+
export const MAX_REPORT_BYTES = 256 * 1024;
|
|
42
|
+
/** Max bytes for a report's structured payload (after JSON.stringify). */
|
|
43
|
+
export const MAX_STRUCTURED_BYTES = 256 * 1024;
|
|
44
|
+
/** Max bytes for a single ctx_spawn task prompt. */
|
|
45
|
+
export const MAX_TASK_BYTES = 64 * 1024;
|
|
46
|
+
/** Max number of keys one node may write. */
|
|
47
|
+
export const MAX_KEYS_PER_NODE = 256;
|
|
48
|
+
/** Max assignments a single ctx_spawn call may queue. */
|
|
49
|
+
export const MAX_SPAWN_ASSIGNMENTS = 16;
|
|
50
|
+
/** Max bytes for a single ctx_spawn `subflow` payload (after JSON.stringify). */
|
|
51
|
+
export const MAX_SUBFLOW_BYTES = 256 * 1024; // 256 KB
|
|
52
|
+
/** A findings/report key must be a short, traversal-safe token. */
|
|
53
|
+
const KEY_RE = /^[A-Za-z0-9._-]{1,128}$/;
|
|
54
|
+
export function isValidKey(key) {
|
|
55
|
+
return typeof key === "string" && KEY_RE.test(key) && !key.includes("..");
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Path helpers
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
function treePath(ctxDir) {
|
|
61
|
+
return path.join(ctxDir, "tree.json");
|
|
62
|
+
}
|
|
63
|
+
function treeLockPath(ctxDir) {
|
|
64
|
+
return path.join(ctxDir, "tree.json.lock");
|
|
65
|
+
}
|
|
66
|
+
function findingsDir(ctxDir) {
|
|
67
|
+
return path.join(ctxDir, "findings");
|
|
68
|
+
}
|
|
69
|
+
function findingsPath(ctxDir, nodeId) {
|
|
70
|
+
return path.join(findingsDir(ctxDir), `${nodeId}.json`);
|
|
71
|
+
}
|
|
72
|
+
function findingsLockPath(ctxDir, nodeId) {
|
|
73
|
+
return path.join(findingsDir(ctxDir), `${nodeId}.json.lock`);
|
|
74
|
+
}
|
|
75
|
+
function reportsDir(ctxDir) {
|
|
76
|
+
return path.join(ctxDir, "reports");
|
|
77
|
+
}
|
|
78
|
+
function reportPath(ctxDir, nodeId) {
|
|
79
|
+
return path.join(reportsDir(ctxDir), `${nodeId}.json`);
|
|
80
|
+
}
|
|
81
|
+
function pendingDir(ctxDir) {
|
|
82
|
+
return path.join(ctxDir, "pending");
|
|
83
|
+
}
|
|
84
|
+
/** Build the per-run ctx directory path under a runs root. */
|
|
85
|
+
export function ctxDirFor(runsRoot, runId) {
|
|
86
|
+
if (!validateRunId(runId))
|
|
87
|
+
throw new Error(`Unsafe runId for ctx dir: ${runId}`);
|
|
88
|
+
return path.join(runsRoot, "ctx", runId);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Ensure the ctx directory tree exists. Idempotent; safe to call repeatedly.
|
|
92
|
+
* Returns the same ctxDir for chaining.
|
|
93
|
+
*/
|
|
94
|
+
export function initCtxDir(ctxDir) {
|
|
95
|
+
fs.mkdirSync(ctxDir, { recursive: true });
|
|
96
|
+
fs.mkdirSync(findingsDir(ctxDir), { recursive: true });
|
|
97
|
+
fs.mkdirSync(reportsDir(ctxDir), { recursive: true });
|
|
98
|
+
fs.mkdirSync(pendingDir(ctxDir), { recursive: true });
|
|
99
|
+
return ctxDir;
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Tree
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
export function readTree(ctxDir) {
|
|
105
|
+
try {
|
|
106
|
+
const raw = fs.readFileSync(treePath(ctxDir), "utf-8");
|
|
107
|
+
const parsed = JSON.parse(raw);
|
|
108
|
+
if (parsed && Array.isArray(parsed.nodes))
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* missing/corrupt → empty tree */
|
|
113
|
+
}
|
|
114
|
+
return { nodes: [] };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Register (or update) a node in the tree. IDEMPOTENT — upserts by nodeId so a
|
|
118
|
+
* resume that re-runs a phase does not duplicate tree entries (which would
|
|
119
|
+
* double-count ancestor findings). This is the C3 resume-safety fix.
|
|
120
|
+
*/
|
|
121
|
+
export function registerNode(ctxDir, nodeId, phaseId, parentNodeId, status = "running") {
|
|
122
|
+
if (!validateRunId(nodeId))
|
|
123
|
+
throw new Error(`Unsafe nodeId: ${nodeId}`);
|
|
124
|
+
withLock(treeLockPath(ctxDir), () => {
|
|
125
|
+
const tree = readTree(ctxDir);
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
const idx = tree.nodes.findIndex((n) => n.nodeId === nodeId);
|
|
128
|
+
if (idx >= 0) {
|
|
129
|
+
const existing = tree.nodes[idx];
|
|
130
|
+
tree.nodes[idx] = {
|
|
131
|
+
...existing,
|
|
132
|
+
phaseId,
|
|
133
|
+
parentNodeId,
|
|
134
|
+
status,
|
|
135
|
+
updatedAt: now,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
tree.nodes.push({ nodeId, phaseId, parentNodeId, status, createdAt: now, updatedAt: now });
|
|
140
|
+
}
|
|
141
|
+
writeFileAtomic(treePath(ctxDir), JSON.stringify(tree, null, 2));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export function setNodeStatus(ctxDir, nodeId, status) {
|
|
145
|
+
withLock(treeLockPath(ctxDir), () => {
|
|
146
|
+
const tree = readTree(ctxDir);
|
|
147
|
+
const node = tree.nodes.find((n) => n.nodeId === nodeId);
|
|
148
|
+
if (!node)
|
|
149
|
+
return;
|
|
150
|
+
node.status = status;
|
|
151
|
+
node.updatedAt = Date.now();
|
|
152
|
+
writeFileAtomic(treePath(ctxDir), JSON.stringify(tree, null, 2));
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** Compute a node's depth (root = 0) by walking the parent chain. */
|
|
156
|
+
export function nodeDepth(tree, nodeId) {
|
|
157
|
+
let depth = 0;
|
|
158
|
+
let current = tree.nodes.find((n) => n.nodeId === nodeId);
|
|
159
|
+
const seen = new Set();
|
|
160
|
+
while (current?.parentNodeId && !seen.has(current.nodeId)) {
|
|
161
|
+
seen.add(current.nodeId);
|
|
162
|
+
depth++;
|
|
163
|
+
const parentId = current.parentNodeId;
|
|
164
|
+
current = tree.nodes.find((n) => n.nodeId === parentId);
|
|
165
|
+
}
|
|
166
|
+
return depth;
|
|
167
|
+
}
|
|
168
|
+
/** Return the ancestor chain nodeIds for a node (excluding itself), in nearest-first order (parent, grandparent, …). */
|
|
169
|
+
export function ancestorIds(tree, nodeId) {
|
|
170
|
+
const out = [];
|
|
171
|
+
const seen = new Set([nodeId]);
|
|
172
|
+
let current = tree.nodes.find((n) => n.nodeId === nodeId);
|
|
173
|
+
while (current?.parentNodeId && !seen.has(current.parentNodeId)) {
|
|
174
|
+
out.push(current.parentNodeId);
|
|
175
|
+
seen.add(current.parentNodeId);
|
|
176
|
+
const parentId = current.parentNodeId;
|
|
177
|
+
current = tree.nodes.find((n) => n.nodeId === parentId);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Findings (the horizontal blackboard)
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
export function readNodeFindings(ctxDir, nodeId) {
|
|
185
|
+
if (!validateRunId(nodeId))
|
|
186
|
+
return {}; // defense-in-depth: never build a path from an unsafe id
|
|
187
|
+
try {
|
|
188
|
+
const raw = fs.readFileSync(findingsPath(ctxDir, nodeId), "utf-8");
|
|
189
|
+
const parsed = JSON.parse(raw);
|
|
190
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
191
|
+
return parsed;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* missing/corrupt → empty */
|
|
195
|
+
}
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Write one finding (last-write-wins per key) into THIS node's findings file.
|
|
200
|
+
* Only locks the node's own file → concurrent siblings never contend.
|
|
201
|
+
* Throws on bad key / oversized value / too many keys (caller surfaces as tool error).
|
|
202
|
+
*/
|
|
203
|
+
export function writeFinding(ctxDir, nodeId, key, value) {
|
|
204
|
+
if (!validateRunId(nodeId))
|
|
205
|
+
throw new Error(`Unsafe nodeId: ${nodeId}`);
|
|
206
|
+
if (!isValidKey(key))
|
|
207
|
+
throw new Error(`Invalid finding key '${key}' (allowed: [A-Za-z0-9._-], <=128 chars, no '..').`);
|
|
208
|
+
const serialized = JSON.stringify(value ?? null);
|
|
209
|
+
if (Buffer.byteLength(serialized, "utf-8") > MAX_VALUE_BYTES) {
|
|
210
|
+
throw new Error(`Finding '${key}' exceeds ${MAX_VALUE_BYTES} bytes.`);
|
|
211
|
+
}
|
|
212
|
+
fs.mkdirSync(findingsDir(ctxDir), { recursive: true });
|
|
213
|
+
withLock(findingsLockPath(ctxDir, nodeId), () => {
|
|
214
|
+
const findings = readNodeFindings(ctxDir, nodeId);
|
|
215
|
+
if (!(key in findings) && Object.keys(findings).length >= MAX_KEYS_PER_NODE) {
|
|
216
|
+
throw new Error(`Node '${nodeId}' exceeds ${MAX_KEYS_PER_NODE} findings keys.`);
|
|
217
|
+
}
|
|
218
|
+
findings[key] = JSON.parse(serialized);
|
|
219
|
+
writeFileAtomic(findingsPath(ctxDir, nodeId), JSON.stringify(findings, null, 2));
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Read the findings visible to a node: its OWN findings unioned with its
|
|
224
|
+
* ancestors' and all sibling/other nodes' findings that are already `done`.
|
|
225
|
+
* "done" visibility prevents reading a half-written blackboard from a sibling
|
|
226
|
+
* that is still running (eventual consistency: you see a sibling's findings
|
|
227
|
+
* once it has reported completion). The node's own findings are always visible.
|
|
228
|
+
*
|
|
229
|
+
* On key conflicts: nearer scope wins (own > ancestors > completed others),
|
|
230
|
+
* matching intuition that a node trusts its own/closer notes most.
|
|
231
|
+
*
|
|
232
|
+
* @param key optional — return only that key's value (or undefined).
|
|
233
|
+
*/
|
|
234
|
+
export function readVisibleFindings(ctxDir, nodeId, key) {
|
|
235
|
+
if (!validateRunId(nodeId))
|
|
236
|
+
return key !== undefined ? undefined : {};
|
|
237
|
+
const tree = readTree(ctxDir);
|
|
238
|
+
const ancestors = new Set(ancestorIds(tree, nodeId));
|
|
239
|
+
// Build layered maps; merge order = lowest priority first.
|
|
240
|
+
const completedOthers = {};
|
|
241
|
+
const ancestorFindings = {};
|
|
242
|
+
for (const n of tree.nodes) {
|
|
243
|
+
if (n.nodeId === nodeId)
|
|
244
|
+
continue;
|
|
245
|
+
const f = readNodeFindings(ctxDir, n.nodeId);
|
|
246
|
+
if (ancestors.has(n.nodeId)) {
|
|
247
|
+
Object.assign(ancestorFindings, f);
|
|
248
|
+
}
|
|
249
|
+
else if (n.status === "done") {
|
|
250
|
+
Object.assign(completedOthers, f);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const own = readNodeFindings(ctxDir, nodeId);
|
|
254
|
+
const merged = { ...completedOthers, ...ancestorFindings, ...own };
|
|
255
|
+
if (key !== undefined) {
|
|
256
|
+
if (!isValidKey(key))
|
|
257
|
+
return undefined;
|
|
258
|
+
return merged[key];
|
|
259
|
+
}
|
|
260
|
+
return merged;
|
|
261
|
+
}
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// Reports (the vertical upward channel)
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
export function writeReport(ctxDir, nodeId, summary, structured) {
|
|
266
|
+
if (!validateRunId(nodeId))
|
|
267
|
+
throw new Error(`Unsafe nodeId: ${nodeId}`);
|
|
268
|
+
if (Buffer.byteLength(String(summary ?? ""), "utf-8") > MAX_REPORT_BYTES) {
|
|
269
|
+
throw new Error(`Report summary exceeds ${MAX_REPORT_BYTES} bytes.`);
|
|
270
|
+
}
|
|
271
|
+
if (structured !== undefined && Buffer.byteLength(JSON.stringify(structured ?? null), "utf-8") > MAX_STRUCTURED_BYTES) {
|
|
272
|
+
throw new Error(`Report 'structured' payload exceeds ${MAX_STRUCTURED_BYTES} bytes.`);
|
|
273
|
+
}
|
|
274
|
+
fs.mkdirSync(reportsDir(ctxDir), { recursive: true });
|
|
275
|
+
// No lock: each node owns its own report file and is a single process, so the
|
|
276
|
+
// pure-overwrite writeFileAtomic is race-free here (unlike findings, which do
|
|
277
|
+
// read-modify-write and therefore lock).
|
|
278
|
+
const report = { nodeId, summary: String(summary ?? ""), structured, at: Date.now() };
|
|
279
|
+
writeFileAtomic(reportPath(ctxDir, nodeId), JSON.stringify(report, null, 2));
|
|
280
|
+
}
|
|
281
|
+
export function readReport(ctxDir, nodeId) {
|
|
282
|
+
if (!validateRunId(nodeId))
|
|
283
|
+
return undefined;
|
|
284
|
+
try {
|
|
285
|
+
const raw = fs.readFileSync(reportPath(ctxDir, nodeId), "utf-8");
|
|
286
|
+
const parsed = JSON.parse(raw);
|
|
287
|
+
if (parsed && typeof parsed.summary === "string")
|
|
288
|
+
return parsed;
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
/* none */
|
|
292
|
+
}
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
// Pending spawns (ctx_spawn intents → runtime supervision loop)
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
/** Queue a ctx_spawn intent. Each call writes a unique file the runtime picks up. */
|
|
299
|
+
export function queueSpawn(ctxDir, parentNodeId, assignments) {
|
|
300
|
+
if (!validateRunId(parentNodeId))
|
|
301
|
+
throw new Error(`Unsafe nodeId: ${parentNodeId}`);
|
|
302
|
+
if (!Array.isArray(assignments) || assignments.length === 0) {
|
|
303
|
+
throw new Error("ctx_spawn requires a non-empty assignments array.");
|
|
304
|
+
}
|
|
305
|
+
if (assignments.length > MAX_SPAWN_ASSIGNMENTS) {
|
|
306
|
+
throw new Error(`ctx_spawn limited to ${MAX_SPAWN_ASSIGNMENTS} assignments per call.`);
|
|
307
|
+
}
|
|
308
|
+
const clean = assignments.map((a) => {
|
|
309
|
+
if (!a || typeof a !== "object") {
|
|
310
|
+
throw new Error("Each ctx_spawn assignment must be an object with 'task' or 'subflow'.");
|
|
311
|
+
}
|
|
312
|
+
const hasTask = typeof a.task === "string" && a.task.trim().length > 0;
|
|
313
|
+
const hasSubflow = a.subflow !== undefined && a.subflow !== null;
|
|
314
|
+
// XOR: exactly one of task / subflow. Check subflow first so a pure-subflow
|
|
315
|
+
// assignment (no `task`) is never rejected by the task-required branch.
|
|
316
|
+
if (hasSubflow) {
|
|
317
|
+
if (hasTask) {
|
|
318
|
+
throw new Error("A ctx_spawn assignment has both 'task' and 'subflow' — provide exactly one.");
|
|
319
|
+
}
|
|
320
|
+
const bytes = Buffer.byteLength(JSON.stringify(a.subflow), "utf-8");
|
|
321
|
+
if (bytes > MAX_SUBFLOW_BYTES) {
|
|
322
|
+
throw new Error(`ctx_spawn subflow exceeds ${MAX_SUBFLOW_BYTES} bytes.`);
|
|
323
|
+
}
|
|
324
|
+
return { subflow: a.subflow, defaultAgent: typeof a.defaultAgent === "string" ? a.defaultAgent : undefined };
|
|
325
|
+
}
|
|
326
|
+
if (hasTask) {
|
|
327
|
+
if (Buffer.byteLength(a.task, "utf-8") > MAX_TASK_BYTES) {
|
|
328
|
+
throw new Error(`ctx_spawn task exceeds ${MAX_TASK_BYTES} bytes.`);
|
|
329
|
+
}
|
|
330
|
+
return { task: a.task, agent: typeof a.agent === "string" ? a.agent : undefined };
|
|
331
|
+
}
|
|
332
|
+
throw new Error("Each ctx_spawn assignment needs exactly one of 'task' (non-empty string) or 'subflow' (object).");
|
|
333
|
+
});
|
|
334
|
+
fs.mkdirSync(pendingDir(ctxDir), { recursive: true });
|
|
335
|
+
// Unique per call: time + crypto-random so two concurrent queueSpawn calls
|
|
336
|
+
// from the same parent in the same ms cannot collide (and overwrite).
|
|
337
|
+
const seq = `${Date.now().toString(36)}-${crypto.randomBytes(6).toString("hex")}`;
|
|
338
|
+
const payload = { parentNodeId, assignments: clean, at: Date.now() };
|
|
339
|
+
writeFileAtomic(path.join(pendingDir(ctxDir), `${parentNodeId}-${seq}.json`), JSON.stringify(payload, null, 2));
|
|
340
|
+
return clean.length;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Drain (read + delete) all pending spawn intents queued by a parent node.
|
|
344
|
+
* Returns the flattened assignment list. Used by the runtime supervision loop
|
|
345
|
+
* after a node's subagent finishes.
|
|
346
|
+
*
|
|
347
|
+
* INVARIANT: only call this AFTER the parent subagent process has exited. The
|
|
348
|
+
* read-then-unlink is not directory-locked, so a concurrent queueSpawn from a
|
|
349
|
+
* still-running parent could be missed. The runtime drains post-exit, so no
|
|
350
|
+
* concurrent writer exists.
|
|
351
|
+
*/
|
|
352
|
+
export function drainPendingSpawns(ctxDir, parentNodeId) {
|
|
353
|
+
if (!validateRunId(parentNodeId))
|
|
354
|
+
return [];
|
|
355
|
+
const dir = pendingDir(ctxDir);
|
|
356
|
+
let files;
|
|
357
|
+
try {
|
|
358
|
+
files = fs.readdirSync(dir).filter((f) => f.startsWith(`${parentNodeId}-`) && f.endsWith(".json"));
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
const out = [];
|
|
364
|
+
for (const f of files.sort()) {
|
|
365
|
+
const full = path.join(dir, f);
|
|
366
|
+
try {
|
|
367
|
+
const parsed = JSON.parse(fs.readFileSync(full, "utf-8"));
|
|
368
|
+
if (parsed && Array.isArray(parsed.assignments))
|
|
369
|
+
out.push(...parsed.assignments);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
/* skip corrupt */
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
fs.unlinkSync(full);
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
/* already gone */
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
//# sourceMappingURL=context-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-store.js","sourceRoot":"","sources":["../src/context-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detached runner — spawned as a child process for background (detached) runs.
|
|
3
|
+
*
|
|
4
|
+
* Reads a context JSON file (path passed as argv[2]), calls executeTaskflow,
|
|
5
|
+
* and persists the terminal state. Top-level try/catch writes status "failed"
|
|
6
|
+
* on crash. Approval phases auto-reject in detached mode (no interactive
|
|
7
|
+
* approver available).
|
|
8
|
+
*
|
|
9
|
+
* This file is NOT imported by index.ts — it is spawned via `child_process.spawn`.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=detached-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detached-runner.d.ts","sourceRoot":"","sources":["../src/detached-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detached runner — spawned as a child process for background (detached) runs.
|
|
3
|
+
*
|
|
4
|
+
* Reads a context JSON file (path passed as argv[2]), calls executeTaskflow,
|
|
5
|
+
* and persists the terminal state. Top-level try/catch writes status "failed"
|
|
6
|
+
* on crash. Approval phases auto-reject in detached mode (no interactive
|
|
7
|
+
* approver available).
|
|
8
|
+
*
|
|
9
|
+
* This file is NOT imported by index.ts — it is spawned via `child_process.spawn`.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { discoverAgents, readSubagentSettings } from "./agents.js";
|
|
13
|
+
import { executeTaskflow } from "./runtime.js";
|
|
14
|
+
import { getFlow, loadRun, saveRun, DEFAULT_KEPT_RUNS, DEFAULT_RUN_AGE_DAYS } from "./store.js";
|
|
15
|
+
const contextPath = process.argv[2];
|
|
16
|
+
if (!contextPath) {
|
|
17
|
+
console.error("[detached-runner] Missing context file path argument");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
let ctx;
|
|
21
|
+
try {
|
|
22
|
+
ctx = JSON.parse(readFileSync(contextPath, "utf-8"));
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
console.error(`[detached-runner] Failed to read context: ${e instanceof Error ? e.message : String(e)}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const cleanupConfig = { maxKeep: DEFAULT_KEPT_RUNS, maxAgeDays: DEFAULT_RUN_AGE_DAYS };
|
|
29
|
+
try {
|
|
30
|
+
const state = loadRun(ctx.cwd, ctx.runId);
|
|
31
|
+
if (!state) {
|
|
32
|
+
console.error(`[detached-runner] Run not found: ${ctx.runId}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
// Re-discover agents using the same settings as the host session.
|
|
36
|
+
const settings = readSubagentSettings();
|
|
37
|
+
cleanupConfig.maxKeep = settings.taskflow.maxKeptRuns;
|
|
38
|
+
cleanupConfig.maxAgeDays = settings.taskflow.maxRunAgeDays;
|
|
39
|
+
const scope = state.def.agentScope ?? "user";
|
|
40
|
+
const { agents } = discoverAgents(ctx.cwd, scope, settings.modelRoles, settings.taskflow);
|
|
41
|
+
const result = await executeTaskflow(state, {
|
|
42
|
+
cwd: ctx.cwd,
|
|
43
|
+
agents,
|
|
44
|
+
globalThinking: settings.globalThinking,
|
|
45
|
+
persist: (s) => saveRun(s, cleanupConfig),
|
|
46
|
+
// No requestApproval — approval phases auto-reject in detached/CI mode
|
|
47
|
+
// (safety: approval gates are never bypassed; the run records the rejection).
|
|
48
|
+
loadFlow: (name) => getFlow(ctx.cwd, name)?.def,
|
|
49
|
+
});
|
|
50
|
+
saveRun(result.state, cleanupConfig);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
// Top-level catch: persist failure so the host can poll the terminal state.
|
|
54
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
55
|
+
console.error(`[detached-runner] Fatal: ${message}`);
|
|
56
|
+
try {
|
|
57
|
+
const state = loadRun(ctx.cwd, ctx.runId);
|
|
58
|
+
if (state && state.status === "running") {
|
|
59
|
+
state.status = "failed";
|
|
60
|
+
saveRun(state, cleanupConfig);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Best-effort — if we can't even load the state, there's nothing to persist.
|
|
65
|
+
}
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=detached-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detached-runner.js","sourceRoot":"","sources":["../src/detached-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-addressed hashing for flow definitions.
|
|
3
|
+
*
|
|
4
|
+
* The canonical-JSON + SHA-256-truncation algorithm here is **vendored from
|
|
5
|
+
* overstory `packages/core/src/ir/hash.ts`** (pinned commit) so that
|
|
6
|
+
* pi-taskflow and overstory share one byte-identical hashing contract. This is
|
|
7
|
+
* the `M1` slice of the overstory-convergence roadmap: we are *not* compiling
|
|
8
|
+
* to overstory FlowIR yet (the IR compiler expects an explicit inject/emits
|
|
9
|
+
* model pi-taskflow doesn't have), but we share the **hash algorithm** now —
|
|
10
|
+
* the cheapest, lowest-risk piece of the contract — and put it to immediate
|
|
11
|
+
* work folding the flow *definition* into the cross-run cache key (M2).
|
|
12
|
+
*
|
|
13
|
+
* Why this matters: previously the cache key folded only the flow **name**
|
|
14
|
+
* (`flow:${flowName}`), so two structurally-different flows that happened to
|
|
15
|
+
* share a name + phase id + task could collide in the cross-run cache, and a
|
|
16
|
+
* flow that changed structure (but not name) could serve a stale hit. Folding
|
|
17
|
+
* `flowDefHash` (a content fingerprint of the desugared definition) closes
|
|
18
|
+
* that hole and is the foundation of "identical re-run is free ($0.00)".
|
|
19
|
+
*
|
|
20
|
+
* Pure module: no IO. Uses Web Crypto (`globalThis.crypto.subtle`) — therefore
|
|
21
|
+
* async — exactly like overstory's `hashIR`, so the contract is identical.
|
|
22
|
+
*
|
|
23
|
+
* @see docs/internal/overstory-convergence-roadmap.md §3 (M1, "cut B")
|
|
24
|
+
* @see docs/internal/rfc-flowir-compilation.md
|
|
25
|
+
*/
|
|
26
|
+
import type { Taskflow } from "../schema.ts";
|
|
27
|
+
/**
|
|
28
|
+
* Deterministic JSON: recursively key-sorted (UTF-16 code units), no
|
|
29
|
+
* whitespace, `undefined` values dropped. Arrays keep their order (the
|
|
30
|
+
* desugared Taskflow is already in a canonical shape). Byte-identical to
|
|
31
|
+
* overstory's `canonicalJson` — do not diverge without bumping the contract
|
|
32
|
+
* and updating the parity test.
|
|
33
|
+
*/
|
|
34
|
+
export declare function canonicalJson(value: unknown): string;
|
|
35
|
+
/** SHA-256 of the canonical serialization, first 16 bytes, lowercase hex.
|
|
36
|
+
* Same shape as overstory's `hashCanonical` / RFC-001 content hashes. */
|
|
37
|
+
export declare function hashCanonical(canonical: string): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Content fingerprint of a desugared `Taskflow` definition.
|
|
40
|
+
*
|
|
41
|
+
* Hashes the **definition** (structure + task text + declared deps), NOT the
|
|
42
|
+
* runtime `args` values — args vary per invocation and are already folded into
|
|
43
|
+
* each phase's `inputHash` via the interpolated task. `flowDefHash` answers a
|
|
44
|
+
* different question: "did the flow *itself* change?" Two flows are
|
|
45
|
+
* definitionally identical ⟺ this hash matches (key order / whitespace /
|
|
46
|
+
* optional-field presence do not affect it).
|
|
47
|
+
*
|
|
48
|
+
* Deterministic and async (Web Crypto), matching overstory's `hashIR` shape.
|
|
49
|
+
*/
|
|
50
|
+
export declare function flowDefHash(def: Taskflow): Promise<string>;
|
|
51
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/flowir/hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAM7C;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAoBpD;AAMD;0EAC0E;AAC1E,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAStE;AAMD;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAEhE"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-addressed hashing for flow definitions.
|
|
3
|
+
*
|
|
4
|
+
* The canonical-JSON + SHA-256-truncation algorithm here is **vendored from
|
|
5
|
+
* overstory `packages/core/src/ir/hash.ts`** (pinned commit) so that
|
|
6
|
+
* pi-taskflow and overstory share one byte-identical hashing contract. This is
|
|
7
|
+
* the `M1` slice of the overstory-convergence roadmap: we are *not* compiling
|
|
8
|
+
* to overstory FlowIR yet (the IR compiler expects an explicit inject/emits
|
|
9
|
+
* model pi-taskflow doesn't have), but we share the **hash algorithm** now —
|
|
10
|
+
* the cheapest, lowest-risk piece of the contract — and put it to immediate
|
|
11
|
+
* work folding the flow *definition* into the cross-run cache key (M2).
|
|
12
|
+
*
|
|
13
|
+
* Why this matters: previously the cache key folded only the flow **name**
|
|
14
|
+
* (`flow:${flowName}`), so two structurally-different flows that happened to
|
|
15
|
+
* share a name + phase id + task could collide in the cross-run cache, and a
|
|
16
|
+
* flow that changed structure (but not name) could serve a stale hit. Folding
|
|
17
|
+
* `flowDefHash` (a content fingerprint of the desugared definition) closes
|
|
18
|
+
* that hole and is the foundation of "identical re-run is free ($0.00)".
|
|
19
|
+
*
|
|
20
|
+
* Pure module: no IO. Uses Web Crypto (`globalThis.crypto.subtle`) — therefore
|
|
21
|
+
* async — exactly like overstory's `hashIR`, so the contract is identical.
|
|
22
|
+
*
|
|
23
|
+
* @see docs/internal/overstory-convergence-roadmap.md §3 (M1, "cut B")
|
|
24
|
+
* @see docs/internal/rfc-flowir-compilation.md
|
|
25
|
+
*/
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Canonical JSON (vendored from overstory ir/hash.ts — byte-identical)
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* Deterministic JSON: recursively key-sorted (UTF-16 code units), no
|
|
31
|
+
* whitespace, `undefined` values dropped. Arrays keep their order (the
|
|
32
|
+
* desugared Taskflow is already in a canonical shape). Byte-identical to
|
|
33
|
+
* overstory's `canonicalJson` — do not diverge without bumping the contract
|
|
34
|
+
* and updating the parity test.
|
|
35
|
+
*/
|
|
36
|
+
export function canonicalJson(value) {
|
|
37
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
38
|
+
return JSON.stringify(value);
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
return JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
return `[${value.map((item) => canonicalJson(item === undefined ? null : item)).join(",")}]`;
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === "object") {
|
|
47
|
+
const record = value;
|
|
48
|
+
const keys = Object.keys(record)
|
|
49
|
+
.filter((key) => record[key] !== undefined)
|
|
50
|
+
.sort();
|
|
51
|
+
const body = keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`);
|
|
52
|
+
return `{${body.join(",")}}`;
|
|
53
|
+
}
|
|
54
|
+
// undefined / function / symbol at the top level — not representable.
|
|
55
|
+
return "null";
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Hashing (vendored from overstory ir/hash.ts — byte-identical)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/** SHA-256 of the canonical serialization, first 16 bytes, lowercase hex.
|
|
61
|
+
* Same shape as overstory's `hashCanonical` / RFC-001 content hashes. */
|
|
62
|
+
export async function hashCanonical(canonical) {
|
|
63
|
+
const bytes = new TextEncoder().encode(canonical);
|
|
64
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
65
|
+
const view = new Uint8Array(digest).slice(0, 16);
|
|
66
|
+
let hex = "";
|
|
67
|
+
for (const byte of view) {
|
|
68
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
69
|
+
}
|
|
70
|
+
return hex;
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Flow-definition fingerprint
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* Content fingerprint of a desugared `Taskflow` definition.
|
|
77
|
+
*
|
|
78
|
+
* Hashes the **definition** (structure + task text + declared deps), NOT the
|
|
79
|
+
* runtime `args` values — args vary per invocation and are already folded into
|
|
80
|
+
* each phase's `inputHash` via the interpolated task. `flowDefHash` answers a
|
|
81
|
+
* different question: "did the flow *itself* change?" Two flows are
|
|
82
|
+
* definitionally identical ⟺ this hash matches (key order / whitespace /
|
|
83
|
+
* optional-field presence do not affect it).
|
|
84
|
+
*
|
|
85
|
+
* Deterministic and async (Web Crypto), matching overstory's `hashIR` shape.
|
|
86
|
+
*/
|
|
87
|
+
export async function flowDefHash(def) {
|
|
88
|
+
return hashCanonical(canonicalJson(def));
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/flowir/hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,8EAA8E;AAC9E,uEAAuE;AACvE,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC3C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/E,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC9F,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,KAAgC,CAAC;QAChD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;aAC9B,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;aAC1C,IAAI,EAAE,CAAC;QACT,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QACvF,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC9B,CAAC;IACD,sEAAsE;IACtE,OAAO,MAAM,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,gEAAgE;AAChE,8EAA8E;AAE9E;0EAC0E;AAC1E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,SAAiB;IACpD,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACvE,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACzB,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,8EAA8E;AAC9E,8BAA8B;AAC9B,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAa;IAC9C,OAAO,aAAa,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1C,CAAC"}
|