@lmthing/repl 0.0.1

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/index.js ADDED
@@ -0,0 +1,3809 @@
1
+ // src/session/session.ts
2
+ import { EventEmitter } from "events";
3
+
4
+ // src/session/config.ts
5
+ import { z } from "zod";
6
+ var DEFAULT_CONFIG = {
7
+ functionTimeout: 3e4,
8
+ askTimeout: 3e5,
9
+ sessionTimeout: 6e5,
10
+ maxStopCalls: 50,
11
+ maxAsyncTasks: 10,
12
+ maxTasklistReminders: 3,
13
+ maxTaskRetries: 3,
14
+ maxTasksPerTasklist: 20,
15
+ taskAsyncTimeout: 6e4,
16
+ sleepMaxSeconds: 30,
17
+ maxContextTokens: 1e5,
18
+ serializationLimits: {
19
+ maxStringLength: 2e3,
20
+ maxArrayElements: 50,
21
+ maxObjectKeys: 20,
22
+ maxDepth: 5
23
+ },
24
+ workspace: {
25
+ maxScopeVariables: 50,
26
+ maxScopeValueWidth: 50,
27
+ maxScopeTokens: 3e3
28
+ },
29
+ contextWindow: {
30
+ codeWindowLines: 200,
31
+ stopDecayTiers: {
32
+ full: 2,
33
+ keysOnly: 5,
34
+ summary: 10
35
+ },
36
+ neverTruncateInterventions: true
37
+ }
38
+ };
39
+ function createDefaultConfig() {
40
+ return structuredClone(DEFAULT_CONFIG);
41
+ }
42
+ var sessionConfigSchema = z.object({
43
+ functionTimeout: z.number().positive().optional(),
44
+ askTimeout: z.number().positive().optional(),
45
+ sessionTimeout: z.number().positive().optional(),
46
+ maxStopCalls: z.number().int().positive().optional(),
47
+ maxAsyncTasks: z.number().int().positive().optional(),
48
+ maxTasklistReminders: z.number().int().positive().optional(),
49
+ maxTaskRetries: z.number().int().positive().optional(),
50
+ maxTasksPerTasklist: z.number().int().positive().optional(),
51
+ taskAsyncTimeout: z.number().int().positive().optional(),
52
+ sleepMaxSeconds: z.number().int().positive().optional(),
53
+ maxContextTokens: z.number().int().positive().optional(),
54
+ serializationLimits: z.object({
55
+ maxStringLength: z.number().int().positive().optional(),
56
+ maxArrayElements: z.number().int().positive().optional(),
57
+ maxObjectKeys: z.number().int().positive().optional(),
58
+ maxDepth: z.number().int().positive().optional()
59
+ }).optional(),
60
+ workspace: z.object({
61
+ maxScopeVariables: z.number().int().positive().optional(),
62
+ maxScopeValueWidth: z.number().int().positive().optional(),
63
+ maxScopeTokens: z.number().int().positive().optional()
64
+ }).optional(),
65
+ contextWindow: z.object({
66
+ codeWindowLines: z.number().int().positive().optional(),
67
+ stopDecayTiers: z.object({
68
+ full: z.number().int().nonnegative().optional(),
69
+ keysOnly: z.number().int().nonnegative().optional(),
70
+ summary: z.number().int().nonnegative().optional()
71
+ }).optional(),
72
+ neverTruncateInterventions: z.boolean().optional()
73
+ }).optional()
74
+ });
75
+ function validateConfig(input) {
76
+ const result = sessionConfigSchema.safeParse(input);
77
+ if (result.success) {
78
+ return { valid: true, config: result.data };
79
+ }
80
+ return {
81
+ valid: false,
82
+ errors: result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`)
83
+ };
84
+ }
85
+ function mergeConfig(overrides) {
86
+ const base = createDefaultConfig();
87
+ return {
88
+ functionTimeout: overrides.functionTimeout ?? base.functionTimeout,
89
+ askTimeout: overrides.askTimeout ?? base.askTimeout,
90
+ sessionTimeout: overrides.sessionTimeout ?? base.sessionTimeout,
91
+ maxStopCalls: overrides.maxStopCalls ?? base.maxStopCalls,
92
+ maxAsyncTasks: overrides.maxAsyncTasks ?? base.maxAsyncTasks,
93
+ maxTasklistReminders: overrides.maxTasklistReminders ?? base.maxTasklistReminders,
94
+ maxTaskRetries: overrides.maxTaskRetries ?? base.maxTaskRetries,
95
+ maxTasksPerTasklist: overrides.maxTasksPerTasklist ?? base.maxTasksPerTasklist,
96
+ taskAsyncTimeout: overrides.taskAsyncTimeout ?? base.taskAsyncTimeout,
97
+ sleepMaxSeconds: overrides.sleepMaxSeconds ?? base.sleepMaxSeconds,
98
+ maxContextTokens: overrides.maxContextTokens ?? base.maxContextTokens,
99
+ serializationLimits: {
100
+ maxStringLength: overrides.serializationLimits?.maxStringLength ?? base.serializationLimits.maxStringLength,
101
+ maxArrayElements: overrides.serializationLimits?.maxArrayElements ?? base.serializationLimits.maxArrayElements,
102
+ maxObjectKeys: overrides.serializationLimits?.maxObjectKeys ?? base.serializationLimits.maxObjectKeys,
103
+ maxDepth: overrides.serializationLimits?.maxDepth ?? base.serializationLimits.maxDepth
104
+ },
105
+ workspace: {
106
+ maxScopeVariables: overrides.workspace?.maxScopeVariables ?? base.workspace.maxScopeVariables,
107
+ maxScopeValueWidth: overrides.workspace?.maxScopeValueWidth ?? base.workspace.maxScopeValueWidth,
108
+ maxScopeTokens: overrides.workspace?.maxScopeTokens ?? base.workspace.maxScopeTokens
109
+ },
110
+ contextWindow: {
111
+ codeWindowLines: overrides.contextWindow?.codeWindowLines ?? base.contextWindow.codeWindowLines,
112
+ stopDecayTiers: {
113
+ full: overrides.contextWindow?.stopDecayTiers?.full ?? base.contextWindow.stopDecayTiers.full,
114
+ keysOnly: overrides.contextWindow?.stopDecayTiers?.keysOnly ?? base.contextWindow.stopDecayTiers.keysOnly,
115
+ summary: overrides.contextWindow?.stopDecayTiers?.summary ?? base.contextWindow.stopDecayTiers.summary
116
+ },
117
+ neverTruncateInterventions: overrides.contextWindow?.neverTruncateInterventions ?? base.contextWindow.neverTruncateInterventions
118
+ }
119
+ };
120
+ }
121
+
122
+ // src/sandbox/sandbox.ts
123
+ import vm2 from "vm";
124
+ import React from "react";
125
+
126
+ // src/sandbox/executor.ts
127
+ import vm from "vm";
128
+
129
+ // src/sandbox/transpiler.ts
130
+ import ts from "typescript";
131
+ var compilerOptions = {
132
+ target: ts.ScriptTarget.ESNext,
133
+ module: ts.ModuleKind.ESNext,
134
+ jsx: ts.JsxEmit.React,
135
+ jsxFactory: "React.createElement",
136
+ jsxFragmentFactory: "React.Fragment",
137
+ strict: false,
138
+ esModuleInterop: true
139
+ };
140
+ function transpile(code) {
141
+ const result = ts.transpileModule(code, { compilerOptions });
142
+ return result.outputText;
143
+ }
144
+
145
+ // src/parser/ast-utils.ts
146
+ import ts2 from "typescript";
147
+ function parseStatement(source) {
148
+ const sourceFile = ts2.createSourceFile(
149
+ "line.ts",
150
+ source,
151
+ ts2.ScriptTarget.ESNext,
152
+ true,
153
+ ts2.ScriptKind.TSX
154
+ );
155
+ const statements = sourceFile.statements;
156
+ if (statements.length === 0) return null;
157
+ return statements[0];
158
+ }
159
+ function extractDeclarations(source) {
160
+ const node = parseStatement(source);
161
+ if (!node) return [];
162
+ const names = [];
163
+ if (ts2.isVariableStatement(node)) {
164
+ for (const decl of node.declarationList.declarations) {
165
+ extractBindingNames(decl.name, names);
166
+ }
167
+ } else if (ts2.isFunctionDeclaration(node) && node.name) {
168
+ names.push(node.name.text);
169
+ } else if (ts2.isClassDeclaration(node) && node.name) {
170
+ names.push(node.name.text);
171
+ }
172
+ return names;
173
+ }
174
+ function extractBindingNames(node, names) {
175
+ if (ts2.isIdentifier(node)) {
176
+ names.push(node.text);
177
+ } else if (ts2.isObjectBindingPattern(node)) {
178
+ for (const element of node.elements) {
179
+ extractBindingNames(element.name, names);
180
+ }
181
+ } else if (ts2.isArrayBindingPattern(node)) {
182
+ for (const element of node.elements) {
183
+ if (ts2.isBindingElement(element)) {
184
+ extractBindingNames(element.name, names);
185
+ }
186
+ }
187
+ }
188
+ }
189
+ function recoverArgumentNames(source) {
190
+ const sourceFile = ts2.createSourceFile(
191
+ "line.ts",
192
+ source,
193
+ ts2.ScriptTarget.ESNext,
194
+ true,
195
+ ts2.ScriptKind.TSX
196
+ );
197
+ let callExpr = null;
198
+ function visit(node) {
199
+ if (callExpr) return;
200
+ if (ts2.isCallExpression(node)) {
201
+ const callee = node.expression;
202
+ if (ts2.isIdentifier(callee)) {
203
+ const name = callee.text;
204
+ if (name === "stop" || name === "display" || name === "ask" || name === "async") {
205
+ callExpr = node;
206
+ return;
207
+ }
208
+ }
209
+ }
210
+ ts2.forEachChild(node, visit);
211
+ }
212
+ visit(sourceFile);
213
+ if (!callExpr) return [];
214
+ return callExpr.arguments.map((arg, i) => {
215
+ if (ts2.isIdentifier(arg)) {
216
+ return arg.text;
217
+ }
218
+ if (ts2.isPropertyAccessExpression(arg)) {
219
+ return arg.getText(sourceFile);
220
+ }
221
+ if (ts2.isElementAccessExpression(arg)) {
222
+ return arg.getText(sourceFile);
223
+ }
224
+ return `arg_${i}`;
225
+ });
226
+ }
227
+ function extractVariableNames(source) {
228
+ const sourceFile = ts2.createSourceFile(
229
+ "line.ts",
230
+ source,
231
+ ts2.ScriptTarget.ESNext,
232
+ true,
233
+ ts2.ScriptKind.TSX
234
+ );
235
+ const names = /* @__PURE__ */ new Set();
236
+ function visit(node) {
237
+ if (ts2.isIdentifier(node)) {
238
+ const parent = node.parent;
239
+ if (parent && ts2.isPropertyAccessExpression(parent) && parent.name === node) {
240
+ } else {
241
+ names.add(node.text);
242
+ }
243
+ }
244
+ ts2.forEachChild(node, visit);
245
+ }
246
+ visit(sourceFile);
247
+ return [...names];
248
+ }
249
+
250
+ // src/sandbox/executor.ts
251
+ async function executeLine(code, lineNumber, context, timeout = 3e4) {
252
+ try {
253
+ const js = transpile(code);
254
+ const trimmedJs = js.trim();
255
+ if (trimmedJs === "") {
256
+ return { ok: true, result: void 0 };
257
+ }
258
+ const hasAwait = trimmedJs.includes("await ");
259
+ if (hasAwait) {
260
+ const declaredNames = extractDeclarations(code);
261
+ const assignments = declaredNames.map((name) => `globalThis[${JSON.stringify(name)}] = typeof ${name} !== 'undefined' ? ${name} : undefined;`).join("\n");
262
+ const wrapped = `(async () => {
263
+ ${trimmedJs}
264
+ ${assignments}
265
+ })()`;
266
+ const script = new vm.Script(wrapped, { filename: `line-${lineNumber}.js` });
267
+ const result = await script.runInContext(context, { timeout });
268
+ return { ok: true, result };
269
+ } else {
270
+ const script = new vm.Script(trimmedJs, { filename: `line-${lineNumber}.js` });
271
+ const result = await script.runInContext(context, { timeout });
272
+ return { ok: true, result };
273
+ }
274
+ } catch (err) {
275
+ const error = err;
276
+ const payload = {
277
+ type: error.constructor?.name ?? "Error",
278
+ message: error.message,
279
+ line: lineNumber,
280
+ source: code.trim()
281
+ };
282
+ return { ok: false, error: payload };
283
+ }
284
+ }
285
+
286
+ // src/sandbox/sandbox.ts
287
+ var BLOCKED_GLOBALS = [
288
+ "process",
289
+ "require",
290
+ "module",
291
+ "exports",
292
+ "__filename",
293
+ "__dirname",
294
+ "eval",
295
+ "Function"
296
+ ];
297
+ var Sandbox = class {
298
+ context;
299
+ declaredNames = /* @__PURE__ */ new Set();
300
+ lineCount = 0;
301
+ timeout;
302
+ constructor(options = {}) {
303
+ this.timeout = options.timeout ?? 3e4;
304
+ const contextGlobals = {
305
+ React,
306
+ console,
307
+ setTimeout,
308
+ clearTimeout,
309
+ setInterval,
310
+ clearInterval,
311
+ Promise,
312
+ Array,
313
+ Object,
314
+ String,
315
+ Number,
316
+ Boolean,
317
+ Date,
318
+ Math,
319
+ JSON,
320
+ Map,
321
+ Set,
322
+ WeakMap,
323
+ WeakSet,
324
+ RegExp,
325
+ Error,
326
+ TypeError,
327
+ RangeError,
328
+ SyntaxError,
329
+ ReferenceError,
330
+ Symbol,
331
+ Uint8Array,
332
+ Int32Array,
333
+ Float64Array,
334
+ ArrayBuffer,
335
+ DataView,
336
+ URL,
337
+ URLSearchParams,
338
+ TextEncoder,
339
+ TextDecoder,
340
+ structuredClone,
341
+ atob: globalThis.atob,
342
+ btoa: globalThis.btoa,
343
+ ...options.globals
344
+ };
345
+ this.context = vm2.createContext(contextGlobals);
346
+ for (const name of BLOCKED_GLOBALS) {
347
+ Object.defineProperty(this.context, name, {
348
+ get() {
349
+ throw new Error(`${name} is not available in the sandbox`);
350
+ },
351
+ configurable: false
352
+ });
353
+ }
354
+ }
355
+ /**
356
+ * Execute a line of TypeScript in the sandbox.
357
+ */
358
+ async execute(code) {
359
+ this.lineCount++;
360
+ const declarations = extractDeclarations(code);
361
+ for (const name of declarations) {
362
+ this.declaredNames.add(name);
363
+ }
364
+ return executeLine(code, this.lineCount, this.context, this.timeout);
365
+ }
366
+ /**
367
+ * Inject a value into the sandbox's global scope.
368
+ */
369
+ inject(name, value) {
370
+ this.context[name] = value;
371
+ }
372
+ /**
373
+ * Get a value from the sandbox scope.
374
+ */
375
+ getValue(name) {
376
+ return this.context[name];
377
+ }
378
+ /**
379
+ * Get all user-declared variable names.
380
+ */
381
+ getDeclaredNames() {
382
+ return [...this.declaredNames];
383
+ }
384
+ /**
385
+ * Get the current scope as ScopeEntry[].
386
+ */
387
+ getScope() {
388
+ const entries = [];
389
+ for (const name of this.declaredNames) {
390
+ try {
391
+ const value = this.context[name];
392
+ entries.push({
393
+ name,
394
+ type: describeType(value),
395
+ value: truncateValue(value)
396
+ });
397
+ } catch {
398
+ entries.push({ name, type: "unknown", value: "<error reading>" });
399
+ }
400
+ }
401
+ return entries;
402
+ }
403
+ /**
404
+ * Get the line count.
405
+ */
406
+ getLineCount() {
407
+ return this.lineCount;
408
+ }
409
+ /**
410
+ * Get the raw vm.Context (for advanced use).
411
+ */
412
+ getContext() {
413
+ return this.context;
414
+ }
415
+ /**
416
+ * Destroy the sandbox.
417
+ */
418
+ destroy() {
419
+ this.declaredNames.clear();
420
+ }
421
+ };
422
+ function describeType(val) {
423
+ if (val === null) return "null";
424
+ if (val === void 0) return "undefined";
425
+ if (Array.isArray(val)) {
426
+ if (val.length === 0) return "Array";
427
+ const firstType = describeType(val[0]);
428
+ return `Array<${firstType}>`;
429
+ }
430
+ const t = typeof val;
431
+ if (t === "object") {
432
+ const name = val.constructor?.name;
433
+ return name && name !== "Object" ? name : "Object";
434
+ }
435
+ return t;
436
+ }
437
+ function truncateValue(val, maxLen = 50) {
438
+ if (val === null) return "null";
439
+ if (val === void 0) return "undefined";
440
+ if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
441
+ if (typeof val === "symbol") return val.toString();
442
+ try {
443
+ let str;
444
+ if (typeof val === "string") {
445
+ str = JSON.stringify(val);
446
+ } else if (Array.isArray(val)) {
447
+ const preview = val.slice(0, 3).map((v) => truncateValue(v, 20)).join(", ");
448
+ str = val.length > 3 ? `[${preview}, ... +${val.length - 3}]` : `[${preview}]`;
449
+ } else if (typeof val === "object") {
450
+ const keys = Object.keys(val);
451
+ const preview = keys.slice(0, 5).join(", ");
452
+ str = keys.length > 5 ? `{${preview}, ... +${keys.length - 5}}` : `{${preview}}`;
453
+ } else {
454
+ str = String(val);
455
+ }
456
+ if (str.length > maxLen) {
457
+ return str.slice(0, maxLen - 3) + "...";
458
+ }
459
+ return str;
460
+ } catch {
461
+ return "[value]";
462
+ }
463
+ }
464
+
465
+ // src/stream/serializer.ts
466
+ var DEFAULT_LIMITS = {
467
+ maxStringLength: 2e3,
468
+ maxArrayElements: 50,
469
+ maxObjectKeys: 20,
470
+ maxDepth: 5
471
+ };
472
+ function serialize(value, limits = {}) {
473
+ const opts = { ...DEFAULT_LIMITS, ...limits };
474
+ const seen = /* @__PURE__ */ new WeakSet();
475
+ return serializeValue(value, opts, seen, 0);
476
+ }
477
+ function serializeValue(value, limits, seen, depth) {
478
+ if (value === null) return "null";
479
+ if (value === void 0) return "undefined";
480
+ const type = typeof value;
481
+ if (type === "string") {
482
+ const str = value;
483
+ if (str.length > limits.maxStringLength) {
484
+ const half = Math.floor(limits.maxStringLength / 2);
485
+ return JSON.stringify(str.slice(0, half) + `... (truncated, ${str.length} chars total)`);
486
+ }
487
+ return JSON.stringify(str);
488
+ }
489
+ if (type === "number" || type === "boolean") {
490
+ return JSON.stringify(value);
491
+ }
492
+ if (type === "function") {
493
+ const fn = value;
494
+ return `[Function: ${fn.name || "anonymous"}]`;
495
+ }
496
+ if (type === "symbol") {
497
+ return `[Symbol: ${value.description ?? ""}]`;
498
+ }
499
+ if (type === "bigint") {
500
+ return `${value}n`;
501
+ }
502
+ if (value instanceof Error) {
503
+ return `[Error: ${value.message}]`;
504
+ }
505
+ if (value instanceof Promise) {
506
+ return "[Promise]";
507
+ }
508
+ if (value instanceof Date) {
509
+ return `"${value.toISOString()}"`;
510
+ }
511
+ if (value instanceof RegExp) {
512
+ return value.toString();
513
+ }
514
+ if (type === "object") {
515
+ const obj = value;
516
+ if (seen.has(obj)) return "[Circular]";
517
+ seen.add(obj);
518
+ if (depth >= limits.maxDepth) {
519
+ if (Array.isArray(obj)) return `[Array(${obj.length})]`;
520
+ return `[Object]`;
521
+ }
522
+ if (Array.isArray(obj)) {
523
+ return serializeArray(obj, limits, seen, depth);
524
+ }
525
+ if (obj instanceof Map) {
526
+ const entries = [];
527
+ let count = 0;
528
+ for (const [k, v] of obj) {
529
+ if (count >= limits.maxObjectKeys) {
530
+ entries.push(`... +${obj.size - count} more`);
531
+ break;
532
+ }
533
+ entries.push(`${serializeValue(k, limits, seen, depth + 1)}: ${serializeValue(v, limits, seen, depth + 1)}`);
534
+ count++;
535
+ }
536
+ return `Map { ${entries.join(", ")} }`;
537
+ }
538
+ if (obj instanceof Set) {
539
+ const items = [];
540
+ let count = 0;
541
+ for (const v of obj) {
542
+ if (count >= limits.maxArrayElements) {
543
+ items.push(`... +${obj.size - count} more`);
544
+ break;
545
+ }
546
+ items.push(serializeValue(v, limits, seen, depth + 1));
547
+ count++;
548
+ }
549
+ return `Set { ${items.join(", ")} }`;
550
+ }
551
+ return serializeObject(obj, limits, seen, depth);
552
+ }
553
+ return String(value);
554
+ }
555
+ function serializeArray(arr, limits, seen, depth) {
556
+ if (arr.length === 0) return "[]";
557
+ const items = [];
558
+ const max = Math.min(arr.length, limits.maxArrayElements);
559
+ for (let i = 0; i < max; i++) {
560
+ items.push(serializeValue(arr[i], limits, seen, depth + 1));
561
+ }
562
+ if (arr.length > limits.maxArrayElements) {
563
+ items.push(`... +${arr.length - limits.maxArrayElements} more`);
564
+ }
565
+ return `[${items.join(", ")}]`;
566
+ }
567
+ function serializeObject(obj, limits, seen, depth) {
568
+ const keys = Object.keys(obj);
569
+ if (keys.length === 0) return "{}";
570
+ const entries = [];
571
+ const max = Math.min(keys.length, limits.maxObjectKeys);
572
+ for (let i = 0; i < max; i++) {
573
+ const key = keys[i];
574
+ const val = obj[key];
575
+ entries.push(`${JSON.stringify(key)}: ${serializeValue(val, limits, seen, depth + 1)}`);
576
+ }
577
+ if (keys.length > limits.maxObjectKeys) {
578
+ entries.push(`... +${keys.length - limits.maxObjectKeys} more`);
579
+ }
580
+ return `{ ${entries.join(", ")} }`;
581
+ }
582
+
583
+ // src/context/knowledge-decay.ts
584
+ var KNOWLEDGE_TAG = /* @__PURE__ */ Symbol.for("lmthing:knowledge");
585
+ var DEFAULT_TIERS = {
586
+ full: 0,
587
+ truncated: 2,
588
+ headers: 4
589
+ };
590
+ function getKnowledgeDecayLevel(distance, tiers = DEFAULT_TIERS) {
591
+ if (distance <= tiers.full) return "full";
592
+ if (distance <= tiers.truncated) return "truncated";
593
+ if (distance <= tiers.headers) return "headers";
594
+ return "names";
595
+ }
596
+ function isKnowledgeContent(value) {
597
+ return value !== null && typeof value === "object" && value[KNOWLEDGE_TAG] === true;
598
+ }
599
+ function tagAsKnowledge(obj) {
600
+ Object.defineProperty(obj, KNOWLEDGE_TAG, {
601
+ value: true,
602
+ enumerable: false,
603
+ configurable: false
604
+ });
605
+ return obj;
606
+ }
607
+ function decayKnowledgeValue(content, distance, tiers = DEFAULT_TIERS) {
608
+ const level = getKnowledgeDecayLevel(distance, tiers);
609
+ switch (level) {
610
+ case "full":
611
+ return formatFull(content);
612
+ case "truncated":
613
+ return formatTruncated(content);
614
+ case "headers":
615
+ return formatHeaders(content);
616
+ case "names":
617
+ return formatNames(content);
618
+ }
619
+ }
620
+ function formatFull(content) {
621
+ return serializeNested(content, (md) => JSON.stringify(md));
622
+ }
623
+ function formatTruncated(content) {
624
+ return serializeNested(content, (md) => {
625
+ if (md.length <= 300) return JSON.stringify(md);
626
+ return JSON.stringify(md.slice(0, 300) + `...(truncated, ${md.length} chars)`);
627
+ });
628
+ }
629
+ function formatHeaders(content) {
630
+ return serializeNested(content, (md) => {
631
+ const headers = md.split("\n").filter((line) => /^#{1,4}\s/.test(line)).join(" | ");
632
+ return JSON.stringify(headers || "(no headings)");
633
+ });
634
+ }
635
+ function formatNames(content) {
636
+ const paths = [];
637
+ collectPaths(content, [], paths);
638
+ return `[knowledge: ${paths.join(", ")}]`;
639
+ }
640
+ function collectPaths(obj, prefix, out) {
641
+ for (const [key, value] of Object.entries(obj)) {
642
+ if (typeof value === "string") {
643
+ out.push([...prefix, key].join("/"));
644
+ } else if (typeof value === "object" && value !== null) {
645
+ collectPaths(value, [...prefix, key], out);
646
+ }
647
+ }
648
+ }
649
+ function serializeNested(content, formatLeaf) {
650
+ return serializeLevel(content, formatLeaf);
651
+ }
652
+ function serializeLevel(obj, formatLeaf) {
653
+ const entries = [];
654
+ for (const [key, value] of Object.entries(obj)) {
655
+ if (typeof value === "string") {
656
+ entries.push(`${JSON.stringify(key)}: ${formatLeaf(value)}`);
657
+ } else if (typeof value === "object" && value !== null) {
658
+ entries.push(`${JSON.stringify(key)}: ${serializeLevel(value, formatLeaf)}`);
659
+ }
660
+ }
661
+ return `{ ${entries.join(", ")} }`;
662
+ }
663
+
664
+ // src/sandbox/globals.ts
665
+ function createGlobals(config) {
666
+ const {
667
+ pauseController,
668
+ renderSurface,
669
+ asyncManager,
670
+ askTimeout = 3e5
671
+ } = config;
672
+ const tasklistsState = {
673
+ tasklists: /* @__PURE__ */ new Map()
674
+ };
675
+ let currentSource = "";
676
+ function setCurrentSource(source) {
677
+ currentSource = source;
678
+ }
679
+ let stopResolve = null;
680
+ async function stopFn(...values) {
681
+ const argNames = recoverArgumentNames(currentSource);
682
+ const resolved = await Promise.allSettled(
683
+ values.map((v) => v instanceof Promise ? v : Promise.resolve(v))
684
+ );
685
+ const payload = {};
686
+ for (let i = 0; i < resolved.length; i++) {
687
+ const key = argNames[i] ?? `arg_${i}`;
688
+ const settlement = resolved[i];
689
+ const value = settlement.status === "fulfilled" ? settlement.value : {
690
+ _error: settlement.reason instanceof Error ? settlement.reason.message : String(settlement.reason)
691
+ };
692
+ payload[key] = {
693
+ value,
694
+ display: serialize(value, config.serializationLimits)
695
+ };
696
+ }
697
+ const asyncPayload = asyncManager.buildStopPayload();
698
+ for (const [key, val] of Object.entries(asyncPayload)) {
699
+ payload[key] = {
700
+ value: val,
701
+ display: serialize(val, config.serializationLimits)
702
+ };
703
+ }
704
+ const promise = new Promise((resolve) => {
705
+ stopResolve = resolve;
706
+ });
707
+ pauseController.pause();
708
+ config.onStop?.(payload, currentSource);
709
+ return promise;
710
+ }
711
+ function resolveStop() {
712
+ if (stopResolve) {
713
+ const resolve = stopResolve;
714
+ stopResolve = null;
715
+ resolve();
716
+ }
717
+ }
718
+ function displayFn(element) {
719
+ const id = `display_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
720
+ renderSurface.append(id, element);
721
+ config.onDisplay?.(id);
722
+ }
723
+ async function askFn(element) {
724
+ const formId = `form_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
725
+ pauseController.pause();
726
+ try {
727
+ const result = await Promise.race([
728
+ renderSurface.renderForm(formId, element),
729
+ new Promise(
730
+ (resolve) => setTimeout(() => resolve({ _timeout: true }), askTimeout)
731
+ )
732
+ ]);
733
+ return result;
734
+ } finally {
735
+ pauseController.resume();
736
+ }
737
+ }
738
+ function asyncFn(fn, label) {
739
+ const derivedLabel = label ?? deriveLabel(currentSource);
740
+ const taskId = asyncManager.register(
741
+ (signal) => fn(),
742
+ derivedLabel
743
+ );
744
+ config.onAsyncStart?.(taskId, derivedLabel);
745
+ }
746
+ function tasklistFn(tasklistId, description, tasks) {
747
+ if (tasklistsState.tasklists.has(tasklistId)) {
748
+ throw new Error(`tasklist() tasklist "${tasklistId}" already declared`);
749
+ }
750
+ if (!tasklistId) {
751
+ throw new Error("tasklist() requires a tasklistId");
752
+ }
753
+ if (!description || !Array.isArray(tasks) || tasks.length === 0) {
754
+ throw new Error("tasklist() requires a description and at least one task");
755
+ }
756
+ const maxTasks = config.maxTasksPerTasklist ?? 20;
757
+ if (tasks.length > maxTasks) {
758
+ throw new Error(`tasklist() exceeds maximum of ${maxTasks} tasks per tasklist`);
759
+ }
760
+ const ids = /* @__PURE__ */ new Set();
761
+ for (const task of tasks) {
762
+ if (!task.id || !task.instructions || !task.outputSchema) {
763
+ throw new Error("Each task must have id, instructions, and outputSchema");
764
+ }
765
+ if (ids.has(task.id)) {
766
+ throw new Error(`Duplicate task id: ${task.id}`);
767
+ }
768
+ ids.add(task.id);
769
+ }
770
+ for (const task of tasks) {
771
+ if (task.dependsOn) {
772
+ for (const dep of task.dependsOn) {
773
+ if (!ids.has(dep)) {
774
+ throw new Error(`Task "${task.id}" depends on unknown task "${dep}" in tasklist "${tasklistId}"`);
775
+ }
776
+ if (dep === task.id) {
777
+ throw new Error(`Task "${task.id}" cannot depend on itself`);
778
+ }
779
+ }
780
+ }
781
+ }
782
+ const hasDependsOn = tasks.some((t) => t.dependsOn && t.dependsOn.length > 0);
783
+ if (!hasDependsOn) {
784
+ for (let i = 1; i < tasks.length; i++) {
785
+ tasks[i] = { ...tasks[i], dependsOn: [tasks[i - 1].id] };
786
+ }
787
+ }
788
+ const visited = /* @__PURE__ */ new Set();
789
+ const visiting = /* @__PURE__ */ new Set();
790
+ const taskMap = new Map(tasks.map((t) => [t.id, t]));
791
+ function visit(id) {
792
+ if (visited.has(id)) return;
793
+ if (visiting.has(id)) {
794
+ throw new Error(`Cycle detected in tasklist "${tasklistId}" involving task "${id}"`);
795
+ }
796
+ visiting.add(id);
797
+ const task = taskMap.get(id);
798
+ if (task.dependsOn) {
799
+ for (const dep of task.dependsOn) {
800
+ visit(dep);
801
+ }
802
+ }
803
+ visiting.delete(id);
804
+ visited.add(id);
805
+ }
806
+ for (const task of tasks) {
807
+ visit(task.id);
808
+ }
809
+ const readyTasks = /* @__PURE__ */ new Set();
810
+ for (const task of tasks) {
811
+ if (!task.dependsOn || task.dependsOn.length === 0) {
812
+ readyTasks.add(task.id);
813
+ }
814
+ }
815
+ const plan = { tasklistId, description, tasks };
816
+ const tasklistState = {
817
+ plan,
818
+ completed: /* @__PURE__ */ new Map(),
819
+ readyTasks,
820
+ runningTasks: /* @__PURE__ */ new Set(),
821
+ outputs: /* @__PURE__ */ new Map(),
822
+ progressMessages: /* @__PURE__ */ new Map(),
823
+ retryCount: /* @__PURE__ */ new Map()
824
+ };
825
+ tasklistsState.tasklists.set(tasklistId, tasklistState);
826
+ renderSurface.appendTasklistProgress?.(tasklistId, tasklistState);
827
+ config.onTasklistDeclared?.(tasklistId, plan);
828
+ }
829
+ function completeTaskFn(tasklistId, id, output) {
830
+ const tasklist = tasklistsState.tasklists.get(tasklistId);
831
+ if (!tasklist) {
832
+ throw new Error(`completeTask() called with unknown tasklist "${tasklistId}" \u2014 declare it with tasklist() first`);
833
+ }
834
+ const task = tasklist.plan.tasks.find((t) => t.id === id);
835
+ if (!task) {
836
+ throw new Error(`Unknown task id: ${id} in tasklist "${tasklistId}"`);
837
+ }
838
+ if (tasklist.completed.has(id)) {
839
+ throw new Error(`Task "${id}" in tasklist "${tasklistId}" already completed`);
840
+ }
841
+ if (!tasklist.readyTasks.has(id)) {
842
+ const isRunning = tasklist.runningTasks.has(id);
843
+ if (isRunning) {
844
+ throw new Error(`Task "${id}" in tasklist "${tasklistId}" is already running via completeTaskAsync()`);
845
+ }
846
+ const pendingDeps = (task.dependsOn ?? []).filter((dep) => {
847
+ const c = tasklist.completed.get(dep);
848
+ return !c || c.status !== "completed";
849
+ });
850
+ const readyTaskDetails = [...tasklist.readyTasks].map((readyId) => {
851
+ const readyTask = tasklist.plan.tasks.find((t) => t.id === readyId);
852
+ return { id: readyId, instructions: readyTask.instructions, outputSchema: readyTask.outputSchema };
853
+ });
854
+ config.onTaskOrderViolation?.(tasklistId, id, readyTaskDetails);
855
+ throw new Error(
856
+ `Task "${id}" in tasklist "${tasklistId}" is not ready. Waiting on: ${pendingDeps.join(", ")}`
857
+ );
858
+ }
859
+ for (const [key, schema] of Object.entries(task.outputSchema)) {
860
+ if (!(key in output)) {
861
+ throw new Error(`Task "${id}" output missing required key: ${key}`);
862
+ }
863
+ const expectedType = schema.type;
864
+ const value = output[key];
865
+ const actual = Array.isArray(value) ? "array" : typeof value;
866
+ if (actual !== expectedType) {
867
+ throw new Error(
868
+ `Task "${id}" output key "${key}": expected ${expectedType}, got ${actual}`
869
+ );
870
+ }
871
+ }
872
+ tasklist.completed.set(id, {
873
+ output,
874
+ timestamp: Date.now(),
875
+ status: "completed"
876
+ });
877
+ tasklist.readyTasks.delete(id);
878
+ tasklist.outputs.set(id, output);
879
+ recomputeReadyTasks(tasklist);
880
+ renderSurface.updateTasklistProgress?.(tasklistId, tasklist);
881
+ config.onTaskComplete?.(tasklistId, id, output);
882
+ const hasRemainingTasks = tasklist.plan.tasks.some((t) => {
883
+ const c = tasklist.completed.get(t.id);
884
+ return (!c || c.status !== "completed" && c.status !== "skipped") && !t.optional;
885
+ });
886
+ if (hasRemainingTasks && tasklist.readyTasks.size > 0) {
887
+ const readyTaskDetails = [...tasklist.readyTasks].map((readyId) => {
888
+ const readyTask = tasklist.plan.tasks.find((t) => t.id === readyId);
889
+ return { id: readyId, instructions: readyTask.instructions, outputSchema: readyTask.outputSchema };
890
+ });
891
+ config.onTaskCompleteContinue?.(tasklistId, id, readyTaskDetails);
892
+ }
893
+ }
894
+ function recomputeReadyTasks(tasklist) {
895
+ for (const task of tasklist.plan.tasks) {
896
+ if (tasklist.completed.has(task.id) || tasklist.readyTasks.has(task.id) || tasklist.runningTasks.has(task.id)) {
897
+ continue;
898
+ }
899
+ const deps = task.dependsOn ?? [];
900
+ const allDepsSatisfied = deps.every((dep) => {
901
+ const c = tasklist.completed.get(dep);
902
+ return c && (c.status === "completed" || c.status === "skipped" || c.status === "failed" && tasklist.plan.tasks.find((t) => t.id === dep)?.optional);
903
+ });
904
+ if (allDepsSatisfied) {
905
+ if (task.condition) {
906
+ const conditionMet = evaluateCondition(task.condition, tasklist.outputs);
907
+ if (!conditionMet) {
908
+ tasklist.completed.set(task.id, {
909
+ output: {},
910
+ timestamp: Date.now(),
911
+ status: "skipped"
912
+ });
913
+ config.onTaskSkipped?.(tasklist.plan.tasklistId, task.id, "condition was falsy");
914
+ recomputeReadyTasks(tasklist);
915
+ return;
916
+ }
917
+ }
918
+ tasklist.readyTasks.add(task.id);
919
+ }
920
+ }
921
+ }
922
+ function evaluateCondition(condition, outputs) {
923
+ try {
924
+ const ctx = Object.fromEntries(outputs);
925
+ const paramNames = Object.keys(ctx);
926
+ const paramValues = Object.values(ctx);
927
+ const fn = new Function(...paramNames, `return !!(${condition})`);
928
+ return fn(...paramValues);
929
+ } catch {
930
+ return false;
931
+ }
932
+ }
933
+ function completeTaskAsyncFn(tasklistId, taskId, fn) {
934
+ const tasklist = tasklistsState.tasklists.get(tasklistId);
935
+ if (!tasklist) {
936
+ throw new Error(`completeTaskAsync() called with unknown tasklist "${tasklistId}"`);
937
+ }
938
+ const task = tasklist.plan.tasks.find((t) => t.id === taskId);
939
+ if (!task) {
940
+ throw new Error(`Unknown task id: ${taskId} in tasklist "${tasklistId}"`);
941
+ }
942
+ if (tasklist.completed.has(taskId)) {
943
+ throw new Error(`Task "${taskId}" in tasklist "${tasklistId}" already completed`);
944
+ }
945
+ if (!tasklist.readyTasks.has(taskId)) {
946
+ const readyTaskDetails = [...tasklist.readyTasks].map((readyId) => {
947
+ const readyTask = tasklist.plan.tasks.find((t) => t.id === readyId);
948
+ return { id: readyId, instructions: readyTask.instructions, outputSchema: readyTask.outputSchema };
949
+ });
950
+ config.onTaskOrderViolation?.(tasklistId, taskId, readyTaskDetails);
951
+ throw new Error(`Task "${taskId}" in tasklist "${tasklistId}" is not ready`);
952
+ }
953
+ tasklist.readyTasks.delete(taskId);
954
+ tasklist.runningTasks.add(taskId);
955
+ config.onTaskAsyncStart?.(tasklistId, taskId);
956
+ const startTime = Date.now();
957
+ const promise = fn().then((output) => {
958
+ for (const [key, schema] of Object.entries(task.outputSchema)) {
959
+ if (!(key in output)) {
960
+ throw new Error(`Task "${taskId}" output missing required key: ${key}`);
961
+ }
962
+ const expectedType = schema.type;
963
+ const value = output[key];
964
+ const actual = Array.isArray(value) ? "array" : typeof value;
965
+ if (actual !== expectedType) {
966
+ throw new Error(
967
+ `Task "${taskId}" output key "${key}": expected ${expectedType}, got ${actual}`
968
+ );
969
+ }
970
+ }
971
+ tasklist.runningTasks.delete(taskId);
972
+ tasklist.completed.set(taskId, {
973
+ output,
974
+ timestamp: Date.now(),
975
+ status: "completed",
976
+ duration: Date.now() - startTime
977
+ });
978
+ tasklist.outputs.set(taskId, output);
979
+ asyncManager.setResult(`task:${taskId}`, output);
980
+ recomputeReadyTasks(tasklist);
981
+ renderSurface.updateTasklistProgress?.(tasklistId, tasklist);
982
+ config.onTaskAsyncComplete?.(tasklistId, taskId, output);
983
+ }).catch((err) => {
984
+ const error = err instanceof Error ? err.message : String(err);
985
+ tasklist.runningTasks.delete(taskId);
986
+ tasklist.completed.set(taskId, {
987
+ output: {},
988
+ timestamp: Date.now(),
989
+ status: "failed",
990
+ error,
991
+ duration: Date.now() - startTime
992
+ });
993
+ asyncManager.setResult(`task:${taskId}`, { error });
994
+ if (task.optional) {
995
+ recomputeReadyTasks(tasklist);
996
+ }
997
+ renderSurface.updateTasklistProgress?.(tasklistId, tasklist);
998
+ config.onTaskAsyncFailed?.(tasklistId, taskId, error);
999
+ });
1000
+ }
1001
+ function taskProgressFn(tasklistId, taskId, message, percent) {
1002
+ const tasklist = tasklistsState.tasklists.get(tasklistId);
1003
+ if (!tasklist) {
1004
+ throw new Error(`taskProgress() called with unknown tasklist "${tasklistId}"`);
1005
+ }
1006
+ const task = tasklist.plan.tasks.find((t) => t.id === taskId);
1007
+ if (!task) {
1008
+ throw new Error(`Unknown task id: ${taskId} in tasklist "${tasklistId}"`);
1009
+ }
1010
+ if (!tasklist.readyTasks.has(taskId) && !tasklist.runningTasks.has(taskId)) {
1011
+ throw new Error(`Task "${taskId}" in tasklist "${tasklistId}" is not in ready or running state`);
1012
+ }
1013
+ tasklist.progressMessages.set(taskId, { message, percent });
1014
+ renderSurface.updateTaskProgress?.(tasklistId, taskId, message, percent);
1015
+ config.onTaskProgress?.(tasklistId, taskId, message, percent);
1016
+ }
1017
+ function failTaskFn(tasklistId, taskId, error) {
1018
+ const tasklist = tasklistsState.tasklists.get(tasklistId);
1019
+ if (!tasklist) {
1020
+ throw new Error(`failTask() called with unknown tasklist "${tasklistId}"`);
1021
+ }
1022
+ const task = tasklist.plan.tasks.find((t) => t.id === taskId);
1023
+ if (!task) {
1024
+ throw new Error(`Unknown task id: ${taskId} in tasklist "${tasklistId}"`);
1025
+ }
1026
+ if (!tasklist.readyTasks.has(taskId) && !tasklist.runningTasks.has(taskId)) {
1027
+ throw new Error(`Task "${taskId}" in tasklist "${tasklistId}" is not in ready or running state`);
1028
+ }
1029
+ tasklist.readyTasks.delete(taskId);
1030
+ tasklist.runningTasks.delete(taskId);
1031
+ tasklist.completed.set(taskId, {
1032
+ output: {},
1033
+ timestamp: Date.now(),
1034
+ status: "failed",
1035
+ error
1036
+ });
1037
+ if (task.optional) {
1038
+ recomputeReadyTasks(tasklist);
1039
+ }
1040
+ renderSurface.updateTasklistProgress?.(tasklistId, tasklist);
1041
+ config.onTaskFailed?.(tasklistId, taskId, error);
1042
+ }
1043
+ function retryTaskFn(tasklistId, taskId) {
1044
+ const tasklist = tasklistsState.tasklists.get(tasklistId);
1045
+ if (!tasklist) {
1046
+ throw new Error(`retryTask() called with unknown tasklist "${tasklistId}"`);
1047
+ }
1048
+ const task = tasklist.plan.tasks.find((t) => t.id === taskId);
1049
+ if (!task) {
1050
+ throw new Error(`Unknown task id: ${taskId} in tasklist "${tasklistId}"`);
1051
+ }
1052
+ const completion = tasklist.completed.get(taskId);
1053
+ if (!completion || completion.status !== "failed") {
1054
+ throw new Error(`retryTask() can only retry failed tasks. Task "${taskId}" status: ${completion?.status ?? "not completed"}`);
1055
+ }
1056
+ const maxRetries = config.maxTaskRetries ?? 3;
1057
+ const currentRetries = tasklist.retryCount.get(taskId) ?? 0;
1058
+ if (currentRetries >= maxRetries) {
1059
+ throw new Error(`Task "${taskId}" has exceeded maximum retries (${maxRetries})`);
1060
+ }
1061
+ tasklist.retryCount.set(taskId, currentRetries + 1);
1062
+ tasklist.completed.delete(taskId);
1063
+ tasklist.outputs.delete(taskId);
1064
+ tasklist.readyTasks.add(taskId);
1065
+ tasklist.progressMessages.delete(taskId);
1066
+ renderSurface.updateTasklistProgress?.(tasklistId, tasklist);
1067
+ config.onTaskRetried?.(tasklistId, taskId);
1068
+ }
1069
+ async function sleepFn(seconds) {
1070
+ const maxSeconds = config.sleepMaxSeconds ?? 30;
1071
+ const capped = Math.min(Math.max(0, seconds), maxSeconds);
1072
+ await new Promise((resolve) => setTimeout(resolve, capped * 1e3));
1073
+ }
1074
+ function loadKnowledgeFn(selector) {
1075
+ if (!selector || typeof selector !== "object") {
1076
+ throw new Error("loadKnowledge() requires a selector object: { spaceName: { domain: { field: { option: true } } } }");
1077
+ }
1078
+ if (!config.onLoadKnowledge) {
1079
+ throw new Error("loadKnowledge() is not available \u2014 no space loaded");
1080
+ }
1081
+ return tagAsKnowledge(config.onLoadKnowledge(selector));
1082
+ }
1083
+ const loadedClasses = /* @__PURE__ */ new Set();
1084
+ function loadClassFn(className) {
1085
+ if (typeof className !== "string" || !className) {
1086
+ throw new Error("loadClass() requires a class name string");
1087
+ }
1088
+ if (loadedClasses.has(className)) return;
1089
+ if (!config.getClassInfo) {
1090
+ throw new Error("loadClass() is not available \u2014 no classes exported");
1091
+ }
1092
+ const result = config.getClassInfo(className);
1093
+ if (!result) {
1094
+ throw new Error(`Unknown class: "${className}"`);
1095
+ }
1096
+ loadedClasses.add(className);
1097
+ config.onLoadClass?.(className);
1098
+ }
1099
+ async function askParentFn(message, schema = {}) {
1100
+ if (typeof message !== "string" || !message) {
1101
+ throw new Error("askParent() requires a message string as first argument");
1102
+ }
1103
+ if (config.isFireAndForget || !config.onAskParent) {
1104
+ return { _noParent: true };
1105
+ }
1106
+ pauseController.pause();
1107
+ try {
1108
+ const result = await Promise.race([
1109
+ config.onAskParent({ message, schema }),
1110
+ new Promise(
1111
+ (resolve) => setTimeout(() => resolve({ _timeout: true }), askTimeout)
1112
+ )
1113
+ ]);
1114
+ return result;
1115
+ } finally {
1116
+ pauseController.resume();
1117
+ }
1118
+ }
1119
+ function respondFn(promise, data) {
1120
+ if (!config.onRespond) throw new Error("respond() is not available");
1121
+ if (!data || typeof data !== "object") {
1122
+ throw new Error("respond() requires a data object as second argument");
1123
+ }
1124
+ config.onRespond(promise, data);
1125
+ }
1126
+ return {
1127
+ stop: stopFn,
1128
+ display: displayFn,
1129
+ ask: askFn,
1130
+ async: asyncFn,
1131
+ tasklist: tasklistFn,
1132
+ completeTask: completeTaskFn,
1133
+ completeTaskAsync: completeTaskAsyncFn,
1134
+ taskProgress: taskProgressFn,
1135
+ failTask: failTaskFn,
1136
+ retryTask: retryTaskFn,
1137
+ sleep: sleepFn,
1138
+ loadKnowledge: loadKnowledgeFn,
1139
+ loadClass: loadClassFn,
1140
+ askParent: askParentFn,
1141
+ respond: respondFn,
1142
+ setCurrentSource,
1143
+ resolveStop,
1144
+ getTasklistsState: () => tasklistsState
1145
+ };
1146
+ }
1147
+ function deriveLabel(source) {
1148
+ const commentMatch = source.match(/\/\/\s*(.+)$/);
1149
+ if (commentMatch) return commentMatch[1].trim();
1150
+ const callMatch = source.match(/=>\s*(?:\{[^}]*)?(\w+)\s*\(/);
1151
+ if (callMatch) return callMatch[1];
1152
+ return "background task";
1153
+ }
1154
+
1155
+ // src/sandbox/async-manager.ts
1156
+ var AsyncManager = class {
1157
+ tasks = /* @__PURE__ */ new Map();
1158
+ results = /* @__PURE__ */ new Map();
1159
+ counter = 0;
1160
+ maxTasks;
1161
+ constructor(maxTasks = 10) {
1162
+ this.maxTasks = maxTasks;
1163
+ }
1164
+ /**
1165
+ * Register a new background task.
1166
+ */
1167
+ register(fn, label) {
1168
+ if (this.getRunningCount() >= this.maxTasks) {
1169
+ throw new Error(`Maximum async tasks reached (${this.maxTasks})`);
1170
+ }
1171
+ const id = `async_${this.counter++}`;
1172
+ const abortController = new AbortController();
1173
+ const startTime = Date.now();
1174
+ const promise = fn(abortController.signal).then(() => {
1175
+ const task = this.tasks.get(id);
1176
+ if (task && task.status === "running") {
1177
+ task.status = "completed";
1178
+ }
1179
+ }).catch((err) => {
1180
+ const task = this.tasks.get(id);
1181
+ if (task) {
1182
+ if (task.status === "running") {
1183
+ task.status = "failed";
1184
+ task.error = err instanceof Error ? err.message : String(err);
1185
+ }
1186
+ }
1187
+ });
1188
+ this.tasks.set(id, {
1189
+ id,
1190
+ label: label ?? id,
1191
+ abortController,
1192
+ promise,
1193
+ status: "running",
1194
+ startTime
1195
+ });
1196
+ return id;
1197
+ }
1198
+ /**
1199
+ * Cancel a task by ID.
1200
+ */
1201
+ cancel(taskId, message = "cancelled by user") {
1202
+ const task = this.tasks.get(taskId);
1203
+ if (!task || task.status !== "running") return false;
1204
+ task.abortController.abort(message);
1205
+ task.status = "cancelled";
1206
+ this.results.set(taskId, { cancelled: true, message });
1207
+ return true;
1208
+ }
1209
+ /**
1210
+ * Store a result from a task's scoped stop() call.
1211
+ */
1212
+ setResult(taskId, value) {
1213
+ this.results.set(taskId, value);
1214
+ }
1215
+ /**
1216
+ * Drain all accumulated results and clear the results map.
1217
+ */
1218
+ drainResults() {
1219
+ const drained = new Map(this.results);
1220
+ this.results.clear();
1221
+ return drained;
1222
+ }
1223
+ /**
1224
+ * Get a task by ID.
1225
+ */
1226
+ getTask(taskId) {
1227
+ return this.tasks.get(taskId);
1228
+ }
1229
+ /**
1230
+ * Get all tasks.
1231
+ */
1232
+ getAllTasks() {
1233
+ return [...this.tasks.values()];
1234
+ }
1235
+ /**
1236
+ * Get count of currently running tasks.
1237
+ */
1238
+ getRunningCount() {
1239
+ return [...this.tasks.values()].filter((t) => t.status === "running").length;
1240
+ }
1241
+ /**
1242
+ * Build the async portion of a stop payload.
1243
+ * Running tasks show "pending", completed ones show their results.
1244
+ */
1245
+ buildStopPayload() {
1246
+ const payload = {};
1247
+ const drained = this.drainResults();
1248
+ for (const [taskId, task] of this.tasks) {
1249
+ if (drained.has(taskId)) {
1250
+ payload[task.label] = drained.get(taskId);
1251
+ } else if (task.status === "running") {
1252
+ payload[task.label] = "pending";
1253
+ }
1254
+ }
1255
+ return payload;
1256
+ }
1257
+ /**
1258
+ * Wait for all running tasks to complete, with timeout.
1259
+ */
1260
+ async drain(timeoutMs = 5e3) {
1261
+ const running = [...this.tasks.values()].filter((t) => t.status === "running");
1262
+ if (running.length === 0) return;
1263
+ await Promise.race([
1264
+ Promise.allSettled(running.map((t) => t.promise)),
1265
+ new Promise((resolve) => setTimeout(resolve, timeoutMs))
1266
+ ]);
1267
+ }
1268
+ /**
1269
+ * Cancel all running tasks.
1270
+ */
1271
+ cancelAll() {
1272
+ for (const task of this.tasks.values()) {
1273
+ if (task.status === "running") {
1274
+ task.abortController.abort("session cleanup");
1275
+ task.status = "cancelled";
1276
+ }
1277
+ }
1278
+ }
1279
+ };
1280
+
1281
+ // src/stream/bracket-tracker.ts
1282
+ function createBracketState() {
1283
+ return {
1284
+ round: 0,
1285
+ curly: 0,
1286
+ square: 0,
1287
+ inString: false,
1288
+ inLineComment: false,
1289
+ inBlockComment: false,
1290
+ templateDepth: 0,
1291
+ jsxDepth: 0,
1292
+ jsxTagState: "none"
1293
+ };
1294
+ }
1295
+ function feedChunk(state, chunk) {
1296
+ for (let i = 0; i < chunk.length; i++) {
1297
+ const ch = chunk[i];
1298
+ const next = chunk[i + 1];
1299
+ if (state.inLineComment) {
1300
+ if (ch === "\n") state.inLineComment = false;
1301
+ continue;
1302
+ }
1303
+ if (state.inBlockComment) {
1304
+ if (ch === "*" && next === "/") {
1305
+ state.inBlockComment = false;
1306
+ i++;
1307
+ }
1308
+ continue;
1309
+ }
1310
+ if (state.inString) {
1311
+ if (ch === "\\") {
1312
+ i++;
1313
+ continue;
1314
+ }
1315
+ if (state.inString === "`") {
1316
+ if (ch === "`") {
1317
+ state.inString = false;
1318
+ }
1319
+ } else if (ch === state.inString) {
1320
+ state.inString = false;
1321
+ }
1322
+ continue;
1323
+ }
1324
+ if (ch === "/" && next === "/") {
1325
+ state.inLineComment = true;
1326
+ i++;
1327
+ continue;
1328
+ }
1329
+ if (ch === "/" && next === "*") {
1330
+ state.inBlockComment = true;
1331
+ i++;
1332
+ continue;
1333
+ }
1334
+ if (ch === "'" || ch === '"' || ch === "`") {
1335
+ state.inString = ch;
1336
+ continue;
1337
+ }
1338
+ if (state.jsxTagState === "pending_open") {
1339
+ if (/[a-zA-Z]/.test(ch)) {
1340
+ state.jsxDepth++;
1341
+ state.jsxTagState = "open";
1342
+ continue;
1343
+ } else if (ch === "/") {
1344
+ state.jsxTagState = "close";
1345
+ continue;
1346
+ } else if (ch === ">") {
1347
+ state.jsxDepth++;
1348
+ state.jsxTagState = "none";
1349
+ continue;
1350
+ } else {
1351
+ state.jsxTagState = "none";
1352
+ }
1353
+ }
1354
+ if (state.jsxTagState === "selfclose_pending") {
1355
+ if (ch === ">") {
1356
+ state.jsxDepth = Math.max(0, state.jsxDepth - 1);
1357
+ state.jsxTagState = "none";
1358
+ continue;
1359
+ }
1360
+ state.jsxTagState = "open";
1361
+ }
1362
+ if (state.jsxTagState === "open") {
1363
+ if (ch === "/") {
1364
+ state.jsxTagState = "selfclose_pending";
1365
+ continue;
1366
+ }
1367
+ if (ch === ">" && state.curly === 0 && state.round === 0 && state.square === 0) {
1368
+ state.jsxTagState = "none";
1369
+ continue;
1370
+ }
1371
+ }
1372
+ if (state.jsxTagState === "close") {
1373
+ if (ch === ">") {
1374
+ state.jsxDepth = Math.max(0, state.jsxDepth - 1);
1375
+ state.jsxTagState = "none";
1376
+ }
1377
+ continue;
1378
+ }
1379
+ if (ch === "<" && state.jsxTagState === "none") {
1380
+ state.jsxTagState = "pending_open";
1381
+ continue;
1382
+ }
1383
+ if (ch === "(") state.round++;
1384
+ else if (ch === ")") state.round = Math.max(0, state.round - 1);
1385
+ else if (ch === "{") state.curly++;
1386
+ else if (ch === "}") state.curly = Math.max(0, state.curly - 1);
1387
+ else if (ch === "[") state.square++;
1388
+ else if (ch === "]") state.square = Math.max(0, state.square - 1);
1389
+ }
1390
+ return state;
1391
+ }
1392
+ function isBalanced(state) {
1393
+ return state.round === 0 && state.curly === 0 && state.square === 0 && state.jsxDepth === 0 && state.jsxTagState === "none" && state.inString === false && !state.inBlockComment;
1394
+ }
1395
+ function resetBracketState(state) {
1396
+ state.round = 0;
1397
+ state.curly = 0;
1398
+ state.square = 0;
1399
+ state.inString = false;
1400
+ state.inLineComment = false;
1401
+ state.inBlockComment = false;
1402
+ state.templateDepth = 0;
1403
+ state.jsxDepth = 0;
1404
+ state.jsxTagState = "none";
1405
+ }
1406
+
1407
+ // src/parser/statement-detector.ts
1408
+ function isCompleteStatement(buffer) {
1409
+ const trimmed = buffer.trim();
1410
+ if (trimmed.length === 0) return false;
1411
+ let roundDepth = 0;
1412
+ let curlyDepth = 0;
1413
+ let squareDepth = 0;
1414
+ let jsxDepth = 0;
1415
+ let jsxTagState = "none";
1416
+ let inString = false;
1417
+ let inLineComment = false;
1418
+ let inBlockComment = false;
1419
+ let i = 0;
1420
+ while (i < trimmed.length) {
1421
+ const ch = trimmed[i];
1422
+ const next = trimmed[i + 1];
1423
+ if (inLineComment) {
1424
+ if (ch === "\n") inLineComment = false;
1425
+ i++;
1426
+ continue;
1427
+ }
1428
+ if (inBlockComment) {
1429
+ if (ch === "*" && next === "/") {
1430
+ inBlockComment = false;
1431
+ i += 2;
1432
+ continue;
1433
+ }
1434
+ i++;
1435
+ continue;
1436
+ }
1437
+ if (inString) {
1438
+ if (ch === "\\") {
1439
+ i += 2;
1440
+ continue;
1441
+ }
1442
+ if (inString === "`") {
1443
+ if (ch === "$" && next === "{") {
1444
+ }
1445
+ if (ch === "`") {
1446
+ inString = false;
1447
+ }
1448
+ } else if (ch === inString) {
1449
+ inString = false;
1450
+ }
1451
+ i++;
1452
+ continue;
1453
+ }
1454
+ if (ch === "/" && next === "/") {
1455
+ inLineComment = true;
1456
+ i += 2;
1457
+ continue;
1458
+ }
1459
+ if (ch === "/" && next === "*") {
1460
+ inBlockComment = true;
1461
+ i += 2;
1462
+ continue;
1463
+ }
1464
+ if (ch === "'" || ch === '"' || ch === "`") {
1465
+ inString = ch;
1466
+ i++;
1467
+ continue;
1468
+ }
1469
+ if (jsxTagState === "open" && ch === "/" && next === ">") {
1470
+ jsxDepth = Math.max(0, jsxDepth - 1);
1471
+ jsxTagState = "none";
1472
+ i += 2;
1473
+ continue;
1474
+ }
1475
+ if (jsxTagState === "open" && ch === ">" && curlyDepth === 0 && roundDepth === 0 && squareDepth === 0) {
1476
+ jsxTagState = "none";
1477
+ i++;
1478
+ continue;
1479
+ }
1480
+ if (jsxTagState === "close" && ch === ">") {
1481
+ jsxDepth = Math.max(0, jsxDepth - 1);
1482
+ jsxTagState = "none";
1483
+ i++;
1484
+ continue;
1485
+ }
1486
+ if (ch === "<" && jsxTagState === "none" && next && /[a-zA-Z]/.test(next)) {
1487
+ jsxDepth++;
1488
+ jsxTagState = "open";
1489
+ i += 2;
1490
+ continue;
1491
+ }
1492
+ if (ch === "<" && jsxTagState === "none" && next === "/") {
1493
+ jsxTagState = "close";
1494
+ i += 2;
1495
+ continue;
1496
+ }
1497
+ if (ch === "<" && jsxTagState === "none" && next === ">") {
1498
+ jsxDepth++;
1499
+ i += 2;
1500
+ continue;
1501
+ }
1502
+ if (ch === "(") roundDepth++;
1503
+ else if (ch === ")") roundDepth = Math.max(0, roundDepth - 1);
1504
+ else if (ch === "{") curlyDepth++;
1505
+ else if (ch === "}") curlyDepth = Math.max(0, curlyDepth - 1);
1506
+ else if (ch === "[") squareDepth++;
1507
+ else if (ch === "]") squareDepth = Math.max(0, squareDepth - 1);
1508
+ i++;
1509
+ }
1510
+ if (inString !== false || inBlockComment) return false;
1511
+ if (roundDepth !== 0 || curlyDepth !== 0 || squareDepth !== 0) return false;
1512
+ if (jsxDepth !== 0 || jsxTagState !== "none") return false;
1513
+ return true;
1514
+ }
1515
+
1516
+ // src/stream/line-accumulator.ts
1517
+ function createLineAccumulator() {
1518
+ return {
1519
+ buffer: "",
1520
+ bracketState: createBracketState()
1521
+ };
1522
+ }
1523
+ function feed(acc, token) {
1524
+ const statements = [];
1525
+ for (const char of token) {
1526
+ acc.buffer += char;
1527
+ feedChunk(acc.bracketState, char);
1528
+ if (char === "\n" && isBalanced(acc.bracketState)) {
1529
+ const trimmed = acc.buffer.trim();
1530
+ if (trimmed.length > 0 && isCompleteStatement(trimmed)) {
1531
+ statements.push(trimmed);
1532
+ acc.buffer = "";
1533
+ resetBracketState(acc.bracketState);
1534
+ }
1535
+ }
1536
+ }
1537
+ return {
1538
+ statements,
1539
+ hasRemaining: acc.buffer.trim().length > 0
1540
+ };
1541
+ }
1542
+ function flush(acc) {
1543
+ const trimmed = acc.buffer.trim();
1544
+ if (trimmed.length === 0) return null;
1545
+ acc.buffer = "";
1546
+ resetBracketState(acc.bracketState);
1547
+ return trimmed;
1548
+ }
1549
+ function clear(acc) {
1550
+ acc.buffer = "";
1551
+ resetBracketState(acc.bracketState);
1552
+ }
1553
+
1554
+ // src/hooks/hook-executor.ts
1555
+ import ts4 from "typescript";
1556
+
1557
+ // src/hooks/pattern-matcher.ts
1558
+ import ts3 from "typescript";
1559
+ function matchPattern(node, pattern, sourceFile) {
1560
+ const captures = {};
1561
+ if ("oneOf" in pattern) {
1562
+ const p = pattern;
1563
+ for (const sub of p.oneOf) {
1564
+ const match = matchPattern(node, sub, sourceFile);
1565
+ if (match) return match;
1566
+ }
1567
+ return null;
1568
+ }
1569
+ if ("not" in pattern && "type" in pattern) {
1570
+ const p = pattern;
1571
+ if (!matchNodeType(node, p.type)) return null;
1572
+ const negMatch = matchPatternProperties(node, p.not, sourceFile, captures);
1573
+ if (negMatch) return null;
1574
+ return { node, source: node.getText(sourceFile), captures };
1575
+ }
1576
+ if ("type" in pattern) {
1577
+ const p = pattern;
1578
+ if (!matchNodeType(node, p.type)) return null;
1579
+ if (!matchPatternProperties(node, p, sourceFile, captures)) return null;
1580
+ return { node, source: node.getText(sourceFile), captures };
1581
+ }
1582
+ return null;
1583
+ }
1584
+ function matchNodeType(node, type) {
1585
+ if (type === "*") return true;
1586
+ const syntaxKind = ts3.SyntaxKind[node.kind];
1587
+ return String(syntaxKind) === type;
1588
+ }
1589
+ function matchPatternProperties(node, pattern, sourceFile, captures) {
1590
+ for (const [key, expectedValue] of Object.entries(pattern)) {
1591
+ if (key === "type" || key === "oneOf" || key === "not") continue;
1592
+ const actualValue = node[key];
1593
+ if (actualValue === void 0) return false;
1594
+ if (typeof expectedValue === "object" && expectedValue !== null) {
1595
+ if (actualValue && typeof actualValue === "object" && "kind" in actualValue) {
1596
+ if (!matchPatternProperties(actualValue, expectedValue, sourceFile, captures)) {
1597
+ return false;
1598
+ }
1599
+ } else {
1600
+ return false;
1601
+ }
1602
+ } else if (typeof expectedValue === "string") {
1603
+ if (expectedValue.startsWith("$")) {
1604
+ const captureName = expectedValue.slice(1);
1605
+ if (actualValue && typeof actualValue === "object" && "kind" in actualValue) {
1606
+ captures[captureName] = actualValue.getText(sourceFile);
1607
+ } else {
1608
+ captures[captureName] = actualValue;
1609
+ }
1610
+ } else {
1611
+ if (actualValue && typeof actualValue === "object" && "kind" in actualValue) {
1612
+ if (actualValue.getText(sourceFile) !== expectedValue) return false;
1613
+ } else if (String(actualValue) !== expectedValue) {
1614
+ return false;
1615
+ }
1616
+ }
1617
+ } else {
1618
+ if (actualValue !== expectedValue) return false;
1619
+ }
1620
+ }
1621
+ return true;
1622
+ }
1623
+ function findMatches(sourceFile, pattern) {
1624
+ const matches = [];
1625
+ function visit(node) {
1626
+ const match = matchPattern(node, pattern, sourceFile);
1627
+ if (match) matches.push(match);
1628
+ ts3.forEachChild(node, visit);
1629
+ }
1630
+ visit(sourceFile);
1631
+ return matches;
1632
+ }
1633
+
1634
+ // src/hooks/hook-executor.ts
1635
+ async function executeHooks(source, phase, registry, context) {
1636
+ const hooks = registry.listByPhase(phase);
1637
+ const result = {
1638
+ action: "execute",
1639
+ source,
1640
+ sideEffects: [],
1641
+ matchedHooks: []
1642
+ };
1643
+ if (hooks.length === 0) return result;
1644
+ const sourceFile = ts4.createSourceFile(
1645
+ "hook.ts",
1646
+ source,
1647
+ ts4.ScriptTarget.ESNext,
1648
+ true,
1649
+ ts4.ScriptKind.TSX
1650
+ );
1651
+ for (const hook of hooks) {
1652
+ const matches = findMatches(sourceFile, hook.pattern);
1653
+ if (matches.length === 0) continue;
1654
+ for (const match of matches) {
1655
+ let action;
1656
+ try {
1657
+ action = await hook.handler(match, context);
1658
+ registry.recordSuccess(hook.id);
1659
+ } catch (err) {
1660
+ registry.recordFailure(hook.id);
1661
+ continue;
1662
+ }
1663
+ result.matchedHooks.push({ hookId: hook.id, action: action.type });
1664
+ switch (action.type) {
1665
+ case "continue":
1666
+ break;
1667
+ case "side_effect":
1668
+ result.sideEffects.push(action.fn);
1669
+ break;
1670
+ case "transform":
1671
+ if (phase === "before") {
1672
+ result.source = action.newSource;
1673
+ }
1674
+ break;
1675
+ case "interrupt":
1676
+ if (phase === "before") {
1677
+ result.action = "interrupt";
1678
+ result.interruptMessage = action.message;
1679
+ return result;
1680
+ }
1681
+ break;
1682
+ case "skip":
1683
+ if (phase === "before") {
1684
+ result.action = "skip";
1685
+ return result;
1686
+ }
1687
+ break;
1688
+ }
1689
+ }
1690
+ }
1691
+ return result;
1692
+ }
1693
+
1694
+ // src/stream/stream-controller.ts
1695
+ var StreamController = class {
1696
+ accumulator = createLineAccumulator();
1697
+ paused = false;
1698
+ pauseResolve = null;
1699
+ options;
1700
+ lineCount = 0;
1701
+ currentBlockId = "";
1702
+ constructor(options) {
1703
+ this.options = options;
1704
+ }
1705
+ /**
1706
+ * Feed tokens from the LLM stream.
1707
+ */
1708
+ async feedToken(token) {
1709
+ if (this.paused) {
1710
+ await this.waitForResume();
1711
+ }
1712
+ const { statements } = feed(this.accumulator, token);
1713
+ if (token.length > 0) {
1714
+ this.options.onEvent({
1715
+ type: "code",
1716
+ lines: token,
1717
+ blockId: this.currentBlockId || this.newBlockId()
1718
+ });
1719
+ }
1720
+ for (const statement of statements) {
1721
+ await this.processStatement(statement);
1722
+ if (this.paused) {
1723
+ await this.waitForResume();
1724
+ }
1725
+ }
1726
+ }
1727
+ /**
1728
+ * Called when the LLM stream ends. Flush remaining buffer.
1729
+ */
1730
+ async finalize() {
1731
+ const remaining = flush(this.accumulator);
1732
+ if (remaining) {
1733
+ await this.processStatement(remaining);
1734
+ }
1735
+ }
1736
+ async processStatement(source) {
1737
+ this.lineCount++;
1738
+ const ctx = this.options.hookContext();
1739
+ const hookResult = await executeHooks(
1740
+ source,
1741
+ "before",
1742
+ this.options.hookRegistry,
1743
+ ctx
1744
+ );
1745
+ for (const fn of hookResult.sideEffects) {
1746
+ try {
1747
+ await fn();
1748
+ } catch {
1749
+ }
1750
+ }
1751
+ for (const match of hookResult.matchedHooks) {
1752
+ this.options.onEvent({
1753
+ type: "hook",
1754
+ hookId: match.hookId,
1755
+ action: match.action,
1756
+ detail: source,
1757
+ blockId: this.currentBlockId
1758
+ });
1759
+ }
1760
+ if (hookResult.action === "skip") {
1761
+ return;
1762
+ }
1763
+ if (hookResult.action === "interrupt") {
1764
+ this.options.onEvent({
1765
+ type: "hook",
1766
+ hookId: hookResult.matchedHooks[hookResult.matchedHooks.length - 1]?.hookId ?? "unknown",
1767
+ action: "interrupt",
1768
+ detail: hookResult.interruptMessage ?? "",
1769
+ blockId: this.currentBlockId
1770
+ });
1771
+ return;
1772
+ }
1773
+ const finalSource = hookResult.source;
1774
+ this.options.onCodeLine(finalSource);
1775
+ const result = await this.options.onStatement(finalSource);
1776
+ await executeHooks(finalSource, "after", this.options.hookRegistry, ctx);
1777
+ if (!result.ok && result.error) {
1778
+ this.options.onError(result.error);
1779
+ this.options.onEvent({
1780
+ type: "error",
1781
+ error: result.error,
1782
+ blockId: this.currentBlockId
1783
+ });
1784
+ }
1785
+ }
1786
+ // ── StreamPauseController interface ──
1787
+ pause() {
1788
+ this.paused = true;
1789
+ }
1790
+ resume() {
1791
+ this.paused = false;
1792
+ if (this.pauseResolve) {
1793
+ const resolve = this.pauseResolve;
1794
+ this.pauseResolve = null;
1795
+ resolve();
1796
+ }
1797
+ }
1798
+ isPaused() {
1799
+ return this.paused;
1800
+ }
1801
+ waitForResume() {
1802
+ if (!this.paused) return Promise.resolve();
1803
+ return new Promise((resolve) => {
1804
+ this.pauseResolve = resolve;
1805
+ });
1806
+ }
1807
+ /**
1808
+ * Clear the line accumulator (e.g., on intervention).
1809
+ */
1810
+ clearBuffer() {
1811
+ clear(this.accumulator);
1812
+ }
1813
+ /**
1814
+ * Set the current block ID for events.
1815
+ */
1816
+ setBlockId(id) {
1817
+ this.currentBlockId = id;
1818
+ }
1819
+ newBlockId() {
1820
+ this.currentBlockId = `block_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
1821
+ return this.currentBlockId;
1822
+ }
1823
+ };
1824
+
1825
+ // src/hooks/hook-registry.ts
1826
+ var HookRegistry = class {
1827
+ hooks = /* @__PURE__ */ new Map();
1828
+ failureCounts = /* @__PURE__ */ new Map();
1829
+ disabledHooks = /* @__PURE__ */ new Set();
1830
+ maxConsecutiveFailures;
1831
+ constructor(maxConsecutiveFailures = 3) {
1832
+ this.maxConsecutiveFailures = maxConsecutiveFailures;
1833
+ }
1834
+ register(hook) {
1835
+ this.hooks.set(hook.id, hook);
1836
+ this.failureCounts.set(hook.id, 0);
1837
+ }
1838
+ unregister(id) {
1839
+ this.failureCounts.delete(id);
1840
+ this.disabledHooks.delete(id);
1841
+ return this.hooks.delete(id);
1842
+ }
1843
+ get(id) {
1844
+ return this.hooks.get(id);
1845
+ }
1846
+ /**
1847
+ * List hooks by phase, excluding disabled hooks.
1848
+ */
1849
+ listByPhase(phase) {
1850
+ return [...this.hooks.values()].filter(
1851
+ (h) => h.phase === phase && !this.disabledHooks.has(h.id)
1852
+ );
1853
+ }
1854
+ /**
1855
+ * Record a failure for a hook. After maxConsecutiveFailures, disable it.
1856
+ */
1857
+ recordFailure(id) {
1858
+ const count = (this.failureCounts.get(id) ?? 0) + 1;
1859
+ this.failureCounts.set(id, count);
1860
+ if (count >= this.maxConsecutiveFailures) {
1861
+ this.disabledHooks.add(id);
1862
+ }
1863
+ }
1864
+ /**
1865
+ * Record a success for a hook (resets failure count).
1866
+ */
1867
+ recordSuccess(id) {
1868
+ this.failureCounts.set(id, 0);
1869
+ }
1870
+ isDisabled(id) {
1871
+ return this.disabledHooks.has(id);
1872
+ }
1873
+ getAll() {
1874
+ return [...this.hooks.values()];
1875
+ }
1876
+ clear() {
1877
+ this.hooks.clear();
1878
+ this.failureCounts.clear();
1879
+ this.disabledHooks.clear();
1880
+ }
1881
+ };
1882
+
1883
+ // src/context/scope-generator.ts
1884
+ function generateScopeTable(entries, options = {}) {
1885
+ const { maxVariables = 50, maxValueWidth = 50 } = options;
1886
+ const visible = entries.slice(0, maxVariables);
1887
+ if (visible.length === 0) {
1888
+ return "(no variables declared)";
1889
+ }
1890
+ const nameCol = Math.max(4, ...visible.map((e) => e.name.length));
1891
+ const typeCol = Math.max(4, ...visible.map((e) => e.type.length));
1892
+ const header = `${"Name".padEnd(nameCol)} ${"Type".padEnd(typeCol)} Value`;
1893
+ const separator = `${"-".repeat(nameCol)} ${"-".repeat(typeCol)} ${"-".repeat(maxValueWidth)}`;
1894
+ const rows = visible.map((e) => {
1895
+ const truncatedValue = e.value.length > maxValueWidth ? e.value.slice(0, maxValueWidth - 3) + "..." : e.value;
1896
+ return `${e.name.padEnd(nameCol)} ${e.type.padEnd(typeCol)} ${truncatedValue}`;
1897
+ });
1898
+ const lines = [header, separator, ...rows];
1899
+ if (entries.length > maxVariables) {
1900
+ lines.push(`... +${entries.length - maxVariables} more variables`);
1901
+ }
1902
+ return lines.join("\n");
1903
+ }
1904
+ function describeType2(val) {
1905
+ if (val === null) return "null";
1906
+ if (val === void 0) return "undefined";
1907
+ if (Array.isArray(val)) {
1908
+ if (val.length === 0) return "Array";
1909
+ return `Array<${describeType2(val[0])}>`;
1910
+ }
1911
+ const t = typeof val;
1912
+ if (t === "object") {
1913
+ const name = val.constructor?.name;
1914
+ return name && name !== "Object" ? name : "Object";
1915
+ }
1916
+ return t;
1917
+ }
1918
+ function truncateValue2(val, maxLen = 50) {
1919
+ if (val === null) return "null";
1920
+ if (val === void 0) return "undefined";
1921
+ if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
1922
+ if (typeof val === "symbol") return val.toString();
1923
+ try {
1924
+ let str;
1925
+ if (typeof val === "string") {
1926
+ str = JSON.stringify(val);
1927
+ } else if (Array.isArray(val)) {
1928
+ const preview = val.slice(0, 3).map((v) => truncateValue2(v, 20)).join(", ");
1929
+ str = val.length > 3 ? `[${preview}, ... +${val.length - 3}]` : `[${preview}]`;
1930
+ } else if (typeof val === "object") {
1931
+ const keys = Object.keys(val);
1932
+ const preview = keys.slice(0, 5).join(", ");
1933
+ str = keys.length > 5 ? `{${preview}, ... +${keys.length - 5}}` : `{${preview}}`;
1934
+ } else {
1935
+ str = String(val);
1936
+ }
1937
+ if (str.length > maxLen) {
1938
+ return str.slice(0, maxLen - 3) + "...";
1939
+ }
1940
+ return str;
1941
+ } catch {
1942
+ return "[value]";
1943
+ }
1944
+ }
1945
+
1946
+ // src/context/message-builder.ts
1947
+ function buildStopMessage(payload) {
1948
+ const entries = Object.entries(payload).map(([key, sv]) => `${key}: ${sv.display}`).join(", ");
1949
+ return `\u2190 stop { ${entries} }`;
1950
+ }
1951
+ function buildErrorMessage(error) {
1952
+ return `\u2190 error [${error.type}] ${error.message} (line ${error.line})`;
1953
+ }
1954
+ function buildInterventionMessage(text) {
1955
+ return text;
1956
+ }
1957
+ function buildHookInterruptMessage(hookId, message) {
1958
+ return `\u26A0 [hook:${hookId}] ${message}`;
1959
+ }
1960
+ function buildTasklistReminderMessage(tasklistId, ready, blocked, failed) {
1961
+ let msg = `\u26A0 [system] Tasklist "${tasklistId}" incomplete.`;
1962
+ if (ready.length > 0) msg += ` Ready: ${ready.join(", ")}.`;
1963
+ if (blocked.length > 0) msg += ` Blocked: ${blocked.join(", ")}.`;
1964
+ if (failed.length > 0) msg += ` Failed: ${failed.join(", ")}.`;
1965
+ msg += " Continue with a ready task.";
1966
+ return msg;
1967
+ }
1968
+ function renderTaskLine(task, state) {
1969
+ const completion = state.completed.get(task.id);
1970
+ if (completion?.status === "completed") {
1971
+ const outputStr = JSON.stringify(completion.output);
1972
+ const truncated = outputStr.length > 40 ? outputStr.slice(0, 37) + "..." : outputStr;
1973
+ return { symbol: "\u2713", detail: `\u2192 ${truncated}` };
1974
+ }
1975
+ if (completion?.status === "failed") {
1976
+ return { symbol: "\u2717", detail: `\u2014 ${completion.error ?? "unknown error"}` };
1977
+ }
1978
+ if (completion?.status === "skipped") {
1979
+ return { symbol: "\u2298", detail: "(skipped \u2014 condition was falsy)" };
1980
+ }
1981
+ if (state.runningTasks.has(task.id)) {
1982
+ const progress = state.progressMessages?.get(task.id);
1983
+ const detail = progress ? `(running \u2014 ${progress.percent != null ? progress.percent + "% " : ""}${progress.message})` : "(running)";
1984
+ return { symbol: "\u25C9", detail };
1985
+ }
1986
+ if (state.readyTasks.has(task.id)) {
1987
+ return { symbol: "\u25CE", detail: "(ready \u2014 deps satisfied)" };
1988
+ }
1989
+ const deps = task.dependsOn?.join(", ") ?? "";
1990
+ return { symbol: "\u25CB", detail: deps ? `(blocked \u2014 waiting on: ${deps})` : "(pending)" };
1991
+ }
1992
+ function buildTaskContinueMessage(tasklistId, completedTaskId, readyTasks, tasklistsState) {
1993
+ let msg = `\u2190 completeTask \u2713 ${tasklistId}/${completedTaskId}`;
1994
+ if (readyTasks.length > 0) {
1995
+ msg += `
1996
+
1997
+ Next task:`;
1998
+ const next = readyTasks[0];
1999
+ msg += `
2000
+ Task: ${next.id}`;
2001
+ msg += `
2002
+ Instructions: ${next.instructions}`;
2003
+ const schemaStr = Object.entries(next.outputSchema).map(([k, v]) => `${k}: ${v.type}`).join(", ");
2004
+ msg += `
2005
+ Expected output: { ${schemaStr} }`;
2006
+ if (readyTasks.length > 1) {
2007
+ msg += `
2008
+
2009
+ Also ready: ${readyTasks.slice(1).map((t) => t.id).join(", ")}`;
2010
+ }
2011
+ }
2012
+ const tasksBlock = generateTasksBlock(tasklistsState);
2013
+ if (tasksBlock) msg += `
2014
+
2015
+ ${tasksBlock}`;
2016
+ return msg;
2017
+ }
2018
+ function buildTaskOrderViolationMessage(tasklistId, attemptedTaskId, readyTasks, tasklistsState) {
2019
+ let msg = `\u26A0 [system] Task order violation in tasklist "${tasklistId}": tried to complete "${attemptedTaskId}" but it is not ready.`;
2020
+ if (readyTasks.length > 0) {
2021
+ msg += `
2022
+
2023
+ Next task to complete:`;
2024
+ const next = readyTasks[0];
2025
+ msg += `
2026
+ Task: ${next.id}`;
2027
+ msg += `
2028
+ Instructions: ${next.instructions}`;
2029
+ const schemaStr = Object.entries(next.outputSchema).map(([k, v]) => `${k}: ${v.type}`).join(", ");
2030
+ msg += `
2031
+ Expected output: { ${schemaStr} }`;
2032
+ if (readyTasks.length > 1) {
2033
+ msg += `
2034
+
2035
+ Also ready: ${readyTasks.slice(1).map((t) => t.id).join(", ")}`;
2036
+ }
2037
+ }
2038
+ msg += "\n\nWork on the ready task above, then call completeTask() when done.";
2039
+ const tasksBlock = generateTasksBlock(tasklistsState);
2040
+ if (tasksBlock) msg += `
2041
+
2042
+ ${tasksBlock}`;
2043
+ return msg;
2044
+ }
2045
+ function generateCurrentTaskBlock(tasklistsState) {
2046
+ if (tasklistsState.tasklists.size === 0) return null;
2047
+ const lines = [];
2048
+ for (const [tasklistId, state] of tasklistsState.tasklists) {
2049
+ const readyIds = [...state.readyTasks];
2050
+ if (readyIds.length === 0) continue;
2051
+ const next = state.plan.tasks.find((t) => t.id === readyIds[0]);
2052
+ if (!next) continue;
2053
+ lines.push(`{{CURRENT_TASK}}`);
2054
+ lines.push(`Tasklist: ${tasklistId}`);
2055
+ lines.push(`Task: ${next.id}`);
2056
+ lines.push(`Instructions: ${next.instructions}`);
2057
+ const schemaStr = Object.entries(next.outputSchema).map(([k, v]) => `${k}: ${v.type}`).join(", ");
2058
+ lines.push(`Expected output: { ${schemaStr} }`);
2059
+ }
2060
+ return lines.length > 0 ? lines.join("\n") : null;
2061
+ }
2062
+ function generateTasksBlock(tasklistsState) {
2063
+ if (tasklistsState.tasklists.size === 0) return null;
2064
+ const lines = ["{{TASKS}}"];
2065
+ for (const [tasklistId, state] of tasklistsState.tasklists) {
2066
+ const width = Math.max(1, 60 - tasklistId.length - 3);
2067
+ lines.push(`\u250C ${tasklistId} ${"\u2500".repeat(width)}\u2510`);
2068
+ for (const task of state.plan.tasks) {
2069
+ const { symbol, detail } = renderTaskLine(task, state);
2070
+ lines.push(`\u2502 ${symbol} ${task.id.padEnd(18)} ${detail.padEnd(40)}\u2502`);
2071
+ }
2072
+ lines.push(`\u2514${"\u2500".repeat(63)}\u2518`);
2073
+ }
2074
+ return lines.join("\n");
2075
+ }
2076
+
2077
+ // src/sandbox/agent-registry.ts
2078
+ var AgentRegistry = class {
2079
+ entries = /* @__PURE__ */ new Map();
2080
+ questionResolvers = /* @__PURE__ */ new Map();
2081
+ currentTurn = 0;
2082
+ config;
2083
+ constructor(config = {}) {
2084
+ this.config = config;
2085
+ }
2086
+ register(varName, promise, label, childSession) {
2087
+ const entry = {
2088
+ varName,
2089
+ label,
2090
+ status: "running",
2091
+ promise,
2092
+ childSession,
2093
+ registeredAt: Date.now(),
2094
+ registeredTurn: this.currentTurn,
2095
+ pendingQuestion: null
2096
+ };
2097
+ this.entries.set(varName, entry);
2098
+ promise.then(
2099
+ (value) => this.resolve(varName, value),
2100
+ (err) => {
2101
+ const error = err instanceof Error ? err.message : String(err);
2102
+ this.fail(varName, error);
2103
+ }
2104
+ );
2105
+ this.config.onRegistered?.(varName, label);
2106
+ }
2107
+ resolve(varName, value) {
2108
+ const entry = this.entries.get(varName);
2109
+ if (!entry) return;
2110
+ entry.status = "resolved";
2111
+ entry.resolvedValue = value;
2112
+ entry.completedAt = Date.now();
2113
+ this.config.onResolved?.(varName);
2114
+ }
2115
+ fail(varName, error) {
2116
+ const entry = this.entries.get(varName);
2117
+ if (!entry) return;
2118
+ entry.status = "failed";
2119
+ entry.error = error;
2120
+ entry.completedAt = Date.now();
2121
+ this.config.onFailed?.(varName, error);
2122
+ }
2123
+ getAll() {
2124
+ return [...this.entries.values()];
2125
+ }
2126
+ getPending() {
2127
+ return [...this.entries.values()].filter(
2128
+ (e) => e.status === "running" || e.status === "waiting"
2129
+ );
2130
+ }
2131
+ getSnapshot(varName) {
2132
+ const entry = this.entries.get(varName);
2133
+ if (!entry) return null;
2134
+ let tasklistsState = null;
2135
+ if (entry.childSession) {
2136
+ try {
2137
+ tasklistsState = entry.childSession.snapshot().tasklistsState;
2138
+ } catch {
2139
+ }
2140
+ }
2141
+ return {
2142
+ varName: entry.varName,
2143
+ label: entry.label,
2144
+ status: entry.status,
2145
+ tasklistsState,
2146
+ pendingQuestion: entry.pendingQuestion ?? null,
2147
+ error: entry.error
2148
+ };
2149
+ }
2150
+ getAllSnapshots() {
2151
+ return [...this.entries.keys()].map((varName) => this.getSnapshot(varName)).filter(Boolean);
2152
+ }
2153
+ findByPromise(promise) {
2154
+ for (const entry of this.entries.values()) {
2155
+ if (entry.promise === promise) return entry;
2156
+ }
2157
+ return null;
2158
+ }
2159
+ advanceTurn() {
2160
+ this.currentTurn++;
2161
+ }
2162
+ getCurrentTurn() {
2163
+ return this.currentTurn;
2164
+ }
2165
+ hasEntries() {
2166
+ return this.entries.size > 0;
2167
+ }
2168
+ hasVisibleEntries() {
2169
+ for (const entry of this.entries.values()) {
2170
+ if (entry.status === "running" || entry.status === "waiting") return true;
2171
+ if (entry.completedAt != null) {
2172
+ const turnsSinceCompletion = this.currentTurn - entry.registeredTurn;
2173
+ if (turnsSinceCompletion <= 5) return true;
2174
+ }
2175
+ }
2176
+ return false;
2177
+ }
2178
+ /**
2179
+ * Low-level setter — updates entry status and question fields.
2180
+ * Prefer askQuestion() for the full flow (sets question + returns Promise).
2181
+ */
2182
+ setPendingQuestion(varName, question) {
2183
+ const entry = this.entries.get(varName);
2184
+ if (!entry) throw new Error(`setPendingQuestion: unknown agent "${varName}"`);
2185
+ entry.pendingQuestion = question;
2186
+ entry.status = "waiting";
2187
+ }
2188
+ /**
2189
+ * Ask a question on behalf of a child agent. Sets status to 'waiting',
2190
+ * stores the question, and returns a Promise that resolves when the
2191
+ * parent calls respond().
2192
+ */
2193
+ askQuestion(varName, question) {
2194
+ const entry = this.entries.get(varName);
2195
+ if (!entry) throw new Error(`askQuestion: unknown agent "${varName}"`);
2196
+ entry.pendingQuestion = question;
2197
+ entry.status = "waiting";
2198
+ this.config.onQuestionAsked?.(varName, question);
2199
+ return new Promise((resolve) => {
2200
+ this.questionResolvers.set(varName, resolve);
2201
+ });
2202
+ }
2203
+ /**
2204
+ * Deliver structured input to a child agent's pending askParent() call.
2205
+ * Resolves the Promise returned by askQuestion(), clears the pending
2206
+ * question, and sets the agent back to 'running'.
2207
+ */
2208
+ respond(varName, data) {
2209
+ const entry = this.entries.get(varName);
2210
+ if (!entry) throw new Error(`respond: unknown agent "${varName}"`);
2211
+ if (entry.status !== "waiting") {
2212
+ throw new Error(`respond: agent "${varName}" is not waiting for input (status: ${entry.status})`);
2213
+ }
2214
+ const resolver = this.questionResolvers.get(varName);
2215
+ if (!resolver) throw new Error(`respond: no pending question for agent "${varName}"`);
2216
+ entry.pendingQuestion = null;
2217
+ entry.status = "running";
2218
+ this.questionResolvers.delete(varName);
2219
+ resolver(data);
2220
+ this.config.onQuestionAnswered?.(varName);
2221
+ }
2222
+ destroy() {
2223
+ this.entries.clear();
2224
+ this.questionResolvers.clear();
2225
+ }
2226
+ };
2227
+
2228
+ // src/context/agents-block.ts
2229
+ function generateAgentsBlock(registry, resolvedInThisStop) {
2230
+ if (!registry.hasVisibleEntries()) return null;
2231
+ const currentTurn = registry.getCurrentTurn();
2232
+ const lines = ["{{AGENTS}}"];
2233
+ for (const entry of registry.getAll()) {
2234
+ const turnsSinceRegistered = currentTurn - entry.registeredTurn;
2235
+ const completedTurnDistance = entry.completedAt != null ? currentTurn - entry.registeredTurn : 0;
2236
+ if ((entry.status === "resolved" || entry.status === "failed") && completedTurnDistance >= 6) {
2237
+ continue;
2238
+ }
2239
+ const width = Math.max(1, 60 - entry.varName.length - entry.label.length - 5);
2240
+ lines.push(`\u250C ${entry.varName} \u2014 ${entry.label} ${"\u2500".repeat(width)}\u2510`);
2241
+ const isCompact = (entry.status === "resolved" || entry.status === "failed") && completedTurnDistance >= 3;
2242
+ if (entry.status === "running") {
2243
+ lines.push(`\u2502 \u25C9 running${" ".repeat(52)}\u2502`);
2244
+ const snapshot = registry.getSnapshot(entry.varName);
2245
+ if (snapshot?.tasklistsState && snapshot.tasklistsState.tasklists.size > 0) {
2246
+ for (const [tlId, tlState] of snapshot.tasklistsState.tasklists) {
2247
+ const tlWidth = Math.max(1, 56 - tlId.length - 3);
2248
+ lines.push(`\u2502 \u250C tasks ${"\u2500".repeat(tlWidth)}\u2510 \u2502`);
2249
+ for (const task of tlState.plan.tasks) {
2250
+ const { symbol, detail } = renderTaskLine(task, tlState);
2251
+ lines.push(`\u2502 \u2502 ${symbol} ${task.id.padEnd(16)} ${detail.padEnd(36)}\u2502 \u2502`);
2252
+ }
2253
+ lines.push(`\u2502 \u2514${"\u2500".repeat(Math.max(1, 57))}\u2518 \u2502`);
2254
+ }
2255
+ } else {
2256
+ lines.push(`\u2502 (no tasklist)${" ".repeat(48)}\u2502`);
2257
+ }
2258
+ } else if (entry.status === "waiting") {
2259
+ lines.push(`\u2502 ? waiting \u2014 needs input from parent${" ".repeat(26)}\u2502`);
2260
+ if (entry.pendingQuestion && !isCompact) {
2261
+ lines.push(`\u2502 \u250C question \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502`);
2262
+ const msg = entry.pendingQuestion.message.slice(0, 50);
2263
+ lines.push(`\u2502 \u2502 "${msg}"${" ".repeat(Math.max(1, 51 - msg.length))}\u2502 \u2502`);
2264
+ const schemaEntries = Object.entries(entry.pendingQuestion.schema);
2265
+ if (schemaEntries.length > 0) {
2266
+ lines.push(`\u2502 \u2502 schema: {${" ".repeat(43)}\u2502 \u2502`);
2267
+ for (const [key, val] of schemaEntries.slice(0, 5)) {
2268
+ const typeStr = formatSchemaValue(val);
2269
+ lines.push(`\u2502 \u2502 ${key}: ${typeStr}`.padEnd(56) + "\u2502 \u2502");
2270
+ }
2271
+ if (schemaEntries.length > 5) {
2272
+ lines.push(`\u2502 \u2502 ... +${schemaEntries.length - 5} more`.padEnd(56) + "\u2502 \u2502");
2273
+ }
2274
+ lines.push(`\u2502 \u2502 }`.padEnd(56) + "\u2502 \u2502");
2275
+ }
2276
+ lines.push(`\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502`);
2277
+ }
2278
+ } else if (entry.status === "resolved") {
2279
+ if (isCompact) {
2280
+ lines.push(`\u2502 \u2713 resolved${" ".repeat(51)}\u2502`);
2281
+ } else if (resolvedInThisStop.has(entry.varName)) {
2282
+ lines.push(`\u2502 \u2713 (value included in this stop payload)${" ".repeat(22)}\u2502`);
2283
+ } else {
2284
+ lines.push(`\u2502 \u2713 resolved${" ".repeat(51)}\u2502`);
2285
+ }
2286
+ } else if (entry.status === "failed") {
2287
+ if (isCompact) {
2288
+ lines.push(`\u2502 \u2717 failed${" ".repeat(53)}\u2502`);
2289
+ } else {
2290
+ const errMsg = (entry.error ?? "unknown error").slice(0, 50);
2291
+ lines.push(`\u2502 \u2717 ${errMsg.padEnd(59)}\u2502`);
2292
+ }
2293
+ }
2294
+ lines.push(`\u2514${"\u2500".repeat(63)}\u2518`);
2295
+ }
2296
+ if (lines.length === 1) return null;
2297
+ return lines.join("\n");
2298
+ }
2299
+ function formatSchemaValue(val) {
2300
+ if (!val || typeof val !== "object") return String(val);
2301
+ const obj = val;
2302
+ if (Array.isArray(obj.enum)) {
2303
+ return obj.enum.slice(0, 4).map((e) => `"${e}"`).join(" | ") + (obj.enum.length > 4 ? ` | ...` : "");
2304
+ }
2305
+ if (typeof obj.type === "string") return obj.type;
2306
+ return JSON.stringify(val).slice(0, 30);
2307
+ }
2308
+
2309
+ // src/session/conversation-state.ts
2310
+ function computeScopeDelta(previous, current) {
2311
+ const prevMap = /* @__PURE__ */ new Map();
2312
+ for (const entry of previous) prevMap.set(entry.name, entry);
2313
+ const currMap = /* @__PURE__ */ new Map();
2314
+ for (const entry of current) currMap.set(entry.name, entry);
2315
+ const added = [];
2316
+ const changed = [];
2317
+ for (const entry of current) {
2318
+ const prev = prevMap.get(entry.name);
2319
+ if (!prev) {
2320
+ added.push(entry);
2321
+ } else if (prev.type !== entry.type || prev.value !== entry.value) {
2322
+ changed.push({
2323
+ ...entry,
2324
+ previousValue: prev.value,
2325
+ previousType: prev.type
2326
+ });
2327
+ }
2328
+ }
2329
+ const removed = [];
2330
+ for (const entry of previous) {
2331
+ if (!currMap.has(entry.name)) {
2332
+ removed.push(entry.name);
2333
+ }
2334
+ }
2335
+ return { added, changed, removed };
2336
+ }
2337
+ function serializeTasklistsState(state) {
2338
+ const tasklists = {};
2339
+ for (const [id, tl] of state.tasklists) {
2340
+ tasklists[id] = {
2341
+ plan: {
2342
+ tasklistId: tl.plan.tasklistId,
2343
+ description: tl.plan.description,
2344
+ tasks: tl.plan.tasks
2345
+ },
2346
+ completed: Object.fromEntries(tl.completed),
2347
+ readyTasks: [...tl.readyTasks],
2348
+ runningTasks: [...tl.runningTasks],
2349
+ outputs: Object.fromEntries(tl.outputs),
2350
+ progressMessages: Object.fromEntries(tl.progressMessages),
2351
+ retryCount: Object.fromEntries(tl.retryCount)
2352
+ };
2353
+ }
2354
+ return { tasklists };
2355
+ }
2356
+ var ConversationRecorder = class {
2357
+ state;
2358
+ previousScope = [];
2359
+ pendingEvents = [];
2360
+ currentTurnStartedAt;
2361
+ constructor() {
2362
+ const now = Date.now();
2363
+ this.state = {
2364
+ startedAt: now,
2365
+ turns: [],
2366
+ tasklists: { tasklists: {} },
2367
+ stopCount: 0,
2368
+ status: "idle"
2369
+ };
2370
+ this.currentTurnStartedAt = now;
2371
+ }
2372
+ /** Record an assistant turn ending at a stop boundary. */
2373
+ recordStop(code, payload, scope, tasklists) {
2374
+ const stopPayload = {};
2375
+ for (const [key, sv] of Object.entries(payload)) {
2376
+ stopPayload[key] = { display: sv.display, type: typeof sv.value };
2377
+ }
2378
+ this.state.stopCount++;
2379
+ this.pushTurn({
2380
+ role: "assistant",
2381
+ code,
2382
+ message: null,
2383
+ boundary: { type: "stop", payload: stopPayload },
2384
+ scope
2385
+ });
2386
+ this.state.tasklists = serializeTasklistsState(tasklists);
2387
+ }
2388
+ /** Record an assistant turn ending at an error boundary. */
2389
+ recordError(code, error, scope) {
2390
+ this.pushTurn({
2391
+ role: "assistant",
2392
+ code,
2393
+ message: null,
2394
+ boundary: {
2395
+ type: "error",
2396
+ error: {
2397
+ type: error.type,
2398
+ message: error.message,
2399
+ line: error.line,
2400
+ source: error.source
2401
+ }
2402
+ },
2403
+ scope
2404
+ });
2405
+ }
2406
+ /** Record an assistant turn ending at an intervention boundary. */
2407
+ recordIntervention(code, text, scope) {
2408
+ this.pushTurn({
2409
+ role: "assistant",
2410
+ code,
2411
+ message: null,
2412
+ boundary: { type: "intervention", text },
2413
+ scope
2414
+ });
2415
+ }
2416
+ /** Record an assistant turn ending at a tasklist reminder boundary. */
2417
+ recordTasklistReminder(code, tasklistId, ready, blocked, failed, scope, tasklists) {
2418
+ this.pushTurn({
2419
+ role: "assistant",
2420
+ code,
2421
+ message: null,
2422
+ boundary: { type: "tasklist_reminder", tasklistId, ready, blocked, failed },
2423
+ scope
2424
+ });
2425
+ this.state.tasklists = serializeTasklistsState(tasklists);
2426
+ }
2427
+ /** Record session completion. */
2428
+ recordCompletion(code, scope, tasklists, status) {
2429
+ this.pushTurn({
2430
+ role: "assistant",
2431
+ code,
2432
+ message: null,
2433
+ boundary: { type: "completion" },
2434
+ scope
2435
+ });
2436
+ this.state.tasklists = serializeTasklistsState(tasklists);
2437
+ this.state.status = status;
2438
+ }
2439
+ /** Record a user message turn. */
2440
+ recordUserMessage(text, scope) {
2441
+ this.pushTurn({
2442
+ role: "user",
2443
+ code: null,
2444
+ message: text,
2445
+ boundary: null,
2446
+ scope
2447
+ });
2448
+ }
2449
+ /** Accumulate a session event (filtered to TurnEvent subset). */
2450
+ recordEvent(event) {
2451
+ const turnEvent = toTurnEvent(event);
2452
+ if (turnEvent) this.pendingEvents.push(turnEvent);
2453
+ if (event.type === "status") {
2454
+ this.state.status = event.status;
2455
+ }
2456
+ }
2457
+ /** Update session status. */
2458
+ updateStatus(status) {
2459
+ this.state.status = status;
2460
+ }
2461
+ /** Get the current full conversation state (returns a shallow copy). */
2462
+ getState() {
2463
+ return {
2464
+ ...this.state,
2465
+ turns: [...this.state.turns]
2466
+ };
2467
+ }
2468
+ pushTurn(opts) {
2469
+ const now = Date.now();
2470
+ const scopeSnapshot = [...opts.scope];
2471
+ const scopeDelta = this.state.turns.length > 0 ? computeScopeDelta(this.previousScope, opts.scope) : opts.scope.length > 0 ? { added: [...opts.scope], changed: [], removed: [] } : null;
2472
+ this.state.turns.push({
2473
+ index: this.state.turns.length,
2474
+ startedAt: this.currentTurnStartedAt,
2475
+ endedAt: now,
2476
+ role: opts.role,
2477
+ code: opts.code ? [...opts.code] : null,
2478
+ message: opts.message,
2479
+ boundary: opts.boundary,
2480
+ scopeSnapshot,
2481
+ scopeDelta,
2482
+ events: this.pendingEvents.splice(0)
2483
+ });
2484
+ this.previousScope = scopeSnapshot;
2485
+ this.currentTurnStartedAt = now;
2486
+ }
2487
+ };
2488
+ function toTurnEvent(event) {
2489
+ switch (event.type) {
2490
+ case "display":
2491
+ return { type: "display", componentId: event.componentId };
2492
+ case "ask_start":
2493
+ return { type: "ask_start", formId: event.formId };
2494
+ case "ask_end":
2495
+ return { type: "ask_end", formId: event.formId };
2496
+ case "async_start":
2497
+ return { type: "async_start", taskId: event.taskId, label: event.label };
2498
+ case "async_complete":
2499
+ return { type: "async_complete", taskId: event.taskId };
2500
+ case "async_failed":
2501
+ return { type: "async_failed", taskId: event.taskId, error: event.error };
2502
+ case "async_cancelled":
2503
+ return { type: "async_cancelled", taskId: event.taskId };
2504
+ case "tasklist_declared":
2505
+ return {
2506
+ type: "tasklist_declared",
2507
+ tasklistId: event.tasklistId,
2508
+ description: event.plan.description,
2509
+ taskCount: event.plan.tasks.length
2510
+ };
2511
+ case "task_complete":
2512
+ return { type: "task_complete", tasklistId: event.tasklistId, taskId: event.id };
2513
+ case "task_failed":
2514
+ return { type: "task_failed", tasklistId: event.tasklistId, taskId: event.id, error: event.error };
2515
+ case "task_retried":
2516
+ return { type: "task_retried", tasklistId: event.tasklistId, taskId: event.id };
2517
+ case "task_skipped":
2518
+ return { type: "task_skipped", tasklistId: event.tasklistId, taskId: event.id, reason: event.reason };
2519
+ case "task_progress":
2520
+ return { type: "task_progress", tasklistId: event.tasklistId, taskId: event.id, message: event.message, percent: event.percent };
2521
+ case "knowledge_loaded":
2522
+ return { type: "knowledge_loaded", domains: event.domains };
2523
+ case "class_loaded":
2524
+ return { type: "class_loaded", className: event.className, methods: event.methods };
2525
+ case "hook":
2526
+ return { type: "hook", hookId: event.hookId, action: event.action };
2527
+ case "agent_registered":
2528
+ return { type: "agent_registered", varName: event.varName, label: event.label };
2529
+ case "agent_resolved":
2530
+ return { type: "agent_resolved", varName: event.varName };
2531
+ case "agent_failed":
2532
+ return { type: "agent_failed", varName: event.varName, error: event.error };
2533
+ default:
2534
+ return null;
2535
+ }
2536
+ }
2537
+
2538
+ // src/session/session.ts
2539
+ var Session = class extends EventEmitter {
2540
+ status = "idle";
2541
+ config;
2542
+ sandbox;
2543
+ asyncManager;
2544
+ hookRegistry;
2545
+ streamController;
2546
+ globalsApi;
2547
+ blocks = [];
2548
+ codeLines = [];
2549
+ messages = [];
2550
+ activeFormId = null;
2551
+ stopCount = 0;
2552
+ tasklistReminderCount = 0;
2553
+ agentRegistry;
2554
+ recorder;
2555
+ turnCodeStart = 0;
2556
+ onSpawn;
2557
+ constructor(options = {}) {
2558
+ super();
2559
+ this.config = options.config ? mergeConfig(options.config) : createDefaultConfig();
2560
+ this.asyncManager = new AsyncManager(this.config.maxAsyncTasks);
2561
+ this.hookRegistry = new HookRegistry();
2562
+ this.agentRegistry = new AgentRegistry({
2563
+ onRegistered: (varName, label) => {
2564
+ this.emitEvent({ type: "agent_registered", varName, label });
2565
+ },
2566
+ onResolved: (varName) => {
2567
+ this.emitEvent({ type: "agent_resolved", varName });
2568
+ },
2569
+ onFailed: (varName, error) => {
2570
+ this.emitEvent({ type: "agent_failed", varName, error });
2571
+ },
2572
+ onQuestionAsked: (varName, question) => {
2573
+ this.emitEvent({ type: "agent_question_asked", varName, question });
2574
+ },
2575
+ onQuestionAnswered: (varName) => {
2576
+ this.emitEvent({ type: "agent_question_answered", varName });
2577
+ }
2578
+ });
2579
+ if (options.hooks) {
2580
+ for (const hook of options.hooks) {
2581
+ this.hookRegistry.register(hook);
2582
+ }
2583
+ }
2584
+ this.sandbox = new Sandbox({
2585
+ timeout: this.config.functionTimeout,
2586
+ globals: options.globals
2587
+ });
2588
+ this.streamController = new StreamController({
2589
+ onStatement: (source) => this.executeStatement(source),
2590
+ onStop: (payload, source) => this.handleStop(payload, source),
2591
+ onError: (error) => this.handleError(error),
2592
+ onEvent: (event) => this.emitEvent(event),
2593
+ onCodeLine: (line) => this.codeLines.push(line),
2594
+ hookRegistry: this.hookRegistry,
2595
+ hookContext: () => ({
2596
+ lineNumber: this.sandbox.getLineCount(),
2597
+ sessionId: `session_${Date.now()}`,
2598
+ scope: this.sandbox.getScope()
2599
+ })
2600
+ });
2601
+ this.globalsApi = createGlobals({
2602
+ pauseController: this.streamController,
2603
+ renderSurface: {
2604
+ append: (id, element) => {
2605
+ this.emitEvent({ type: "display", componentId: id, jsx: serializeReactElement(element) });
2606
+ },
2607
+ renderForm: async (formId, element) => {
2608
+ this.activeFormId = formId;
2609
+ this.emitEvent({ type: "ask_start", formId, jsx: serializeReactElement(element) });
2610
+ return new Promise((resolve) => {
2611
+ this.once(`form:${formId}`, (data) => {
2612
+ this.activeFormId = null;
2613
+ this.emitEvent({ type: "ask_end", formId });
2614
+ resolve(data);
2615
+ });
2616
+ });
2617
+ },
2618
+ cancelForm: (formId) => {
2619
+ this.activeFormId = null;
2620
+ this.emit(`form:${formId}`, { _cancelled: true });
2621
+ }
2622
+ },
2623
+ asyncManager: this.asyncManager,
2624
+ serializationLimits: this.config.serializationLimits,
2625
+ askTimeout: this.config.askTimeout,
2626
+ onStop: (payload, source) => this.handleStop(payload, source),
2627
+ onDisplay: (id) => {
2628
+ },
2629
+ onAsyncStart: (taskId, label) => {
2630
+ this.emitEvent({ type: "async_start", taskId, label });
2631
+ },
2632
+ onTasklistDeclared: (tasklistId, plan) => {
2633
+ this.emitEvent({ type: "tasklist_declared", tasklistId, plan });
2634
+ },
2635
+ onTaskComplete: (tasklistId, id, output) => {
2636
+ this.emitEvent({ type: "task_complete", tasklistId, id, output });
2637
+ },
2638
+ onTaskFailed: (tasklistId, id, error) => {
2639
+ this.emitEvent({ type: "task_failed", tasklistId, id, error });
2640
+ },
2641
+ onTaskRetried: (tasklistId, id) => {
2642
+ this.emitEvent({ type: "task_retried", tasklistId, id });
2643
+ },
2644
+ onTaskSkipped: (tasklistId, id, reason) => {
2645
+ this.emitEvent({ type: "task_skipped", tasklistId, id, reason });
2646
+ },
2647
+ onTaskProgress: (tasklistId, id, message, percent) => {
2648
+ this.emitEvent({ type: "task_progress", tasklistId, id, message, percent });
2649
+ },
2650
+ onTaskAsyncStart: (tasklistId, id) => {
2651
+ this.emitEvent({ type: "task_async_start", tasklistId, id });
2652
+ },
2653
+ onTaskAsyncComplete: (tasklistId, id, output) => {
2654
+ this.emitEvent({ type: "task_async_complete", tasklistId, id, output });
2655
+ },
2656
+ onTaskAsyncFailed: (tasklistId, id, error) => {
2657
+ this.emitEvent({ type: "task_async_failed", tasklistId, id, error });
2658
+ },
2659
+ onTaskOrderViolation: (tasklistId, attemptedTaskId, readyTasks) => {
2660
+ this.emitEvent({ type: "task_order_violation", tasklistId, attemptedTaskId, readyTasks });
2661
+ },
2662
+ onTaskCompleteContinue: (tasklistId, completedTaskId, readyTasks) => {
2663
+ this.emitEvent({ type: "task_complete_continue", tasklistId, completedTaskId, readyTasks });
2664
+ },
2665
+ maxTaskRetries: this.config.maxTaskRetries,
2666
+ maxTasksPerTasklist: this.config.maxTasksPerTasklist,
2667
+ sleepMaxSeconds: this.config.sleepMaxSeconds,
2668
+ onLoadKnowledge: options.knowledgeLoader ? (selector) => {
2669
+ const content = options.knowledgeLoader(selector);
2670
+ const domains = Object.keys(content);
2671
+ this.emitEvent({ type: "knowledge_loaded", domains });
2672
+ return content;
2673
+ } : void 0,
2674
+ getClassInfo: options.getClassInfo ?? void 0,
2675
+ onLoadClass: options.loadClass ? (className) => {
2676
+ const info = options.getClassInfo?.(className);
2677
+ const methodNames = info?.methods.map((m) => m.name) ?? [];
2678
+ options.loadClass(className, this);
2679
+ this.emitEvent({ type: "class_loaded", className, methods: methodNames });
2680
+ } : void 0,
2681
+ onAskParent: options.onAskParent,
2682
+ isFireAndForget: options.isFireAndForget,
2683
+ onRespond: (promise, data) => {
2684
+ const entry = this.agentRegistry.findByPromise(promise);
2685
+ if (!entry) throw new Error("respond: unknown agent \u2014 pass the agent variable as the first argument");
2686
+ this.agentRegistry.respond(entry.varName, data);
2687
+ }
2688
+ });
2689
+ this.sandbox.inject("stop", this.globalsApi.stop);
2690
+ this.sandbox.inject("display", this.globalsApi.display);
2691
+ this.sandbox.inject("ask", this.globalsApi.ask);
2692
+ this.sandbox.inject("async", this.globalsApi.async);
2693
+ this.sandbox.inject("tasklist", this.globalsApi.tasklist);
2694
+ this.sandbox.inject("completeTask", this.globalsApi.completeTask);
2695
+ this.sandbox.inject("completeTaskAsync", this.globalsApi.completeTaskAsync);
2696
+ this.sandbox.inject("taskProgress", this.globalsApi.taskProgress);
2697
+ this.sandbox.inject("failTask", this.globalsApi.failTask);
2698
+ this.sandbox.inject("retryTask", this.globalsApi.retryTask);
2699
+ this.sandbox.inject("sleep", this.globalsApi.sleep);
2700
+ this.sandbox.inject("loadKnowledge", this.globalsApi.loadKnowledge);
2701
+ this.sandbox.inject("loadClass", this.globalsApi.loadClass);
2702
+ this.sandbox.inject("askParent", this.globalsApi.askParent);
2703
+ this.sandbox.inject("respond", this.globalsApi.respond);
2704
+ if (options.agentNamespaces) {
2705
+ for (const [name, ns] of Object.entries(options.agentNamespaces)) {
2706
+ this.sandbox.inject(name, ns);
2707
+ }
2708
+ }
2709
+ if (options.knowledgeNamespace) {
2710
+ this.sandbox.inject("knowledge", options.knowledgeNamespace);
2711
+ }
2712
+ this.recorder = new ConversationRecorder();
2713
+ this.on("event", (event) => this.recorder.recordEvent(event));
2714
+ this.onSpawn = options.onSpawn ? async (config) => {
2715
+ this.emitEvent({
2716
+ type: "agent_spawn_start",
2717
+ spaceName: config.spaceName,
2718
+ agentSlug: config.agentSlug,
2719
+ actionId: config.actionId
2720
+ });
2721
+ try {
2722
+ const result = await options.onSpawn(config);
2723
+ this.emitEvent({
2724
+ type: "agent_spawn_complete",
2725
+ spaceName: config.spaceName,
2726
+ agentSlug: config.agentSlug,
2727
+ actionId: config.actionId,
2728
+ result
2729
+ });
2730
+ return result;
2731
+ } catch (err) {
2732
+ this.emitEvent({
2733
+ type: "agent_spawn_failed",
2734
+ spaceName: config.spaceName,
2735
+ agentSlug: config.agentSlug,
2736
+ actionId: config.actionId,
2737
+ error: err?.message ?? String(err)
2738
+ });
2739
+ throw err;
2740
+ }
2741
+ } : void 0;
2742
+ }
2743
+ async executeStatement(source) {
2744
+ this.globalsApi.setCurrentSource(source);
2745
+ return this.sandbox.execute(source);
2746
+ }
2747
+ handleStop(payload, source) {
2748
+ this.stopCount++;
2749
+ this.agentRegistry.advanceTurn();
2750
+ const cpState = this.globalsApi.getTasklistsState();
2751
+ const tasksBlock = generateTasksBlock(cpState);
2752
+ const resolvedInThisStop = /* @__PURE__ */ new Set();
2753
+ for (const [, sv] of Object.entries(payload)) {
2754
+ const entry = this.agentRegistry.findByPromise(sv.value);
2755
+ if (entry?.status === "resolved") resolvedInThisStop.add(entry.varName);
2756
+ }
2757
+ const agentsBlock = generateAgentsBlock(this.agentRegistry, resolvedInThisStop);
2758
+ const baseMsg = buildStopMessage(payload);
2759
+ let msg = baseMsg;
2760
+ if (tasksBlock) msg += `
2761
+
2762
+ ${tasksBlock}`;
2763
+ if (agentsBlock) msg += `
2764
+
2765
+ ${agentsBlock}`;
2766
+ this.messages.push({ role: "assistant", content: this.codeLines.join("\n") });
2767
+ this.messages.push({ role: "user", content: msg });
2768
+ this.emitEvent({
2769
+ type: "read",
2770
+ payload: Object.fromEntries(
2771
+ Object.entries(payload).map(([k, v]) => [k, v.value])
2772
+ ),
2773
+ blockId: `stop_${this.stopCount}`
2774
+ });
2775
+ this.emitEvent({ type: "scope", entries: this.sandbox.getScope() });
2776
+ const turnCode = this.codeLines.slice(this.turnCodeStart);
2777
+ this.recorder.recordStop(turnCode, payload, this.sandbox.getScope(), cpState);
2778
+ this.turnCodeStart = this.codeLines.length;
2779
+ }
2780
+ handleError(error) {
2781
+ const msg = buildErrorMessage(error);
2782
+ this.messages.push({ role: "assistant", content: this.codeLines.join("\n") });
2783
+ this.messages.push({ role: "user", content: msg });
2784
+ this.emitEvent({ type: "scope", entries: this.sandbox.getScope() });
2785
+ const turnCode = this.codeLines.slice(this.turnCodeStart);
2786
+ this.recorder.recordError(turnCode, error, this.sandbox.getScope());
2787
+ this.turnCodeStart = this.codeLines.length;
2788
+ }
2789
+ /**
2790
+ * Handle a user message.
2791
+ */
2792
+ async handleUserMessage(text) {
2793
+ this.setStatus("executing");
2794
+ this.messages.push({ role: "user", content: text });
2795
+ this.recorder.recordUserMessage(text, this.sandbox.getScope());
2796
+ }
2797
+ /**
2798
+ * Feed tokens from the LLM stream.
2799
+ */
2800
+ async feedToken(token) {
2801
+ await this.streamController.feedToken(token);
2802
+ }
2803
+ /**
2804
+ * Finalize the LLM stream.
2805
+ * Returns 'complete' if done, or 'tasklist_incomplete' if tasks remain.
2806
+ */
2807
+ async finalize() {
2808
+ await this.streamController.finalize();
2809
+ const cpState = this.globalsApi.getTasklistsState();
2810
+ for (const [tasklistId, tasklist] of cpState.tasklists) {
2811
+ const hasRequiredIncomplete = tasklist.plan.tasks.some((t) => {
2812
+ const completion = tasklist.completed.get(t.id);
2813
+ const isIncomplete = !completion || completion.status !== "completed" && completion.status !== "skipped";
2814
+ return isIncomplete && !t.optional;
2815
+ });
2816
+ if (!hasRequiredIncomplete) continue;
2817
+ if (tasklist.runningTasks.size > 0) {
2818
+ await Promise.race([
2819
+ Promise.allSettled(
2820
+ [...tasklist.runningTasks].map(
2821
+ (id) => new Promise((resolve) => {
2822
+ const check = () => {
2823
+ if (!tasklist.runningTasks.has(id)) {
2824
+ resolve();
2825
+ return;
2826
+ }
2827
+ setTimeout(check, 100);
2828
+ };
2829
+ check();
2830
+ })
2831
+ )
2832
+ ),
2833
+ new Promise((resolve) => setTimeout(resolve, this.config.taskAsyncTimeout))
2834
+ ]);
2835
+ }
2836
+ if (this.tasklistReminderCount < this.config.maxTasklistReminders) {
2837
+ this.tasklistReminderCount++;
2838
+ const ready = [...tasklist.readyTasks];
2839
+ const blocked = tasklist.plan.tasks.filter((t) => !tasklist.readyTasks.has(t.id) && !tasklist.completed.has(t.id) && !tasklist.runningTasks.has(t.id)).map((t) => `${t.id} (waiting on ${(t.dependsOn ?? []).join(", ")})`);
2840
+ const failed = [...tasklist.completed.entries()].filter(([_, c]) => c.status === "failed").map(([id]) => id);
2841
+ const msg = buildTasklistReminderMessage(tasklistId, ready, blocked, failed);
2842
+ const tasksBlock = generateTasksBlock(cpState);
2843
+ const fullMsg = tasksBlock ? `${msg}
2844
+
2845
+ ${tasksBlock}` : msg;
2846
+ this.messages.push({ role: "assistant", content: this.codeLines.join("\n") });
2847
+ this.messages.push({ role: "user", content: fullMsg });
2848
+ const blockedIds = blocked.map((b) => b.split(" ")[0]);
2849
+ this.recorder.recordTasklistReminder(
2850
+ [...this.codeLines],
2851
+ tasklistId,
2852
+ ready,
2853
+ blockedIds,
2854
+ failed,
2855
+ this.sandbox.getScope(),
2856
+ cpState
2857
+ );
2858
+ this.codeLines = [];
2859
+ this.turnCodeStart = 0;
2860
+ this.emitEvent({ type: "tasklist_reminder", tasklistId, ready, blocked: blockedIds, failed });
2861
+ this.emitEvent({ type: "scope", entries: this.sandbox.getScope() });
2862
+ return "tasklist_incomplete";
2863
+ }
2864
+ }
2865
+ await this.asyncManager.drain(5e3);
2866
+ const turnCode = this.codeLines.slice(this.turnCodeStart);
2867
+ this.recorder.recordCompletion(turnCode, this.sandbox.getScope(), this.globalsApi.getTasklistsState(), "complete");
2868
+ this.turnCodeStart = this.codeLines.length;
2869
+ this.setStatus("complete");
2870
+ return "complete";
2871
+ }
2872
+ /**
2873
+ * Resolve a pending stop() call, allowing sandbox to continue.
2874
+ * Called by the runner after injecting the stop payload as a user message.
2875
+ */
2876
+ resolveStop() {
2877
+ this.globalsApi.resolveStop();
2878
+ this.streamController.resume();
2879
+ }
2880
+ /**
2881
+ * Inject a value into the sandbox as a global.
2882
+ * Used to inject class namespace objects after loadClass().
2883
+ */
2884
+ injectGlobal(name, value) {
2885
+ this.sandbox.inject(name, value);
2886
+ }
2887
+ /**
2888
+ * Resolve a pending ask() form.
2889
+ */
2890
+ resolveAsk(formId, data) {
2891
+ const hasListener = this.listenerCount(`form:${formId}`) > 0;
2892
+ console.log(`\x1B[90m [session] resolveAsk ${formId} hasListener=${hasListener} activeFormId=${this.activeFormId}\x1B[0m`);
2893
+ this.emit(`form:${formId}`, data);
2894
+ }
2895
+ /**
2896
+ * Cancel a pending ask() form.
2897
+ */
2898
+ cancelAsk(formId) {
2899
+ this.emit(`form:${formId}`, { _cancelled: true });
2900
+ }
2901
+ /**
2902
+ * Cancel an async task.
2903
+ */
2904
+ cancelAsyncTask(taskId, message = "") {
2905
+ this.asyncManager.cancel(taskId, message);
2906
+ this.emitEvent({ type: "async_cancelled", taskId });
2907
+ }
2908
+ /**
2909
+ * Pause the session.
2910
+ */
2911
+ pause() {
2912
+ this.streamController.pause();
2913
+ this.setStatus("paused");
2914
+ }
2915
+ /**
2916
+ * Resume the session.
2917
+ */
2918
+ resume() {
2919
+ this.streamController.resume();
2920
+ this.setStatus("executing");
2921
+ }
2922
+ /**
2923
+ * Handle user intervention (message while agent is running).
2924
+ */
2925
+ handleIntervention(text) {
2926
+ if (this.activeFormId) {
2927
+ this.cancelAsk(this.activeFormId);
2928
+ }
2929
+ this.streamController.pause();
2930
+ const msg = buildInterventionMessage(text);
2931
+ this.messages.push({ role: "assistant", content: this.codeLines.join("\n") });
2932
+ this.messages.push({ role: "user", content: msg });
2933
+ this.recorder.recordIntervention([...this.codeLines], text, this.sandbox.getScope());
2934
+ this.codeLines = [];
2935
+ this.turnCodeStart = 0;
2936
+ this.emitEvent({ type: "scope", entries: this.sandbox.getScope() });
2937
+ this.streamController.resume();
2938
+ }
2939
+ /**
2940
+ * Get a snapshot of the current session state.
2941
+ */
2942
+ snapshot() {
2943
+ return {
2944
+ status: this.status,
2945
+ blocks: [...this.blocks],
2946
+ scope: this.sandbox.getScope(),
2947
+ asyncTasks: this.asyncManager.getAllTasks().map((t) => ({
2948
+ id: t.id,
2949
+ label: t.label,
2950
+ status: t.status,
2951
+ elapsed: Date.now() - t.startTime
2952
+ })),
2953
+ activeFormId: this.activeFormId,
2954
+ tasklistsState: this.globalsApi.getTasklistsState(),
2955
+ agentEntries: this.agentRegistry.getAll().map((e) => ({
2956
+ varName: e.varName,
2957
+ label: e.label,
2958
+ status: e.status,
2959
+ error: e.error
2960
+ }))
2961
+ };
2962
+ }
2963
+ /**
2964
+ * Get the full serializable conversation state.
2965
+ */
2966
+ getConversationState() {
2967
+ return this.recorder.getState();
2968
+ }
2969
+ /**
2970
+ * Get the current status.
2971
+ */
2972
+ getStatus() {
2973
+ return this.status;
2974
+ }
2975
+ /**
2976
+ * Get messages for context.
2977
+ */
2978
+ getMessages() {
2979
+ return this.messages;
2980
+ }
2981
+ /**
2982
+ * Get the public globals object (for passing to setup functions).
2983
+ */
2984
+ getGlobals() {
2985
+ return {
2986
+ stop: this.globalsApi.stop,
2987
+ display: this.globalsApi.display,
2988
+ ask: this.globalsApi.ask,
2989
+ async: this.globalsApi.async,
2990
+ tasklist: this.globalsApi.tasklist,
2991
+ completeTask: this.globalsApi.completeTask,
2992
+ completeTaskAsync: this.globalsApi.completeTaskAsync,
2993
+ taskProgress: this.globalsApi.taskProgress,
2994
+ failTask: this.globalsApi.failTask,
2995
+ retryTask: this.globalsApi.retryTask,
2996
+ sleep: this.globalsApi.sleep,
2997
+ loadKnowledge: this.globalsApi.loadKnowledge,
2998
+ loadClass: this.globalsApi.loadClass,
2999
+ askParent: this.globalsApi.askParent,
3000
+ respond: this.globalsApi.respond
3001
+ };
3002
+ }
3003
+ /**
3004
+ * Get the agent registry.
3005
+ */
3006
+ getAgentRegistry() {
3007
+ return this.agentRegistry;
3008
+ }
3009
+ /**
3010
+ * Get scope table as string.
3011
+ */
3012
+ getScopeTable() {
3013
+ return generateScopeTable(this.sandbox.getScope(), {
3014
+ maxVariables: this.config.workspace.maxScopeVariables,
3015
+ maxValueWidth: this.config.workspace.maxScopeValueWidth
3016
+ });
3017
+ }
3018
+ setStatus(status) {
3019
+ this.status = status;
3020
+ this.emitEvent({ type: "status", status });
3021
+ }
3022
+ emitEvent(event) {
3023
+ this.emit("event", event);
3024
+ }
3025
+ /**
3026
+ * Destroy the session and clean up resources.
3027
+ */
3028
+ destroy() {
3029
+ this.agentRegistry.destroy();
3030
+ this.asyncManager.cancelAll();
3031
+ this.sandbox.destroy();
3032
+ this.hookRegistry.clear();
3033
+ this.removeAllListeners();
3034
+ }
3035
+ };
3036
+ var CLIENT_COMPONENTS = /* @__PURE__ */ new Set([
3037
+ "TextInput",
3038
+ "TextArea",
3039
+ "NumberInput",
3040
+ "Slider",
3041
+ "Checkbox",
3042
+ "Select",
3043
+ "MultiSelect",
3044
+ "DatePicker",
3045
+ "FileUpload"
3046
+ ]);
3047
+ function serializeReactElement(element, depth = 0) {
3048
+ if (depth > 20) return { component: "div", props: {}, children: ["[max depth]"] };
3049
+ if (!element || typeof element !== "object" || !("type" in element)) {
3050
+ return { component: "span", props: {}, children: [String(element ?? "")] };
3051
+ }
3052
+ const el = element;
3053
+ const { children, ...restProps } = el.props ?? {};
3054
+ let component;
3055
+ if (typeof el.type === "string") {
3056
+ component = el.type;
3057
+ } else if (typeof el.type === "function") {
3058
+ const name = el.type.name || "";
3059
+ if (CLIENT_COMPONENTS.has(name)) {
3060
+ component = name;
3061
+ } else {
3062
+ const _consoleError = console.error;
3063
+ try {
3064
+ console.error = () => {
3065
+ };
3066
+ const rendered = el.type(el.props);
3067
+ return serializeReactElement(rendered, depth + 1);
3068
+ } catch {
3069
+ component = name || "div";
3070
+ } finally {
3071
+ console.error = _consoleError;
3072
+ }
3073
+ }
3074
+ } else {
3075
+ component = "div";
3076
+ }
3077
+ const safeProps = {};
3078
+ for (const [key, value] of Object.entries(restProps)) {
3079
+ if (typeof value === "function") continue;
3080
+ if (typeof value === "symbol") continue;
3081
+ safeProps[key] = value;
3082
+ }
3083
+ const serializedChildren = serializeChildren(children, depth);
3084
+ return { component, props: safeProps, children: serializedChildren.length > 0 ? serializedChildren : void 0 };
3085
+ }
3086
+ function serializeChildren(children, depth) {
3087
+ if (children == null) return [];
3088
+ if (typeof children === "string") return [children];
3089
+ if (typeof children === "number" || typeof children === "boolean") return [String(children)];
3090
+ if (Array.isArray(children)) {
3091
+ return children.flatMap((child) => serializeChildren(child, depth));
3092
+ }
3093
+ if (typeof children === "object" && "type" in children) {
3094
+ return [serializeReactElement(children, depth + 1)];
3095
+ }
3096
+ return [String(children)];
3097
+ }
3098
+
3099
+ // src/parser/global-detector.ts
3100
+ var GLOBALS = ["stop", "display", "ask", "async", "tasklist", "completeTask", "loadKnowledge"];
3101
+ function detectGlobalCall(source) {
3102
+ const trimmed = source.trim();
3103
+ const withoutAwait = trimmed.startsWith("await ") ? trimmed.slice(6).trim() : trimmed;
3104
+ for (const name of GLOBALS) {
3105
+ if (withoutAwait.startsWith(name + "(") || withoutAwait.startsWith(name + " (")) {
3106
+ return name;
3107
+ }
3108
+ }
3109
+ const assignMatch = trimmed.match(/^(?:const|let|var)\s+\w+\s*=\s*(?:await\s+)?(\w+)\s*\(/);
3110
+ if (assignMatch) {
3111
+ const callee = assignMatch[1];
3112
+ if (GLOBALS.includes(callee)) {
3113
+ return callee;
3114
+ }
3115
+ }
3116
+ return null;
3117
+ }
3118
+
3119
+ // src/context/code-window.ts
3120
+ function compressCodeWindow(turns, maxLines) {
3121
+ if (turns.length === 0) return [];
3122
+ let totalLines = turns.reduce((sum, t) => sum + t.lines.length, 0);
3123
+ if (totalLines <= maxLines) {
3124
+ return turns.flatMap((t) => t.lines);
3125
+ }
3126
+ const result = [];
3127
+ const summaries = [];
3128
+ let linesRemaining = maxLines;
3129
+ for (let i = turns.length - 1; i >= 0; i--) {
3130
+ const turn = turns[i];
3131
+ if (linesRemaining >= turn.lines.length) {
3132
+ result.unshift(...turn.lines);
3133
+ linesRemaining -= turn.lines.length;
3134
+ } else {
3135
+ const startLine = turn.turnIndex;
3136
+ const endLine = startLine + turn.lines.length - 1;
3137
+ const declList = turn.declarations.length > 0 ? ` declared: ${turn.declarations.join(", ")}` : "";
3138
+ summaries.unshift(`// [lines ${startLine}-${endLine} executed]${declList}`);
3139
+ }
3140
+ }
3141
+ return [...summaries, ...result];
3142
+ }
3143
+ function buildSummaryComment(startLine, endLine, declarations) {
3144
+ const declList = declarations.length > 0 ? ` declared: ${declarations.join(", ")}` : "";
3145
+ return `// [lines ${startLine}-${endLine} executed]${declList}`;
3146
+ }
3147
+
3148
+ // src/context/stop-decay.ts
3149
+ var DEFAULT_TIERS2 = { full: 2, keysOnly: 5, summary: 10 };
3150
+ function getDecayLevel(distance, tiers = DEFAULT_TIERS2) {
3151
+ if (distance <= tiers.full) return "full";
3152
+ if (distance <= tiers.keysOnly) return "keys";
3153
+ if (distance <= tiers.summary) return "count";
3154
+ return "removed";
3155
+ }
3156
+ function decayStopPayload(payload, distance, tiers = DEFAULT_TIERS2) {
3157
+ const level = getDecayLevel(distance, tiers);
3158
+ switch (level) {
3159
+ case "full":
3160
+ return formatFullPayload(payload);
3161
+ case "keys":
3162
+ return formatKeysPayload(payload);
3163
+ case "count":
3164
+ return formatCountPayload(payload);
3165
+ case "removed":
3166
+ return null;
3167
+ }
3168
+ }
3169
+ function formatFullPayload(payload) {
3170
+ const entries = Object.entries(payload).map(([key, sv]) => `${key}: ${sv.display}`);
3171
+ return `\u2190 stop { ${entries.join(", ")} }`;
3172
+ }
3173
+ function formatKeysPayload(payload) {
3174
+ const entries = Object.entries(payload).map(([key, sv]) => {
3175
+ const type = describeValueType(sv);
3176
+ return `${key}: ${type}`;
3177
+ });
3178
+ return `\u2190 stop { ${entries.join(", ")} }`;
3179
+ }
3180
+ function formatCountPayload(payload) {
3181
+ const count = Object.keys(payload).length;
3182
+ return `\u2190 stop (${count} value${count === 1 ? "" : "s"} read)`;
3183
+ }
3184
+ function describeValueType(sv) {
3185
+ const val = sv.value;
3186
+ if (val === null) return "null";
3187
+ if (val === void 0) return "undefined";
3188
+ if (Array.isArray(val)) return `Array(${val.length})`;
3189
+ if (typeof val === "object") {
3190
+ const keys = Object.keys(val);
3191
+ return `Object{${keys.join(",")}}`;
3192
+ }
3193
+ return typeof val;
3194
+ }
3195
+ function decayErrorMessage(errorMsg, distance, tiers = DEFAULT_TIERS2) {
3196
+ const level = getDecayLevel(distance, tiers);
3197
+ if (level === "removed") return null;
3198
+ if (level === "count") return "\u2190 error (1 error occurred)";
3199
+ return errorMsg;
3200
+ }
3201
+
3202
+ // src/context/system-prompt.ts
3203
+ function buildSystemPrompt(template, slots) {
3204
+ let result = template;
3205
+ for (const [key, value] of Object.entries(slots)) {
3206
+ const marker = `{{${key}}}`;
3207
+ result = result.replaceAll(marker, value);
3208
+ }
3209
+ return result;
3210
+ }
3211
+ function updateScopeInPrompt(systemPrompt, scopeTable) {
3212
+ const scopeStart = systemPrompt.indexOf("{{SCOPE}}");
3213
+ if (scopeStart === -1) {
3214
+ return systemPrompt;
3215
+ }
3216
+ return systemPrompt.replace(
3217
+ /\{\{SCOPE\}\}[\s\S]*?(?=\{\{|$)/,
3218
+ scopeTable + "\n\n"
3219
+ );
3220
+ }
3221
+
3222
+ // src/security/function-registry.ts
3223
+ function wrapFunction(name, fn, options = {}) {
3224
+ const timeout = options.timeout ?? 3e4;
3225
+ const rateState = options.rateLimit ? { calls: [] } : null;
3226
+ const wrapped = async function(...args) {
3227
+ if (rateState && options.rateLimit) {
3228
+ const now = Date.now();
3229
+ const { maxCalls, windowMs } = options.rateLimit;
3230
+ rateState.calls = rateState.calls.filter((t) => now - t < windowMs);
3231
+ if (rateState.calls.length >= maxCalls) {
3232
+ throw new Error(`Rate limit exceeded for ${name}: max ${maxCalls} calls per ${windowMs}ms`);
3233
+ }
3234
+ rateState.calls.push(now);
3235
+ }
3236
+ const start = Date.now();
3237
+ const result = await Promise.race([
3238
+ Promise.resolve(fn(...args)),
3239
+ new Promise(
3240
+ (_, reject) => setTimeout(() => reject(new Error(`Timeout: ${name} exceeded ${timeout}ms`)), timeout)
3241
+ )
3242
+ ]);
3243
+ const duration = Date.now() - start;
3244
+ options.onCall?.(name, args, duration);
3245
+ return result;
3246
+ };
3247
+ Object.defineProperty(wrapped, "name", { value: name });
3248
+ return wrapped;
3249
+ }
3250
+ var FunctionRegistry = class {
3251
+ functions = /* @__PURE__ */ new Map();
3252
+ options;
3253
+ constructor(options = {}) {
3254
+ this.options = options;
3255
+ }
3256
+ register(name, fn) {
3257
+ this.functions.set(name, wrapFunction(name, fn, this.options));
3258
+ }
3259
+ get(name) {
3260
+ return this.functions.get(name);
3261
+ }
3262
+ getAll() {
3263
+ const result = {};
3264
+ for (const [name, fn] of this.functions) {
3265
+ result[name] = fn;
3266
+ }
3267
+ return result;
3268
+ }
3269
+ has(name) {
3270
+ return this.functions.has(name);
3271
+ }
3272
+ names() {
3273
+ return [...this.functions.keys()];
3274
+ }
3275
+ };
3276
+
3277
+ // src/security/jsx-sanitizer.ts
3278
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed"]);
3279
+ var DANGEROUS_PROPS = /* @__PURE__ */ new Set(["dangerouslySetInnerHTML"]);
3280
+ var JAVASCRIPT_URL_PATTERN = /^\s*javascript:/i;
3281
+ function sanitizeJSX(jsx, path = "root") {
3282
+ const errors = [];
3283
+ if (BLOCKED_TAGS.has(jsx.component.toLowerCase())) {
3284
+ errors.push({
3285
+ path,
3286
+ message: `Blocked element: <${jsx.component}> is not allowed`
3287
+ });
3288
+ }
3289
+ for (const [key, value] of Object.entries(jsx.props)) {
3290
+ if (DANGEROUS_PROPS.has(key)) {
3291
+ errors.push({
3292
+ path: `${path}.props.${key}`,
3293
+ message: `Dangerous prop: ${key} is not allowed`
3294
+ });
3295
+ }
3296
+ if ((key === "href" || key === "src" || key === "action") && typeof value === "string" && JAVASCRIPT_URL_PATTERN.test(value)) {
3297
+ errors.push({
3298
+ path: `${path}.props.${key}`,
3299
+ message: `javascript: URLs are not allowed in ${key}`
3300
+ });
3301
+ }
3302
+ if (key.startsWith("on") && key.length > 2 && key[2] === key[2].toUpperCase()) {
3303
+ if (typeof value === "string") {
3304
+ errors.push({
3305
+ path: `${path}.props.${key}`,
3306
+ message: `String event handler: ${key} must be a function, not a string`
3307
+ });
3308
+ }
3309
+ }
3310
+ }
3311
+ if (jsx.children) {
3312
+ for (let i = 0; i < jsx.children.length; i++) {
3313
+ const child = jsx.children[i];
3314
+ if (typeof child === "string") continue;
3315
+ const childErrors = sanitizeJSX(child, `${path}.children[${i}]`);
3316
+ errors.push(...childErrors);
3317
+ }
3318
+ }
3319
+ return errors;
3320
+ }
3321
+ function isJSXSafe(jsx) {
3322
+ return sanitizeJSX(jsx).length === 0;
3323
+ }
3324
+ function validateFormComponents(jsx, allowedComponents, path = "root") {
3325
+ const errors = [];
3326
+ if (path === "root" && jsx.component !== "Form" && jsx.component !== "form") {
3327
+ errors.push({
3328
+ path,
3329
+ message: "ask() root must be a <Form> component"
3330
+ });
3331
+ }
3332
+ if (jsx.children) {
3333
+ for (let i = 0; i < jsx.children.length; i++) {
3334
+ const child = jsx.children[i];
3335
+ if (typeof child === "string") continue;
3336
+ if (!allowedComponents.has(child.component) && child.component !== "Form" && child.component !== "form") {
3337
+ errors.push({
3338
+ path: `${path}.children[${i}]`,
3339
+ message: `Unknown form component: <${child.component}>. Only registered input components are allowed.`
3340
+ });
3341
+ }
3342
+ const childErrors = validateFormComponents(child, allowedComponents, `${path}.children[${i}]`);
3343
+ errors.push(...childErrors);
3344
+ }
3345
+ }
3346
+ return errors;
3347
+ }
3348
+
3349
+ // src/catalog/index.ts
3350
+ var BUILTIN_MODULE_IDS = [
3351
+ "path",
3352
+ "date",
3353
+ "crypto",
3354
+ "json",
3355
+ "csv",
3356
+ "env",
3357
+ "fs",
3358
+ "fetch",
3359
+ "shell",
3360
+ "image",
3361
+ "db"
3362
+ ];
3363
+ async function loadCatalog(moduleIds) {
3364
+ const ids = moduleIds === "all" ? [...BUILTIN_MODULE_IDS] : moduleIds;
3365
+ const modules = [];
3366
+ for (const id of ids) {
3367
+ if (!isBuiltinModule(id)) {
3368
+ throw new Error(`Unknown catalog module: ${id}`);
3369
+ }
3370
+ const mod = await importModule(id);
3371
+ modules.push(mod);
3372
+ }
3373
+ return modules;
3374
+ }
3375
+ function isBuiltinModule(id) {
3376
+ return BUILTIN_MODULE_IDS.includes(id);
3377
+ }
3378
+ async function importModule(id) {
3379
+ switch (id) {
3380
+ case "path":
3381
+ return (await import("./path-K3VLZVTH.js")).default;
3382
+ case "date":
3383
+ return (await import("./date-DL6PIRCI.js")).default;
3384
+ case "crypto":
3385
+ return (await import("./crypto-BTVPQPPV.js")).default;
3386
+ case "json":
3387
+ return (await import("./json-VPTMTXR6.js")).default;
3388
+ case "csv":
3389
+ return (await import("./csv-3IL3IKAY.js")).default;
3390
+ case "env":
3391
+ return (await import("./env-PPLEQ47H.js")).default;
3392
+ case "fs":
3393
+ return (await import("./fs-QRXDDXB5.js")).default;
3394
+ case "fetch":
3395
+ return (await import("./fetch-TN4ZJVQW.js")).default;
3396
+ case "shell":
3397
+ return (await import("./shell-NAGRIUA4.js")).default;
3398
+ case "image":
3399
+ return (await import("./image-FGNY3CMJ.js")).default;
3400
+ case "db":
3401
+ return (await import("./db-IVNVHDFE.js")).default;
3402
+ }
3403
+ }
3404
+ function mergeCatalogs(modules) {
3405
+ return modules.flatMap((m) => m.functions);
3406
+ }
3407
+ function getCatalogModule(modules, id) {
3408
+ return modules.find((m) => m.id === id);
3409
+ }
3410
+ function formatCatalogForPrompt(modules) {
3411
+ const lines = [];
3412
+ for (const mod of modules) {
3413
+ lines.push(` # Built-in: ${mod.id}`);
3414
+ for (const fn of mod.functions) {
3415
+ lines.push(` ${fn.name}${fn.signature}`);
3416
+ lines.push(` \u2014 ${fn.description}`);
3417
+ }
3418
+ lines.push("");
3419
+ }
3420
+ return lines.join("\n");
3421
+ }
3422
+
3423
+ // src/knowledge/index.ts
3424
+ import { readFileSync, readdirSync, existsSync } from "fs";
3425
+ import { join } from "path";
3426
+ function mergeKnowledgeTrees(trees) {
3427
+ const domainMap = /* @__PURE__ */ new Map();
3428
+ for (const tree of trees) {
3429
+ for (const domain of tree.domains) {
3430
+ const existing = domainMap.get(domain.slug);
3431
+ if (!existing) {
3432
+ domainMap.set(domain.slug, { ...domain, fields: [...domain.fields] });
3433
+ } else {
3434
+ const fieldSlugs = new Set(existing.fields.map((f) => f.slug));
3435
+ for (const field of domain.fields) {
3436
+ if (!fieldSlugs.has(field.slug)) {
3437
+ existing.fields.push(field);
3438
+ }
3439
+ }
3440
+ }
3441
+ }
3442
+ }
3443
+ return { domains: [...domainMap.values()] };
3444
+ }
3445
+ function buildKnowledgeTree(knowledgeDir) {
3446
+ if (!existsSync(knowledgeDir)) {
3447
+ return { domains: [] };
3448
+ }
3449
+ const domains = [];
3450
+ const entries = readdirSync(knowledgeDir, { withFileTypes: true });
3451
+ for (const entry of entries) {
3452
+ if (!entry.isDirectory()) continue;
3453
+ const domainPath = join(knowledgeDir, entry.name);
3454
+ const domain = readDomain(domainPath, entry.name);
3455
+ if (domain) domains.push(domain);
3456
+ }
3457
+ return { domains };
3458
+ }
3459
+ function readDomain(domainPath, slug) {
3460
+ const configPath = join(domainPath, "config.json");
3461
+ if (!existsSync(configPath)) return null;
3462
+ try {
3463
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
3464
+ const fields = [];
3465
+ const entries = readdirSync(domainPath, { withFileTypes: true });
3466
+ for (const entry of entries) {
3467
+ if (!entry.isDirectory()) continue;
3468
+ const field = readField(join(domainPath, entry.name), entry.name);
3469
+ if (field) fields.push(field);
3470
+ }
3471
+ return {
3472
+ slug,
3473
+ label: config.label ?? slug,
3474
+ description: config.description ?? "",
3475
+ icon: config.icon ?? "",
3476
+ color: config.color ?? "#888888",
3477
+ fields
3478
+ };
3479
+ } catch {
3480
+ return null;
3481
+ }
3482
+ }
3483
+ function readField(fieldPath, slug) {
3484
+ const configPath = join(fieldPath, "config.json");
3485
+ if (!existsSync(configPath)) return null;
3486
+ try {
3487
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
3488
+ const options = [];
3489
+ const entries = readdirSync(fieldPath, { withFileTypes: true });
3490
+ for (const entry of entries) {
3491
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
3492
+ const optionSlug = entry.name.replace(/\.md$/, "");
3493
+ const option = readOptionMeta(join(fieldPath, entry.name), optionSlug);
3494
+ if (option) options.push(option);
3495
+ }
3496
+ options.sort((a, b) => a.order - b.order);
3497
+ return {
3498
+ slug,
3499
+ label: config.label ?? slug,
3500
+ description: config.description ?? "",
3501
+ fieldType: config.fieldType ?? "select",
3502
+ required: config.required ?? false,
3503
+ default: config.default,
3504
+ variableName: config.variableName ?? slug,
3505
+ options
3506
+ };
3507
+ } catch {
3508
+ return null;
3509
+ }
3510
+ }
3511
+ function readOptionMeta(filePath, slug) {
3512
+ try {
3513
+ const content = readFileSync(filePath, "utf-8");
3514
+ const fm = parseFrontmatter(content);
3515
+ return {
3516
+ slug,
3517
+ title: fm.title ?? slug,
3518
+ description: fm.description ?? "",
3519
+ order: fm.order ?? 99
3520
+ };
3521
+ } catch {
3522
+ return null;
3523
+ }
3524
+ }
3525
+ function parseFrontmatter(content) {
3526
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
3527
+ if (!match) return {};
3528
+ const result = {};
3529
+ for (const line of match[1].split("\n")) {
3530
+ const m = line.match(/^(\w[\w-]*)\s*:\s*(.+)$/);
3531
+ if (!m) continue;
3532
+ const [, key, raw] = m;
3533
+ let value = raw.trim();
3534
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3535
+ value = value.slice(1, -1);
3536
+ }
3537
+ if (/^\d+$/.test(value)) value = parseInt(value, 10);
3538
+ result[key] = value;
3539
+ }
3540
+ return result;
3541
+ }
3542
+ function loadKnowledgeFiles(knowledgeDir, selector) {
3543
+ const result = {};
3544
+ for (const [domainSlug, fields] of Object.entries(selector)) {
3545
+ if (typeof fields !== "object" || fields === null) continue;
3546
+ result[domainSlug] = {};
3547
+ for (const [fieldSlug, options] of Object.entries(fields)) {
3548
+ if (typeof options !== "object" || options === null) continue;
3549
+ result[domainSlug][fieldSlug] = {};
3550
+ for (const [optionSlug, selected] of Object.entries(options)) {
3551
+ if (selected !== true) continue;
3552
+ const filePath = join(knowledgeDir, domainSlug, fieldSlug, `${optionSlug}.md`);
3553
+ if (!existsSync(filePath)) continue;
3554
+ try {
3555
+ const content = readFileSync(filePath, "utf-8");
3556
+ const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
3557
+ result[domainSlug][fieldSlug][optionSlug] = body;
3558
+ } catch {
3559
+ }
3560
+ }
3561
+ }
3562
+ }
3563
+ return result;
3564
+ }
3565
+ function formatKnowledgeTreeForPrompt(treeOrTrees) {
3566
+ const trees = Array.isArray(treeOrTrees) ? treeOrTrees : [treeOrTrees];
3567
+ const allDomains = trees.flatMap((t) => t.domains);
3568
+ if (allDomains.length === 0) return "(no knowledge loaded)";
3569
+ const lines = ["<knowledge>"];
3570
+ const hasNames = trees.some((t) => t.name);
3571
+ if (hasNames) {
3572
+ const nonEmpty = trees.filter((t) => t.domains.length > 0);
3573
+ for (const tree of nonEmpty) {
3574
+ lines.push(` <space name="${xmlEsc(tree.name ?? "unknown")}">`);
3575
+ for (const domain of tree.domains) {
3576
+ formatDomainXml(lines, domain, " ");
3577
+ }
3578
+ lines.push(" </space>");
3579
+ }
3580
+ } else {
3581
+ for (const domain of allDomains) {
3582
+ formatDomainXml(lines, domain, " ");
3583
+ }
3584
+ }
3585
+ lines.push("</knowledge>");
3586
+ return lines.join("\n");
3587
+ }
3588
+ function formatDomainXml(lines, domain, indent) {
3589
+ lines.push(`${indent}<domain name="${xmlEsc(domain.slug)}" icon="${xmlEsc(domain.icon)}" label="${xmlEsc(domain.label)}">`);
3590
+ for (const field of domain.fields) {
3591
+ lines.push(`${indent} <field name="${xmlEsc(field.slug)}" type="${xmlEsc(field.fieldType)}" var="${xmlEsc(field.variableName)}">`);
3592
+ for (const option of field.options) {
3593
+ const desc = option.description ? ` \u2014 ${xmlEsc(option.description)}` : "";
3594
+ lines.push(`${indent} <option name="${xmlEsc(option.slug)}">${xmlEsc(option.title)}${desc}</option>`);
3595
+ }
3596
+ lines.push(`${indent} </field>`);
3597
+ }
3598
+ lines.push(`${indent}</domain>`);
3599
+ }
3600
+ function xmlEsc(s) {
3601
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3602
+ }
3603
+
3604
+ // src/knowledge/writer.ts
3605
+ import { mkdirSync, writeFileSync, unlinkSync, existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
3606
+ import { join as join2 } from "path";
3607
+ var MEMORY_DOMAIN_CONFIG = {
3608
+ label: "Memory",
3609
+ description: "Persistent agent memory",
3610
+ icon: "\u{1F9E0}",
3611
+ color: "#9b59b6",
3612
+ renderAs: "section"
3613
+ };
3614
+ var MEMORY_FIELDS = {
3615
+ user: {
3616
+ label: "User",
3617
+ description: "User preferences and context",
3618
+ fieldType: "text",
3619
+ variableName: "userMemory"
3620
+ },
3621
+ project: {
3622
+ label: "Project",
3623
+ description: "Project-specific knowledge",
3624
+ fieldType: "text",
3625
+ variableName: "projectMemory"
3626
+ },
3627
+ feedback: {
3628
+ label: "Feedback",
3629
+ description: "Behavioral guidance",
3630
+ fieldType: "text",
3631
+ variableName: "feedbackMemory"
3632
+ },
3633
+ reference: {
3634
+ label: "Reference",
3635
+ description: "External resource pointers",
3636
+ fieldType: "text",
3637
+ variableName: "referenceMemory"
3638
+ }
3639
+ };
3640
+ function saveKnowledgeFile(knowledgeDir, domain, field, option, content) {
3641
+ const domainDir = join2(knowledgeDir, domain);
3642
+ const fieldDir = join2(domainDir, field);
3643
+ const filePath = join2(fieldDir, `${option}.md`);
3644
+ mkdirSync(fieldDir, { recursive: true });
3645
+ const domainConfigPath = join2(domainDir, "config.json");
3646
+ if (!existsSync2(domainConfigPath)) {
3647
+ if (domain === "memory") {
3648
+ writeFileSync(domainConfigPath, JSON.stringify(MEMORY_DOMAIN_CONFIG, null, 2), "utf-8");
3649
+ } else {
3650
+ writeFileSync(domainConfigPath, JSON.stringify({
3651
+ label: domain.charAt(0).toUpperCase() + domain.slice(1),
3652
+ description: "",
3653
+ icon: "\u{1F4C1}",
3654
+ color: "#888888",
3655
+ renderAs: "section"
3656
+ }, null, 2), "utf-8");
3657
+ }
3658
+ }
3659
+ const fieldConfigPath = join2(fieldDir, "config.json");
3660
+ if (!existsSync2(fieldConfigPath)) {
3661
+ const memoryFieldConfig = MEMORY_FIELDS[field];
3662
+ if (domain === "memory" && memoryFieldConfig) {
3663
+ writeFileSync(fieldConfigPath, JSON.stringify({
3664
+ ...memoryFieldConfig,
3665
+ required: false,
3666
+ renderAs: "field"
3667
+ }, null, 2), "utf-8");
3668
+ } else {
3669
+ writeFileSync(fieldConfigPath, JSON.stringify({
3670
+ label: field.charAt(0).toUpperCase() + field.slice(1),
3671
+ description: "",
3672
+ fieldType: "text",
3673
+ required: false,
3674
+ variableName: field,
3675
+ renderAs: "field"
3676
+ }, null, 2), "utf-8");
3677
+ }
3678
+ }
3679
+ const title = option.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3680
+ const hasFrontmatter = content.trimStart().startsWith("---");
3681
+ let fileContent;
3682
+ if (hasFrontmatter) {
3683
+ fileContent = content;
3684
+ } else {
3685
+ fileContent = `---
3686
+ title: ${title}
3687
+ description: ${content.slice(0, 80).replace(/\n/g, " ")}
3688
+ order: 99
3689
+ ---
3690
+
3691
+ ${content}
3692
+ `;
3693
+ }
3694
+ writeFileSync(filePath, fileContent, "utf-8");
3695
+ }
3696
+ function deleteKnowledgeFile(knowledgeDir, domain, field, option) {
3697
+ const filePath = join2(knowledgeDir, domain, field, `${option}.md`);
3698
+ if (!existsSync2(filePath)) return false;
3699
+ unlinkSync(filePath);
3700
+ return true;
3701
+ }
3702
+ function ensureMemoryDomain(knowledgeDir) {
3703
+ if (!existsSync2(knowledgeDir)) {
3704
+ mkdirSync(knowledgeDir, { recursive: true });
3705
+ }
3706
+ const memoryDir = join2(knowledgeDir, "memory");
3707
+ mkdirSync(memoryDir, { recursive: true });
3708
+ const domainConfigPath = join2(memoryDir, "config.json");
3709
+ if (!existsSync2(domainConfigPath)) {
3710
+ writeFileSync(domainConfigPath, JSON.stringify(MEMORY_DOMAIN_CONFIG, null, 2), "utf-8");
3711
+ }
3712
+ for (const [fieldSlug, fieldConfig] of Object.entries(MEMORY_FIELDS)) {
3713
+ const fieldDir = join2(memoryDir, fieldSlug);
3714
+ mkdirSync(fieldDir, { recursive: true });
3715
+ const fieldConfigPath = join2(fieldDir, "config.json");
3716
+ if (!existsSync2(fieldConfigPath)) {
3717
+ writeFileSync(fieldConfigPath, JSON.stringify({
3718
+ ...fieldConfig,
3719
+ required: false,
3720
+ renderAs: "field"
3721
+ }, null, 2), "utf-8");
3722
+ }
3723
+ }
3724
+ }
3725
+ function parseFieldPath(fieldParam) {
3726
+ const parts = fieldParam.split("/");
3727
+ if (parts.length >= 2) {
3728
+ return { domain: parts[0], field: parts.slice(1).join("/") };
3729
+ }
3730
+ return { domain: "memory", field: parts[0] };
3731
+ }
3732
+ export {
3733
+ AgentRegistry,
3734
+ AsyncManager,
3735
+ ConversationRecorder,
3736
+ FunctionRegistry,
3737
+ HookRegistry,
3738
+ KNOWLEDGE_TAG,
3739
+ Sandbox,
3740
+ Session,
3741
+ StreamController,
3742
+ buildErrorMessage,
3743
+ buildHookInterruptMessage,
3744
+ buildInterventionMessage,
3745
+ buildKnowledgeTree,
3746
+ buildStopMessage,
3747
+ buildSummaryComment,
3748
+ buildSystemPrompt,
3749
+ buildTaskContinueMessage,
3750
+ buildTaskOrderViolationMessage,
3751
+ buildTasklistReminderMessage,
3752
+ clear,
3753
+ compressCodeWindow,
3754
+ computeScopeDelta,
3755
+ createBracketState,
3756
+ createDefaultConfig,
3757
+ createGlobals,
3758
+ createLineAccumulator,
3759
+ decayErrorMessage,
3760
+ decayKnowledgeValue,
3761
+ decayStopPayload,
3762
+ deleteKnowledgeFile,
3763
+ describeType2 as describeType,
3764
+ detectGlobalCall,
3765
+ ensureMemoryDomain,
3766
+ executeHooks,
3767
+ executeLine,
3768
+ extractDeclarations,
3769
+ extractVariableNames,
3770
+ feed,
3771
+ feedChunk,
3772
+ findMatches,
3773
+ flush,
3774
+ formatCatalogForPrompt,
3775
+ formatKnowledgeTreeForPrompt,
3776
+ generateAgentsBlock,
3777
+ generateCurrentTaskBlock,
3778
+ generateScopeTable,
3779
+ generateTasksBlock,
3780
+ getCatalogModule,
3781
+ getDecayLevel,
3782
+ getKnowledgeDecayLevel,
3783
+ isBalanced,
3784
+ isCompleteStatement,
3785
+ isJSXSafe,
3786
+ isKnowledgeContent,
3787
+ loadCatalog,
3788
+ loadKnowledgeFiles,
3789
+ matchPattern,
3790
+ mergeCatalogs,
3791
+ mergeConfig,
3792
+ mergeKnowledgeTrees,
3793
+ parseFieldPath,
3794
+ parseStatement,
3795
+ recoverArgumentNames,
3796
+ renderTaskLine,
3797
+ resetBracketState,
3798
+ sanitizeJSX,
3799
+ saveKnowledgeFile,
3800
+ serialize,
3801
+ serializeTasklistsState,
3802
+ tagAsKnowledge,
3803
+ transpile,
3804
+ truncateValue2 as truncateValue,
3805
+ updateScopeInPrompt,
3806
+ validateConfig,
3807
+ validateFormComponents,
3808
+ wrapFunction
3809
+ };