@rowan-agent/agent 0.4.9 → 0.4.11

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.cjs DELETED
@@ -1,4474 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
- };
11
- var __export = (target, all) => {
12
- for (var name in all)
13
- __defProp(target, name, { get: all[name], enumerable: true });
14
- };
15
- var __copyProps = (to, from, except, desc) => {
16
- if (from && typeof from === "object" || typeof from === "function") {
17
- for (let key of __getOwnPropNames(from))
18
- if (!__hasOwnProp.call(to, key) && key !== except)
19
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
- }
21
- return to;
22
- };
23
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
- // If the importer is in node compatibility mode or this is not an ESM
25
- // file that has been converted to a CommonJS file using a Babel-
26
- // compatible transform (i.e. "__esModule" has not been set), then set
27
- // "default" to the CommonJS "module.exports" for node compatibility.
28
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
- mod
30
- ));
31
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
-
33
- // src/harness/env/path.ts
34
- var path_exports = {};
35
- __export(path_exports, {
36
- BINARY_WORKSPACE_DIR: () => BINARY_WORKSPACE_DIR,
37
- PACKAGED_ENV: () => PACKAGED_ENV,
38
- RUNTIME_ENV: () => RUNTIME_ENV,
39
- WORKSPACE_ENV: () => WORKSPACE_ENV,
40
- detectRuntimeMode: () => detectRuntimeMode,
41
- findSourceWorkspaceRoot: () => findSourceWorkspaceRoot,
42
- normalizeRelativePath: () => normalizeRelativePath,
43
- resolveInWorkspace: () => resolveInWorkspace,
44
- resolveProjectRowanDir: () => resolveProjectRowanDir,
45
- resolveWorkspacePath: () => resolveWorkspacePath,
46
- resolveWorkspacePaths: () => resolveWorkspacePaths,
47
- resolveWorkspaceRoot: () => resolveWorkspaceRoot
48
- });
49
- function nonEmptyEnv(env, key) {
50
- const value = env[key]?.trim();
51
- return value ? value : void 0;
52
- }
53
- function isTruthyEnvValue(value) {
54
- return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
55
- }
56
- function resolveUserPath(path, homeDir) {
57
- if (path === "~") {
58
- return homeDir;
59
- }
60
- if (path.startsWith("~/") || path.startsWith("~\\")) {
61
- return (0, import_node_path.join)(homeDir, path.slice(2));
62
- }
63
- return (0, import_node_path.resolve)(path);
64
- }
65
- function isSourceWorkspaceRoot(path) {
66
- const packagePath = (0, import_node_path.join)(path, "package.json");
67
- if (!(0, import_node_fs.existsSync)(packagePath)) {
68
- return false;
69
- }
70
- try {
71
- const manifest = JSON.parse((0, import_node_fs.readFileSync)(packagePath, "utf8"));
72
- return manifest.name === "rowan-agent" || Array.isArray(manifest.workspaces);
73
- } catch {
74
- return false;
75
- }
76
- }
77
- function findSourceWorkspaceRoot(startDir = process.cwd()) {
78
- let current = (0, import_node_path.resolve)(startDir);
79
- const { root } = (0, import_node_path.parse)(current);
80
- while (true) {
81
- if (isSourceWorkspaceRoot(current)) {
82
- return current;
83
- }
84
- if (current === root) {
85
- return (0, import_node_path.resolve)(startDir);
86
- }
87
- current = (0, import_node_path.dirname)(current);
88
- }
89
- }
90
- function detectRuntimeMode(input = {}) {
91
- const env = input.env ?? process.env;
92
- const explicitMode = nonEmptyEnv(env, RUNTIME_ENV)?.toLowerCase();
93
- if (explicitMode === "source" || explicitMode === "binary") {
94
- return explicitMode;
95
- }
96
- if (isTruthyEnvValue(nonEmptyEnv(env, PACKAGED_ENV))) {
97
- return "binary";
98
- }
99
- const executable = (0, import_node_path.basename)(input.execPath ?? process.execPath).toLowerCase().replace(/\.exe$/, "");
100
- return executable === "bun" ? "source" : "binary";
101
- }
102
- function defaultWorkspaceStartDir(options) {
103
- if (options.cwd) {
104
- return options.cwd;
105
- }
106
- return process.cwd();
107
- }
108
- function resolveWorkspaceRoot(options = {}) {
109
- const env = options.env ?? process.env;
110
- const homeDir = options.homeDir ?? (0, import_node_os.homedir)();
111
- const override = nonEmptyEnv(env, WORKSPACE_ENV);
112
- if (override) {
113
- return resolveUserPath(override, homeDir);
114
- }
115
- return findSourceWorkspaceRoot(defaultWorkspaceStartDir(options));
116
- }
117
- function resolveWorkspacePaths(options = {}) {
118
- const mode = options.mode ?? detectRuntimeMode(options);
119
- const cwd = resolveWorkspaceRoot({ ...options, mode });
120
- return {
121
- mode,
122
- cwd,
123
- rowanDir: resolveProjectRowanDir(cwd, options.rowanDir)
124
- };
125
- }
126
- function resolveProjectRowanDir(cwd, rowanDir = BINARY_WORKSPACE_DIR) {
127
- const inputPath = rowanDir.trim() || BINARY_WORKSPACE_DIR;
128
- if ((0, import_node_path.isAbsolute)(inputPath)) {
129
- throw new Error(`Project Rowan dir must be a relative path: ${rowanDir}`);
130
- }
131
- return resolveWorkspacePath({ root: cwd }, inputPath).absolutePath;
132
- }
133
- function resolveInWorkspace(path, rootOrPaths) {
134
- if (path === "~" || path.startsWith("~/") || path.startsWith("~\\")) {
135
- return resolveUserPath(path, (0, import_node_os.homedir)());
136
- }
137
- if ((0, import_node_path.isAbsolute)(path)) {
138
- return path;
139
- }
140
- const root = typeof rootOrPaths === "string" ? rootOrPaths : rootOrPaths.cwd;
141
- return (0, import_node_path.resolve)(root, path);
142
- }
143
- function normalizeRelativePath(path) {
144
- return path.split(import_node_path.sep).join("/");
145
- }
146
- function normalizeWorkspaceInputPath(path = ".") {
147
- const trimmed = path.trim();
148
- if (!trimmed || trimmed === "/" || trimmed === "\\") {
149
- return ".";
150
- }
151
- return path;
152
- }
153
- function resolveWorkspacePath(context, path = ".") {
154
- const root = (0, import_node_path.resolve)(context.root);
155
- const inputPath = normalizeWorkspaceInputPath(path);
156
- const absolutePath = (0, import_node_path.resolve)(root, inputPath);
157
- const relativePath = (0, import_node_path.relative)(root, absolutePath);
158
- if (relativePath === ".." || relativePath.startsWith(`..${import_node_path.sep}`) || (0, import_node_path.isAbsolute)(relativePath)) {
159
- throw new Error(`Path escapes workspace root: ${path}`);
160
- }
161
- return {
162
- root,
163
- inputPath,
164
- absolutePath,
165
- relativePath: normalizeRelativePath(relativePath || ".")
166
- };
167
- }
168
- var import_node_fs, import_node_os, import_node_path, WORKSPACE_ENV, RUNTIME_ENV, PACKAGED_ENV, BINARY_WORKSPACE_DIR;
169
- var init_path = __esm({
170
- "src/harness/env/path.ts"() {
171
- "use strict";
172
- import_node_fs = require("fs");
173
- import_node_os = require("os");
174
- import_node_path = require("path");
175
- WORKSPACE_ENV = "ROWAN_WORKSPACE";
176
- RUNTIME_ENV = "ROWAN_RUNTIME";
177
- PACKAGED_ENV = "ROWAN_PACKAGED";
178
- BINARY_WORKSPACE_DIR = ".rowan";
179
- }
180
- });
181
-
182
- // src/harness/loader.ts
183
- function parseFrontmatter(raw) {
184
- const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
185
- if (!match) return { frontmatter: {}, body: raw };
186
- const lines = match[1].split("\n");
187
- const frontmatter = {};
188
- for (let i = 0; i < lines.length; i++) {
189
- const line = lines[i];
190
- const idx = line.indexOf(":");
191
- if (idx === -1) continue;
192
- const key = line.slice(0, idx).trim();
193
- const value = line.slice(idx + 1).trim();
194
- if (!key) continue;
195
- if (value === "" && i + 1 < lines.length) {
196
- const nested = {};
197
- while (i + 1 < lines.length) {
198
- const next = lines[i + 1];
199
- if (!next.startsWith(" ") || next.trim() === "") break;
200
- const nIdx = next.indexOf(":");
201
- if (nIdx === -1) break;
202
- const nKey = next.slice(0, nIdx).trim();
203
- const nValue = next.slice(nIdx + 1).trim();
204
- nested[nKey] = nValue;
205
- i++;
206
- }
207
- if (Object.keys(nested).length) {
208
- frontmatter[key] = nested;
209
- }
210
- continue;
211
- }
212
- frontmatter[key] = parseValue(value);
213
- }
214
- return { frontmatter, body: raw.slice(match[0].length) };
215
- }
216
- function parseValue(value) {
217
- const arrayMatch = value.match(/^\[(.*)\]$/);
218
- if (arrayMatch) {
219
- return arrayMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
220
- }
221
- if (value.toLowerCase() === "true") return true;
222
- if (value.toLowerCase() === "false") return false;
223
- if (/^\d+$/.test(value)) return Number(value);
224
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
225
- return value.slice(1, -1);
226
- }
227
- return value;
228
- }
229
- async function loadMarkdown(filePath) {
230
- const raw = await (0, import_promises.readFile)(filePath, "utf8");
231
- const { frontmatter, body } = parseFrontmatter(raw);
232
- return { frontmatter, body, raw };
233
- }
234
- function isExplicitPath(input) {
235
- return input.includes("/") || input.includes("\\") || Boolean((0, import_node_path2.extname)(input));
236
- }
237
- function inferResourceName(path, markerFile) {
238
- const file = (0, import_node_path2.basename)(path);
239
- if (file.toLowerCase() === markerFile.toLowerCase()) {
240
- return (0, import_node_path2.basename)((0, import_node_path2.dirname)(path));
241
- }
242
- const extension = (0, import_node_path2.extname)(file);
243
- return extension ? file.slice(0, -extension.length) : file;
244
- }
245
- function resolveResourcePath(input, resourceType, markerFile, workspace) {
246
- const ws = workspace ?? resolveWorkspacePaths();
247
- if ((0, import_node_path2.isAbsolute)(input)) {
248
- return input;
249
- }
250
- if (!isExplicitPath(input)) {
251
- return (0, import_node_path2.join)(ws.rowanDir, resourceType, input, markerFile);
252
- }
253
- const workspacePath = resolveInWorkspace(input, ws);
254
- if ((0, import_node_fs2.existsSync)(workspacePath)) {
255
- return workspacePath;
256
- }
257
- return (0, import_node_path2.resolve)(input);
258
- }
259
- var import_node_fs2, import_promises, import_node_path2;
260
- var init_loader = __esm({
261
- "src/harness/loader.ts"() {
262
- "use strict";
263
- import_node_fs2 = require("fs");
264
- import_promises = require("fs/promises");
265
- import_node_path2 = require("path");
266
- init_path();
267
- }
268
- });
269
-
270
- // src/harness/config.ts
271
- function interpolateEnvVars(value) {
272
- return value.replace(ENV_VAR_PATTERN, (match, varName) => {
273
- const resolved = process.env[varName];
274
- if (resolved === void 0 || resolved === "") {
275
- throw new Error(
276
- `Environment variable "${varName}" is not set but is referenced in config.yaml via "${match}".`
277
- );
278
- }
279
- return resolved;
280
- });
281
- }
282
- async function loadConfigFile(workspace) {
283
- let configPath;
284
- let configFilename;
285
- for (const candidate of CONFIG_CANDIDATES) {
286
- const p = (0, import_node_path3.join)(workspace.rowanDir, candidate);
287
- if ((0, import_node_fs3.existsSync)(p)) {
288
- configPath = p;
289
- configFilename = candidate;
290
- break;
291
- }
292
- }
293
- if (!configPath || !configFilename) {
294
- return void 0;
295
- }
296
- const raw = await (0, import_promises2.readFile)(configPath, "utf8");
297
- let parsed;
298
- try {
299
- parsed = (0, import_yaml.parse)(raw);
300
- } catch (error) {
301
- const message = error instanceof Error ? error.message : String(error);
302
- throw new Error(`Failed to parse .rowan/${configFilename}: ${message}`);
303
- }
304
- if (parsed === null || parsed === void 0) {
305
- return void 0;
306
- }
307
- return validateConfigFile(parsed, configFilename);
308
- }
309
- function validateConfigFile(parsed, configFilename) {
310
- if (typeof parsed !== "object" || parsed === null) {
311
- throw new Error(`.rowan/${configFilename} must be a YAML object at the top level.`);
312
- }
313
- const obj = parsed;
314
- const providers = obj.providers;
315
- if (!Array.isArray(providers)) {
316
- throw new Error(`.rowan/${configFilename} must have a "providers" array.`);
317
- }
318
- const validatedProviders = providers.map((p, i) => {
319
- if (typeof p !== "object" || p === null) {
320
- throw new Error(`providers[${i}] must be an object.`);
321
- }
322
- const provider = p;
323
- if (typeof provider.id !== "string" || !provider.id) {
324
- throw new Error(`providers[${i}].id is required and must be a non-empty string.`);
325
- }
326
- if (typeof provider.baseUrl !== "string" || !provider.baseUrl) {
327
- throw new Error(`providers[${i}].baseUrl is required and must be a non-empty string.`);
328
- }
329
- if (typeof provider.apiKey !== "string" || !provider.apiKey) {
330
- throw new Error(`providers[${i}].apiKey is required and must be a non-empty string.`);
331
- }
332
- if (typeof provider.protocol !== "string" || !provider.protocol) {
333
- throw new Error(`providers[${i}].protocol is required and must be a non-empty string.`);
334
- }
335
- if (!Array.isArray(provider.models) || provider.models.length === 0) {
336
- throw new Error(`providers[${i}].models must be a non-empty array.`);
337
- }
338
- const interpolatedApiKey = interpolateEnvVars(provider.apiKey);
339
- const validatedModels = provider.models.map((m, j) => {
340
- if (typeof m !== "object" || m === null) {
341
- throw new Error(`providers[${i}].models[${j}] must be an object.`);
342
- }
343
- const model = m;
344
- if (typeof model.id !== "string" || !model.id) {
345
- throw new Error(`providers[${i}].models[${j}].id is required and must be a non-empty string.`);
346
- }
347
- return model;
348
- });
349
- return {
350
- id: provider.id,
351
- baseUrl: provider.baseUrl,
352
- apiKey: interpolatedApiKey,
353
- protocol: provider.protocol,
354
- ...typeof provider.name === "string" ? { name: provider.name } : {},
355
- ...typeof provider.timeoutMs === "number" ? { timeoutMs: provider.timeoutMs } : {},
356
- ...typeof provider.maxRetries === "number" ? { maxRetries: provider.maxRetries } : {},
357
- ...typeof provider.retryDelayMs === "number" ? { retryDelayMs: provider.retryDelayMs } : {},
358
- ...provider.headers && typeof provider.headers === "object" ? { headers: provider.headers } : {},
359
- models: validatedModels
360
- };
361
- });
362
- const result = { providers: validatedProviders };
363
- if (obj.model && typeof obj.model === "object") {
364
- const modelRef = obj.model;
365
- if (typeof modelRef.provider !== "string" || typeof modelRef.id !== "string") {
366
- throw new Error(`.rowan/${configFilename} "model" must have "provider" and "id" string fields.`);
367
- }
368
- result.model = { provider: modelRef.provider, id: modelRef.id };
369
- }
370
- if (obj.logLevel !== void 0) {
371
- const valid = ["debug", "info", "warn", "error", "silent"];
372
- if (typeof obj.logLevel !== "string" || !valid.includes(obj.logLevel)) {
373
- throw new Error(`.rowan/${configFilename} "logLevel" must be one of: ${valid.join(", ")}.`);
374
- }
375
- result.logLevel = obj.logLevel;
376
- }
377
- return result;
378
- }
379
- function resolveDefaultModel(config) {
380
- if (config.model) {
381
- return { provider: config.model.provider, id: config.model.id };
382
- }
383
- for (const provider of config.providers) {
384
- for (const model of provider.models) {
385
- if (model.primary) {
386
- return { provider: provider.id, id: model.id };
387
- }
388
- }
389
- }
390
- for (const provider of config.providers) {
391
- if (provider.models.length > 0) {
392
- return { provider: provider.id, id: provider.models[0].id };
393
- }
394
- }
395
- return void 0;
396
- }
397
- function registerConfigModels(config) {
398
- for (const provider of config.providers) {
399
- for (const fileModel of provider.models) {
400
- const model = {
401
- id: fileModel.id,
402
- name: fileModel.name ?? fileModel.id,
403
- protocol: provider.protocol,
404
- provider: provider.id,
405
- baseUrl: provider.baseUrl,
406
- reasoning: fileModel.reasoning ?? DEFAULT_REASONING,
407
- input: fileModel.input ?? DEFAULT_INPUT,
408
- cost: { ...DEFAULT_COST, ...fileModel.cost },
409
- contextWindow: fileModel.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
410
- maxTokens: fileModel.maxTokens ?? DEFAULT_MAX_TOKENS,
411
- apiKey: provider.apiKey,
412
- timeoutMs: provider.timeoutMs ?? DEFAULT_TIMEOUT_MS,
413
- ...provider.maxRetries !== void 0 ? { maxRetries: provider.maxRetries } : {},
414
- ...provider.retryDelayMs !== void 0 ? { retryDelayMs: provider.retryDelayMs } : {},
415
- ...provider.headers ? { headers: provider.headers } : {}
416
- };
417
- (0, import_models.registerModel)(model);
418
- }
419
- }
420
- }
421
- function parseModelRef(input) {
422
- if (!input) return void 0;
423
- const slashIndex = input.indexOf("/");
424
- if (slashIndex === -1) {
425
- return { provider: "*", id: input };
426
- }
427
- return {
428
- provider: input.slice(0, slashIndex),
429
- id: input.slice(slashIndex + 1)
430
- };
431
- }
432
- var import_node_fs3, import_promises2, import_node_path3, import_yaml, import_models, DEFAULT_REASONING, DEFAULT_INPUT, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS, DEFAULT_COST, DEFAULT_TIMEOUT_MS, ENV_VAR_PATTERN, CONFIG_CANDIDATES;
433
- var init_config = __esm({
434
- "src/harness/config.ts"() {
435
- "use strict";
436
- import_node_fs3 = require("fs");
437
- import_promises2 = require("fs/promises");
438
- import_node_path3 = require("path");
439
- import_yaml = require("yaml");
440
- import_models = require("@rowan-agent/models");
441
- DEFAULT_REASONING = false;
442
- DEFAULT_INPUT = ["text"];
443
- DEFAULT_CONTEXT_WINDOW = 128e3;
444
- DEFAULT_MAX_TOKENS = 16384;
445
- DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
446
- DEFAULT_TIMEOUT_MS = 6e4;
447
- ENV_VAR_PATTERN = /\$\{([^}]+)\}/g;
448
- CONFIG_CANDIDATES = ["config.yaml", "config.yml"];
449
- }
450
- });
451
-
452
- // src/harness/context/resource-formatter.ts
453
- function escapeXml(value) {
454
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
455
- }
456
- function buildStructuredSection(tag, items) {
457
- const lines = [];
458
- for (const item of items) {
459
- lines.push(` <${tag}>`);
460
- for (const [key, value] of Object.entries(item)) {
461
- lines.push(` <${key}>${escapeXml(value)}</${key}>`);
462
- }
463
- lines.push(` </${tag}>`);
464
- }
465
- return lines.join("\n");
466
- }
467
- function buildSkillsDescription(skills) {
468
- const visibleSkills = skills.filter((skill) => !skill.disableModelInvocation);
469
- if (visibleSkills.length === 0) return "";
470
- const lines = ["<available_skills>"];
471
- for (const skill of visibleSkills) {
472
- lines.push(" <skill>");
473
- lines.push(` <name>${escapeXml(skill.name)}</name>`);
474
- lines.push(` <description>${escapeXml(skill.description)}</description>`);
475
- lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
476
- lines.push(" </skill>");
477
- }
478
- lines.push("</available_skills>");
479
- return lines.join("\n");
480
- }
481
- function formatResourceOutput(resource) {
482
- const parts = [`<${resource.type} name="${escapeXml(resource.name)}" location="${escapeXml(resource.location)}">`];
483
- if (resource.baseDir) {
484
- parts.push(`References are relative to ${resource.baseDir}.`);
485
- parts.push("");
486
- }
487
- parts.push(resource.content);
488
- parts.push(`</${resource.type}>`);
489
- return parts.join("\n");
490
- }
491
- function detectResourceType(filePath) {
492
- const normalized = filePath.replace(/\\/g, "/");
493
- if (normalized.includes("/.rowan/skills/") && normalized.endsWith("/SKILL.md")) return "skill";
494
- if (normalized.includes("/.rowan/phases/") && normalized.endsWith("/PHASE.md")) return "phase";
495
- if (normalized.endsWith(".md")) return "markdown";
496
- if (/\.(ts|js|tsx|jsx|py|rs|go|java|c|cpp|rb|sh|sql|yaml|yml|json|toml)$/.test(normalized)) return "code";
497
- return "file";
498
- }
499
- function jsonToXml(value, depth) {
500
- const indent = " ".repeat(depth);
501
- if (value === null || value === void 0) return "";
502
- if (typeof value !== "object") return `${indent}${escapeXml(String(value))}`;
503
- if (Array.isArray(value)) {
504
- return value.map((item) => {
505
- if (typeof item === "object" && item !== null) {
506
- return `${indent}<item>
507
- ${jsonToXml(item, depth + 1)}
508
- ${indent}</item>`;
509
- }
510
- return `${indent}<item>${escapeXml(String(item))}</item>`;
511
- }).join("\n");
512
- }
513
- return Object.entries(value).map(([key, val]) => {
514
- const tag = escapeXml(key);
515
- if (typeof val === "object" && val !== null) {
516
- return `${indent}<${tag}>
517
- ${jsonToXml(val, depth + 1)}
518
- ${indent}</${tag}>`;
519
- }
520
- return `${indent}<${tag}>${escapeXml(String(val))}</${tag}>`;
521
- }).join("\n");
522
- }
523
- function buildPhaseDirectiveMessage(phase, output, toolUseId) {
524
- const parts = [];
525
- parts.push(`<phase name="${escapeXml(phase.name)}">`);
526
- parts.push(` <content>${phase.content}</content>`);
527
- if (output.results && output.results.length > 0) {
528
- parts.push(` <prev_phase_outputs>`);
529
- if (output.instruction) {
530
- parts.push(` <instruction>${escapeXml(output.instruction)}</instruction>`);
531
- }
532
- for (const r of output.results) {
533
- parts.push(` <phase name="${escapeXml(r.name)}">`);
534
- if (r.output !== void 0) {
535
- parts.push(jsonToXml(r.output, 3));
536
- }
537
- parts.push(` </phase>`);
538
- }
539
- parts.push(` </prev_phase_outputs>`);
540
- }
541
- parts.push(`</phase>`);
542
- return [{
543
- type: "tool_result",
544
- toolUseId,
545
- content: parts.join("\n"),
546
- isError: false
547
- }];
548
- }
549
- var init_resource_formatter = __esm({
550
- "src/harness/context/resource-formatter.ts"() {
551
- "use strict";
552
- }
553
- });
554
-
555
- // src/harness/phases/loader.ts
556
- var loader_exports = {};
557
- __export(loader_exports, {
558
- loadPhase: () => loadPhase,
559
- loadPhases: () => loadPhases,
560
- readPhaseContent: () => readPhaseContent,
561
- reloadPhases: () => reloadPhases
562
- });
563
- async function loadPhase(input, workspace) {
564
- const resolved = resolveResourcePath(input, PHASE_DIR, PHASE_MARKER, workspace);
565
- const { frontmatter, body } = await loadMarkdown(resolved);
566
- const id = inferResourceName(resolved, PHASE_MARKER);
567
- if (!frontmatter.name) {
568
- throw new Error(`Phase "${id}" at "${resolved}" is missing required field "name" in PHASE.md frontmatter.`);
569
- }
570
- if (!frontmatter.description) {
571
- throw new Error(`Phase "${id}" at "${resolved}" is missing required field "description" in PHASE.md frontmatter.`);
572
- }
573
- const baseDir = (0, import_node_path4.dirname)(resolved);
574
- const phase = {
575
- id,
576
- name: frontmatter.name,
577
- description: frontmatter.description,
578
- tools: frontmatter.tools,
579
- skills: frontmatter.skills,
580
- target: frontmatter.target,
581
- input: frontmatter.input,
582
- isolated: frontmatter.isolated,
583
- filePath: resolved,
584
- baseDir,
585
- content: body,
586
- model: parseModelRef(frontmatter.model)
587
- };
588
- const codePath = await discoverPhaseCode(baseDir);
589
- if (codePath) {
590
- const code = await loadPhaseCode(codePath);
591
- if (code.factory) {
592
- phase.factory = code.factory;
593
- } else if (code.run) {
594
- phase.run = code.run;
595
- }
596
- }
597
- return phase;
598
- }
599
- function readPhaseContent(phase) {
600
- return formatResourceOutput({
601
- type: "phase",
602
- name: phase.name,
603
- location: phase.filePath,
604
- content: phase.content,
605
- baseDir: phase.baseDir
606
- });
607
- }
608
- async function loadPhases(workspace, paths) {
609
- const phases = /* @__PURE__ */ new Map();
610
- if (paths && paths.length > 0) {
611
- for (const path of paths) {
612
- try {
613
- const phase = await loadPhase(path, workspace);
614
- phases.set(phase.id, phase);
615
- } catch (error) {
616
- console.warn(`Failed to load phase "${path}":`, error);
617
- }
618
- }
619
- return { phases, entryPhaseId: null };
620
- }
621
- const ws = workspace ?? (await Promise.resolve().then(() => (init_path(), path_exports))).resolveWorkspacePaths();
622
- const phasesDir = (0, import_node_path4.join)(ws.rowanDir, PHASE_DIR);
623
- if (!(0, import_node_fs4.existsSync)(phasesDir)) {
624
- return { phases, entryPhaseId: null };
625
- }
626
- const entries = await (0, import_promises3.readdir)(phasesDir, { withFileTypes: true });
627
- for (const entry of entries) {
628
- if (!entry.isDirectory()) continue;
629
- const phaseFile = (0, import_node_path4.join)(phasesDir, entry.name, PHASE_MARKER);
630
- if (!(0, import_node_fs4.existsSync)(phaseFile)) continue;
631
- try {
632
- const phase = await loadPhase(entry.name, ws);
633
- phases.set(phase.id, phase);
634
- } catch (error) {
635
- console.warn(`Failed to load phase "${entry.name}":`, error);
636
- }
637
- }
638
- return { phases, entryPhaseId: null };
639
- }
640
- async function discoverPhaseCode(baseDir) {
641
- const candidates = ["index.ts", "index.js"];
642
- for (const candidate of candidates) {
643
- const fullPath = (0, import_node_path4.join)(baseDir, candidate);
644
- if ((0, import_node_fs4.existsSync)(fullPath)) {
645
- return fullPath;
646
- }
647
- }
648
- return null;
649
- }
650
- async function loadPhaseCode(codePath) {
651
- const { createJiti: createJiti2 } = await import("jiti");
652
- const jiti = createJiti2(import_meta.url, {
653
- moduleCache: false
654
- });
655
- const mod = await jiti.import(codePath, { default: true });
656
- const fn = typeof mod === "function" ? mod : mod?.default;
657
- if (typeof fn === "function") {
658
- return { factory: fn };
659
- }
660
- if (typeof mod?.run === "function") {
661
- return { run: mod.run };
662
- }
663
- throw new Error(`Phase code at "${codePath}" must export a default function or a run() function.`);
664
- }
665
- async function reloadPhases(registry, workspace) {
666
- for (const [id, phase] of registry.phases) {
667
- if (!phase.filePath) {
668
- continue;
669
- }
670
- try {
671
- const fresh = await loadPhase(phase.filePath, workspace);
672
- registry.phases.set(id, fresh);
673
- } catch (error) {
674
- console.warn(`Failed to reload phase "${id}":`, error);
675
- }
676
- }
677
- }
678
- var import_node_fs4, import_promises3, import_node_path4, import_meta, PHASE_MARKER, PHASE_DIR;
679
- var init_loader2 = __esm({
680
- "src/harness/phases/loader.ts"() {
681
- "use strict";
682
- import_node_fs4 = require("fs");
683
- import_promises3 = require("fs/promises");
684
- import_node_path4 = require("path");
685
- init_loader();
686
- init_config();
687
- init_resource_formatter();
688
- import_meta = {};
689
- PHASE_MARKER = "PHASE.md";
690
- PHASE_DIR = "phases";
691
- }
692
- });
693
-
694
- // src/index.ts
695
- var index_exports = {};
696
- __export(index_exports, {
697
- Agent: () => Agent,
698
- AgentEventStream: () => AgentEventStream,
699
- EventStream: () => EventStream,
700
- ExtensionRunner: () => ExtensionRunner,
701
- HooksManager: () => HooksManager,
702
- LocalJsonlSessionManager: () => LocalJsonlSessionManager,
703
- appendUserTurn: () => appendUserTurn,
704
- buildModelRequest: () => buildModelRequest,
705
- buildSystemPrompt: () => buildSystemPrompt,
706
- conversationMessages: () => conversationMessages,
707
- createCoreTools: () => createCoreTools,
708
- createEventBus: () => createEventBus,
709
- createExtension: () => createExtension,
710
- createExtensionAPI: () => createExtensionAPI,
711
- createExtensionRunner: () => createExtensionRunner,
712
- createExtensionRuntime: () => createExtensionRuntime,
713
- createId: () => createId,
714
- createMessage: () => createMessage,
715
- createSession: () => createSession,
716
- createSourceInfo: () => createSourceInfo,
717
- createTimestamp: () => createTimestamp,
718
- discoverAndLoadExtensions: () => discoverAndLoadExtensions,
719
- getGlobalHooks: () => getGlobalHooks,
720
- interpolateEnvVars: () => interpolateEnvVars,
721
- latestUserInput: () => latestUserInput,
722
- loadConfigFile: () => loadConfigFile,
723
- loadExtensionFromFactory: () => loadExtensionFromFactory,
724
- loadExtensions: () => loadExtensions,
725
- loadPhases: () => loadPhases,
726
- loadSkill: () => loadSkill,
727
- loadSkills: () => loadSkills,
728
- messageContentText: () => messageContentText,
729
- parseModelRef: () => parseModelRef,
730
- registerConfigModels: () => registerConfigModels,
731
- resetGlobalHooks: () => resetGlobalHooks,
732
- resolveDefaultModel: () => resolveDefaultModel,
733
- resolveInWorkspace: () => resolveInWorkspace,
734
- resolveSkillPath: () => resolveSkillPath,
735
- resolveWorkspacePaths: () => resolveWorkspacePaths,
736
- serializeSkills: () => serializeSkills
737
- });
738
- module.exports = __toCommonJS(index_exports);
739
-
740
- // src/utils.ts
741
- function createId(prefix) {
742
- return `${prefix}_${crypto.randomUUID().slice(0, 8)}`;
743
- }
744
- function createTimestamp(date = /* @__PURE__ */ new Date()) {
745
- const offset = date.getTimezoneOffset();
746
- const local = new Date(date.getTime() - offset * 6e4);
747
- const iso = local.toISOString().slice(0, -1);
748
- const sign = offset <= 0 ? "+" : "-";
749
- const abs = Math.abs(offset);
750
- const hh = String(Math.floor(abs / 60)).padStart(2, "0");
751
- const mm = String(abs % 60).padStart(2, "0");
752
- return `${iso}${sign}${hh}:${mm}`;
753
- }
754
-
755
- // src/loop/errors.ts
756
- var EmptyResponseError = class extends Error {
757
- code = "empty_response";
758
- constructor(message = "Model returned an empty response.") {
759
- super(message);
760
- this.name = "EmptyResponseError";
761
- }
762
- };
763
- var LoopGuard = {
764
- /** Returns abort result if signal is aborted */
765
- checkAbort(signal) {
766
- if (signal?.aborted) {
767
- return { stopReason: "aborted", message: "Agent run aborted." };
768
- }
769
- return { stopReason: "none" };
770
- }
771
- };
772
-
773
- // src/loop/outcomes.ts
774
- function parseToolResult(content) {
775
- try {
776
- const parsed = JSON.parse(content);
777
- if (typeof parsed.toolCallId === "string" && typeof parsed.toolName === "string" && typeof parsed.ok === "boolean") {
778
- return {
779
- toolCallId: parsed.toolCallId,
780
- toolName: parsed.toolName,
781
- ok: parsed.ok,
782
- content: parsed.content,
783
- ...parsed.error ? { error: parsed.error } : {}
784
- };
785
- }
786
- } catch {
787
- return void 0;
788
- }
789
- return void 0;
790
- }
791
- function extractToolResults(transcript) {
792
- const results = [];
793
- for (const msg of transcript) {
794
- if (msg.role !== "tool") continue;
795
- if (typeof msg.content === "string") {
796
- const result = parseToolResult(msg.content);
797
- if (result) {
798
- results.push(result);
799
- }
800
- continue;
801
- }
802
- for (const part of msg.content) {
803
- if (part.type !== "tool_result") continue;
804
- const result = parseToolResult(part.content);
805
- if (result) {
806
- results.push(result);
807
- }
808
- }
809
- }
810
- return results;
811
- }
812
- var createOutcome = {
813
- phaseNotFound(output) {
814
- return { id: createId("out"), message: `Phase "${output.phase ?? "Unknown"}" not found.` };
815
- },
816
- default(output, transcript) {
817
- const toolResults = transcript ? extractToolResults(transcript) : [];
818
- const message = output.message || output.routeReason || `${output.phase ?? "Unknown"} phase completed.`;
819
- const outcome = {
820
- id: createId("out"),
821
- message
822
- };
823
- if (toolResults.length > 0) {
824
- outcome.toolResults = toolResults;
825
- }
826
- return outcome;
827
- },
828
- aborted() {
829
- return { id: createId("out"), message: "Agent run aborted." };
830
- },
831
- error(message) {
832
- return { id: createId("out"), message };
833
- }
834
- };
835
-
836
- // src/loop/state.ts
837
- function snapshotMessage(message) {
838
- return {
839
- ...message,
840
- ...message.metadata ? { metadata: { ...message.metadata } } : {}
841
- };
842
- }
843
- function snapshotMessages(messages) {
844
- return messages.map(snapshotMessage);
845
- }
846
-
847
- // src/types.ts
848
- function createMessage(role, content, metadata) {
849
- return {
850
- id: createId("msg"),
851
- role,
852
- content,
853
- createdAt: createTimestamp(),
854
- ...metadata ? { metadata } : {}
855
- };
856
- }
857
- function messageContentText(content) {
858
- if (typeof content === "string") {
859
- return content;
860
- }
861
- return content.map((part) => {
862
- if (part.type === "text") return part.text;
863
- if (part.type === "thinking") return part.thinking;
864
- if (part.type === "tool_result") return part.content;
865
- if (part.type === "tool_use") return JSON.stringify(part.input);
866
- if (part.type === "image") return `[image:${part.mimeType}]`;
867
- return "";
868
- }).filter(Boolean).join("\n");
869
- }
870
- function contentBlocksToMessageContent(blocks) {
871
- return blocks.map((block) => {
872
- if (block.type === "text") {
873
- return { type: "text", text: block.text };
874
- }
875
- if (block.type === "thinking") {
876
- return { type: "thinking", thinking: block.thinking };
877
- }
878
- let input = block.args;
879
- try {
880
- input = JSON.parse(block.args);
881
- } catch {
882
- }
883
- return { type: "tool_use", id: block.id, name: block.name, input };
884
- });
885
- }
886
-
887
- // src/loop/stream-collector.ts
888
- async function invokeModel(input) {
889
- const events = input.config.stream(input.request, { signal: input.config.signal });
890
- const result = await collectStreamResult({
891
- config: input.config,
892
- message: input.message,
893
- events,
894
- metadataPhase: input.phaseId
895
- });
896
- const response = result.doneResponse ?? {
897
- content: result.text,
898
- toolCalls: result.toolCalls.map((tc) => ({
899
- id: tc.id,
900
- name: tc.name,
901
- arguments: tc.args
902
- })),
903
- stopReason: result.stopReason
904
- };
905
- return {
906
- text: result.text,
907
- contentBlocks: result.contentBlocks,
908
- toolCalls: result.toolCalls,
909
- stopReason: result.stopReason,
910
- transcript: {
911
- request: input.request,
912
- response
913
- }
914
- };
915
- }
916
- async function collectStreamResult(input) {
917
- let activeMessageId;
918
- let lastPartial;
919
- let stopReason;
920
- let doneResponse;
921
- for await (const event of input.events) {
922
- const abortResult = LoopGuard.checkAbort(input.config.signal);
923
- if (abortResult.stopReason !== "none") {
924
- return { text: abortResult.message, contentBlocks: [], toolCalls: [], stopReason: "aborted" };
925
- }
926
- if (event.type === "model_requested") {
927
- input.config.emit?.({
928
- type: "model_requested",
929
- model: event.model,
930
- usage: event.usage,
931
- ts: createTimestamp()
932
- });
933
- }
934
- if (event.type === "error") {
935
- throw event.error;
936
- }
937
- if (event.type === "start") {
938
- lastPartial = event.partial;
939
- }
940
- if (event.type === "text_delta") {
941
- lastPartial = event.partial;
942
- if (!activeMessageId) {
943
- activeMessageId = input.message.start("assistant", event.text, {
944
- phase: input.metadataPhase
945
- });
946
- } else {
947
- await input.message.update(activeMessageId, event.text);
948
- }
949
- }
950
- if (event.type === "tool_call_start" || event.type === "tool_call_delta" || event.type === "tool_call_end") {
951
- lastPartial = event.partial;
952
- }
953
- if (event.type === "thinking_delta") {
954
- lastPartial = event.partial;
955
- }
956
- if (event.type === "done") {
957
- doneResponse = event.response;
958
- stopReason = event.response?.stopReason;
959
- const contentBlocks2 = lastPartial?.contentBlocks ?? [];
960
- if (!activeMessageId && contentBlocks2.length > 0) {
961
- activeMessageId = input.message.start("assistant", "", {
962
- phase: input.metadataPhase
963
- });
964
- }
965
- const shouldStoreContentParts = contentBlocks2.some((block) => block.type !== "text");
966
- if (activeMessageId && shouldStoreContentParts) {
967
- input.message.replaceContent(activeMessageId, contentBlocksToMessageContent(contentBlocks2));
968
- }
969
- if (activeMessageId) {
970
- await input.message.end(activeMessageId);
971
- activeMessageId = void 0;
972
- }
973
- }
974
- }
975
- if (activeMessageId) {
976
- await input.message.end(activeMessageId);
977
- }
978
- const hasContent = (lastPartial?.contentBlocks?.length ?? 0) > 0 || (doneResponse?.content?.length ?? 0) > 0;
979
- if (!hasContent && stopReason !== "error" && stopReason !== "aborted") {
980
- throw new EmptyResponseError();
981
- }
982
- if (doneResponse) {
983
- const toolCalls2 = (doneResponse.toolCalls ?? []).map((tc) => ({
984
- id: tc.id,
985
- name: tc.name,
986
- args: tc.arguments
987
- }));
988
- return {
989
- text: doneResponse.content,
990
- contentBlocks: lastPartial?.contentBlocks ?? [],
991
- toolCalls: toolCalls2,
992
- stopReason: doneResponse.stopReason,
993
- doneResponse
994
- };
995
- }
996
- const contentBlocks = lastPartial?.contentBlocks ?? [];
997
- const text = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
998
- const toolCalls = contentBlocks.filter((b) => b.type === "tool_call").map((b) => {
999
- let parsedArgs = b.args;
1000
- try {
1001
- parsedArgs = JSON.parse(b.args);
1002
- } catch {
1003
- }
1004
- return { id: b.id, name: b.name, args: parsedArgs };
1005
- });
1006
- return { text, contentBlocks, toolCalls, stopReason };
1007
- }
1008
-
1009
- // src/extensions/api.ts
1010
- function createExtensionAPI(hooks, _extensionPath, options, runtime, eventBus) {
1011
- let idCounter = 0;
1012
- const createId2 = (prefix) => {
1013
- idCounter++;
1014
- return `${prefix}_${Date.now().toString(36)}_${idCounter}`;
1015
- };
1016
- const formatJson = (value) => {
1017
- try {
1018
- return JSON.stringify(value, null, 2) ?? "undefined";
1019
- } catch {
1020
- return "[unserializable]";
1021
- }
1022
- };
1023
- const assertActive = () => runtime?.assertActive?.();
1024
- const noopContext = {
1025
- cwd: "",
1026
- signal: void 0,
1027
- isIdle: () => false,
1028
- abort: () => {
1029
- },
1030
- exec: async () => ({ exitCode: 1, stdout: "", stderr: "not available" })
1031
- };
1032
- const ctx = options?.context ?? noopContext;
1033
- const phaseIn = options?.phase;
1034
- let outputPayload = phaseIn?.state?.payload;
1035
- let nextPhase;
1036
- let outputMessage;
1037
- return {
1038
- on: (eventType, handler) => {
1039
- assertActive();
1040
- hooks?.on(eventType, handler);
1041
- },
1042
- off: (eventType, handler) => {
1043
- assertActive();
1044
- hooks?.off(eventType, handler);
1045
- },
1046
- registerTool: (tool) => {
1047
- assertActive();
1048
- options?.registerTool?.(tool);
1049
- },
1050
- registerPhase: (registration) => {
1051
- assertActive();
1052
- options?.registerPhase?.(registration);
1053
- },
1054
- registerProvider: (config) => {
1055
- assertActive();
1056
- options?.registerProvider?.(config);
1057
- },
1058
- unregisterProvider: (name) => {
1059
- assertActive();
1060
- options?.unregisterProvider?.(name);
1061
- },
1062
- manifest: options?.manifest,
1063
- utils: {
1064
- createId: createId2,
1065
- formatJson
1066
- },
1067
- context: ctx,
1068
- events: eventBus ?? { on: () => () => {
1069
- }, off: () => {
1070
- }, emit: () => {
1071
- }, has: () => false, count: () => 0 },
1072
- session: {
1073
- getContext: () => {
1074
- if (!options?.session) throw new Error("session.getContext() is not available in this context.");
1075
- return options.session;
1076
- }
1077
- },
1078
- phase: {
1079
- getPayload: () => outputPayload,
1080
- setPayload: (p) => {
1081
- outputPayload = p;
1082
- },
1083
- setMessage: (msg) => {
1084
- outputMessage = msg;
1085
- },
1086
- getCurrentPhase: () => phaseIn?.state?.current ?? "",
1087
- setNextPhase: (id) => {
1088
- nextPhase = id;
1089
- },
1090
- getNextPhase: () => nextPhase,
1091
- getMessage: () => outputMessage
1092
- }
1093
- };
1094
- }
1095
-
1096
- // src/harness/phases/index.ts
1097
- init_loader2();
1098
-
1099
- // src/harness/tools/index.ts
1100
- var import_promises4 = require("fs/promises");
1101
- var import_node_path5 = require("path");
1102
- var import_typebox2 = __toESM(require("typebox"), 1);
1103
- var import_schema = __toESM(require("typebox/schema"), 1);
1104
- init_resource_formatter();
1105
- init_loader();
1106
- init_path();
1107
-
1108
- // src/harness/tools/route-tool.ts
1109
- var import_typebox = __toESM(require("typebox"), 1);
1110
- init_resource_formatter();
1111
- var PhaseRouteTool = "route";
1112
- function buildPhaseEntry(p) {
1113
- const entry = { id: p.id, description: p.description };
1114
- if (p.tools && p.tools.length > 0) {
1115
- entry.available_tools = p.tools.join(", ");
1116
- }
1117
- if (p.skills && p.skills.length > 0) {
1118
- entry.available_skills = p.skills.join(", ");
1119
- }
1120
- if (p.input && Object.keys(p.input).length > 0) {
1121
- entry.required_input = Object.entries(p.input).map(([key, desc]) => `- ${key}: ${desc}`).join("\n");
1122
- }
1123
- return entry;
1124
- }
1125
- function buildRouteDescription(availablePhases) {
1126
- const phasesBlock = buildStructuredSection("phase", [
1127
- ...availablePhases.map(buildPhaseEntry),
1128
- { id: "stop", description: "Terminate the workflow and return final result to the user" }
1129
- ]);
1130
- return [
1131
- "Route execution to one or more phases concurrently, or stop execution.",
1132
- "",
1133
- "Rules:",
1134
- "- `decision` lists phase executions; use phase 'stop' to terminate.",
1135
- "- Each target may include `phase`, `reason`, `payload`.",
1136
- "- A phase may appear multiple times as independent execution instances.",
1137
- "- `payload` MUST match the phase's `payload_schema`",
1138
- "- `instruction` is optional shared guidance for all phases.",
1139
- "- Executions are independent and concurrent; order is irrelevant.",
1140
- "",
1141
- "<available_phases>",
1142
- phasesBlock,
1143
- "</available_phases>"
1144
- ].join("\n");
1145
- }
1146
- function createRouteTool(availablePhases) {
1147
- const DecisionTarget = import_typebox.default.Object({
1148
- phase: import_typebox.default.Union([
1149
- ...availablePhases.map((p) => import_typebox.default.Literal(p.id)),
1150
- import_typebox.default.Literal("stop")
1151
- ]),
1152
- reason: import_typebox.default.Optional(import_typebox.default.String({ description: "Brief reason for this decision" })),
1153
- payload: import_typebox.default.Optional(import_typebox.default.Unknown({ description: "Structured input for the target phase" }))
1154
- });
1155
- return {
1156
- name: PhaseRouteTool,
1157
- description: buildRouteDescription(availablePhases),
1158
- promptSnippet: "Route to next phase on completion.",
1159
- promptGuidelines: [
1160
- "Call route immediately when the phase is complete."
1161
- ],
1162
- parameters: import_typebox.default.Object({
1163
- decision: import_typebox.default.Array(DecisionTarget, { description: "Phase executions to start" }),
1164
- instruction: import_typebox.default.Optional(import_typebox.default.String({ description: "Overall instruction, passed as context" }))
1165
- }),
1166
- // No-op: this tool is intercepted by phases, never executed via tool execution
1167
- execute: async (args, context) => ({
1168
- toolCallId: context.toolCallId,
1169
- toolName: PhaseRouteTool,
1170
- ok: true,
1171
- content: ""
1172
- })
1173
- };
1174
- }
1175
- function extractRouteCall(toolCalls) {
1176
- const routeCall = toolCalls.find((t) => t.name === PhaseRouteTool);
1177
- if (!routeCall) return void 0;
1178
- let args;
1179
- if (typeof routeCall.args === "string") {
1180
- try {
1181
- args = JSON.parse(routeCall.args);
1182
- } catch {
1183
- return void 0;
1184
- }
1185
- } else {
1186
- args = routeCall.args;
1187
- }
1188
- const decisionRaw = args.decision;
1189
- let decision = [];
1190
- if (Array.isArray(decisionRaw)) {
1191
- decision = decisionRaw.map((d) => {
1192
- const obj = d;
1193
- return {
1194
- phase: typeof obj?.phase === "string" ? obj.phase : "stop",
1195
- reason: typeof obj?.reason === "string" ? obj.reason : void 0,
1196
- payload: obj?.payload
1197
- };
1198
- });
1199
- }
1200
- return {
1201
- decision,
1202
- instruction: typeof args.instruction === "string" ? args.instruction : void 0
1203
- };
1204
- }
1205
-
1206
- // src/harness/tools/index.ts
1207
- var DEFAULT_MAX_READ_BYTES = 64e3;
1208
- var DEFAULT_BASH_TIMEOUT_MS = 3e4;
1209
- var DEFAULT_MAX_BASH_OUTPUT_BYTES = 64e3;
1210
- var ReadArgsSchema = import_typebox2.default.Object({
1211
- path: import_typebox2.default.String(),
1212
- maxBytes: import_typebox2.default.Optional(import_typebox2.default.Number()),
1213
- type: import_typebox2.default.Optional(import_typebox2.default.Union([
1214
- import_typebox2.default.Literal("skill"),
1215
- import_typebox2.default.Literal("phase"),
1216
- import_typebox2.default.Literal("markdown"),
1217
- import_typebox2.default.Literal("code"),
1218
- import_typebox2.default.Literal("file")
1219
- ]))
1220
- });
1221
- var ReadArgsValidator = import_schema.default.Compile(ReadArgsSchema);
1222
- var WriteArgsSchema = import_typebox2.default.Object({
1223
- path: import_typebox2.default.String(),
1224
- content: import_typebox2.default.String()
1225
- });
1226
- var WriteArgsValidator = import_schema.default.Compile(WriteArgsSchema);
1227
- var EditArgsSchema = import_typebox2.default.Object({
1228
- path: import_typebox2.default.String(),
1229
- oldText: import_typebox2.default.String(),
1230
- newText: import_typebox2.default.String(),
1231
- replaceAll: import_typebox2.default.Optional(import_typebox2.default.Boolean())
1232
- });
1233
- var EditArgsValidator = import_schema.default.Compile(EditArgsSchema);
1234
- var BashArgsSchema = import_typebox2.default.Object({
1235
- command: import_typebox2.default.String(),
1236
- cwd: import_typebox2.default.Optional(import_typebox2.default.String()),
1237
- timeoutMs: import_typebox2.default.Optional(import_typebox2.default.Number()),
1238
- maxOutputBytes: import_typebox2.default.Optional(import_typebox2.default.Number())
1239
- });
1240
- var BashArgsValidator = import_schema.default.Compile(BashArgsSchema);
1241
- var validatorCache = /* @__PURE__ */ new WeakMap();
1242
- function validatorFor(schema) {
1243
- const cached = validatorCache.get(schema);
1244
- if (cached) {
1245
- return cached;
1246
- }
1247
- const validator = import_schema.default.Compile(schema);
1248
- validatorCache.set(schema, validator);
1249
- return validator;
1250
- }
1251
- function normalizeCoreToolInputPath(path = ".") {
1252
- const trimmed = path.trim();
1253
- if (!trimmed || trimmed === "/" || trimmed === "\\") {
1254
- return ".";
1255
- }
1256
- return path;
1257
- }
1258
- function createCoreToolContext(input = {}) {
1259
- return {
1260
- root: (0, import_node_path5.resolve)(input.root ?? process.cwd()),
1261
- maxReadBytes: input.maxReadBytes ?? DEFAULT_MAX_READ_BYTES,
1262
- bashTimeoutMs: input.bashTimeoutMs ?? DEFAULT_BASH_TIMEOUT_MS,
1263
- maxBashOutputBytes: input.maxBashOutputBytes ?? DEFAULT_MAX_BASH_OUTPUT_BYTES
1264
- };
1265
- }
1266
- function resolveCoreToolPath(context, path = ".") {
1267
- const root = (0, import_node_path5.resolve)(context.root);
1268
- const inputPath = normalizeCoreToolInputPath(path);
1269
- const absolutePath = (0, import_node_path5.resolve)(root, inputPath);
1270
- const relativePath = (0, import_node_path5.relative)(root, absolutePath);
1271
- if (relativePath === ".." || relativePath.startsWith(`..${import_node_path5.sep}`) || (0, import_node_path5.isAbsolute)(relativePath)) {
1272
- throw new Error(`Path escapes workspace root: ${path}`);
1273
- }
1274
- return {
1275
- root,
1276
- inputPath,
1277
- absolutePath,
1278
- relativePath: normalizeRelativePath(relativePath || ".")
1279
- };
1280
- }
1281
- function toolResult(input) {
1282
- return {
1283
- toolCallId: input.context.toolCallId,
1284
- toolName: input.toolName,
1285
- ok: input.ok,
1286
- content: input.content,
1287
- ...input.error ? { error: input.error } : {}
1288
- };
1289
- }
1290
- async function executeRuntimeToolCall(input) {
1291
- const tool = input.tools.find((candidate) => candidate.name === input.toolCall.name);
1292
- if (!tool) {
1293
- const result = toolResult({
1294
- context: input.toolContext,
1295
- toolName: input.toolCall.name,
1296
- ok: false,
1297
- content: null,
1298
- error: `Unknown tool: ${input.toolCall.name}`
1299
- });
1300
- await input.observe?.({ type: "tool_end", toolName: input.toolCall.name, result });
1301
- return result;
1302
- }
1303
- let args;
1304
- try {
1305
- args = validatorFor(tool.parameters).Parse(input.toolCall.args);
1306
- } catch (error) {
1307
- const result = toolResult({
1308
- context: input.toolContext,
1309
- toolName: tool.name,
1310
- ok: false,
1311
- content: null,
1312
- error: error instanceof Error ? error.message : String(error)
1313
- });
1314
- await input.observe?.({ type: "tool_end", toolName: tool.name, result });
1315
- return result;
1316
- }
1317
- let decision;
1318
- if (input.beforeToolCall) {
1319
- await input.observe?.({ type: "approval_requested", tool, args });
1320
- decision = await input.beforeToolCall({ tool, args });
1321
- await input.observe?.({
1322
- type: "approval_result",
1323
- tool,
1324
- args,
1325
- decision: decision ?? { allow: true }
1326
- });
1327
- }
1328
- if (decision && !decision.allow) {
1329
- const result = toolResult({
1330
- context: input.toolContext,
1331
- toolName: tool.name,
1332
- ok: false,
1333
- content: null,
1334
- error: decision.reason
1335
- });
1336
- await input.observe?.({ type: "tool_blocked", tool, reason: decision.reason });
1337
- return result;
1338
- }
1339
- await input.observe?.({ type: "tool_start", tool, args });
1340
- try {
1341
- const rawResult = await tool.execute(args, input.toolContext, input.signal);
1342
- let result = {
1343
- ...rawResult,
1344
- toolCallId: input.toolCall.id,
1345
- toolName: tool.name
1346
- };
1347
- if (input.afterToolCall) {
1348
- await input.observe?.({
1349
- type: "result_review_requested",
1350
- tool,
1351
- result
1352
- });
1353
- result = await input.afterToolCall({ tool, result });
1354
- await input.observe?.({
1355
- type: "result_review_result",
1356
- tool,
1357
- result
1358
- });
1359
- }
1360
- await input.observe?.({ type: "tool_end", toolName: tool.name, result });
1361
- return result;
1362
- } catch (error) {
1363
- const result = toolResult({
1364
- context: input.toolContext,
1365
- toolName: tool.name,
1366
- ok: false,
1367
- content: null,
1368
- error: error instanceof Error ? error.message : "Tool execution failed."
1369
- });
1370
- await input.observe?.({ type: "tool_end", toolName: tool.name, result });
1371
- return result;
1372
- }
1373
- }
1374
- function positiveNumber(value, name) {
1375
- if (!Number.isFinite(value) || value <= 0) {
1376
- return `${name} must be a positive number.`;
1377
- }
1378
- return void 0;
1379
- }
1380
- async function captureStream(stream, maxBytes) {
1381
- const reader = stream.getReader();
1382
- const chunks = [];
1383
- let totalBytes = 0;
1384
- let truncated = false;
1385
- while (true) {
1386
- const { done, value } = await reader.read();
1387
- if (done) {
1388
- break;
1389
- }
1390
- if (!value) {
1391
- continue;
1392
- }
1393
- const remainingBytes = maxBytes - totalBytes;
1394
- if (remainingBytes > 0) {
1395
- const kept = value.subarray(0, remainingBytes);
1396
- chunks.push(kept);
1397
- totalBytes += kept.byteLength;
1398
- }
1399
- if (value.byteLength > remainingBytes || totalBytes >= maxBytes) {
1400
- truncated = true;
1401
- await reader.cancel().catch(() => void 0);
1402
- break;
1403
- }
1404
- }
1405
- const bytes = new Uint8Array(totalBytes);
1406
- let offset = 0;
1407
- for (const chunk of chunks) {
1408
- bytes.set(chunk, offset);
1409
- offset += chunk.byteLength;
1410
- }
1411
- return {
1412
- text: new TextDecoder().decode(bytes),
1413
- truncated
1414
- };
1415
- }
1416
- function createReadTool(context) {
1417
- return {
1418
- name: "read",
1419
- description: "Reads a text file within the workspace.",
1420
- parameters: ReadArgsSchema,
1421
- promptSnippet: "Read a text file (max 64KB default).",
1422
- promptGuidelines: [
1423
- "Always read a file before editing or writing to understand its current content.",
1424
- "Use maxBytes to limit output for large files."
1425
- ],
1426
- async execute(args, toolContext) {
1427
- const parsed = ReadArgsValidator.Parse(args);
1428
- const resolved = resolveCoreToolPath(context, parsed.path);
1429
- const maxBytes = parsed.maxBytes ?? context.maxReadBytes;
1430
- const invalidLimit = positiveNumber(maxBytes, "maxBytes");
1431
- if (invalidLimit) {
1432
- return toolResult({
1433
- context: toolContext,
1434
- toolName: "read",
1435
- ok: false,
1436
- content: null,
1437
- error: invalidLimit
1438
- });
1439
- }
1440
- const fileStat = await (0, import_promises4.stat)(resolved.absolutePath);
1441
- if (!fileStat.isFile()) {
1442
- return toolResult({
1443
- context: toolContext,
1444
- toolName: "read",
1445
- ok: false,
1446
- content: null,
1447
- error: `Not a file: ${resolved.relativePath}`
1448
- });
1449
- }
1450
- const bytes = await (0, import_promises4.readFile)(resolved.absolutePath);
1451
- const sliced = bytes.subarray(0, maxBytes);
1452
- const text = new TextDecoder().decode(sliced);
1453
- const resourceType = parsed.type ?? detectResourceType(resolved.absolutePath);
1454
- let name;
1455
- let baseDir;
1456
- if (resourceType === "skill" || resourceType === "phase") {
1457
- const { frontmatter } = parseFrontmatter(text);
1458
- const marker = resourceType === "skill" ? "SKILL.md" : "PHASE.md";
1459
- name = frontmatter.name ?? inferResourceName(resolved.absolutePath, marker);
1460
- baseDir = (0, import_node_path5.dirname)(resolved.absolutePath);
1461
- } else {
1462
- name = inferResourceName(resolved.absolutePath, (0, import_node_path5.basename)(resolved.absolutePath));
1463
- }
1464
- return toolResult({
1465
- context: toolContext,
1466
- toolName: "read",
1467
- ok: true,
1468
- content: {
1469
- path: resolved.relativePath,
1470
- content: formatResourceOutput({ type: resourceType, name, location: resolved.absolutePath, content: text, baseDir }),
1471
- sizeBytes: bytes.byteLength,
1472
- truncated: bytes.byteLength > maxBytes
1473
- }
1474
- });
1475
- }
1476
- };
1477
- }
1478
- function createWriteTool(context) {
1479
- return {
1480
- name: "write",
1481
- description: "Writes provided text content to a workspace file, creating parent directories as needed.",
1482
- parameters: WriteArgsSchema,
1483
- promptSnippet: "Write content to a file (creates parent dirs automatically).",
1484
- promptGuidelines: [
1485
- "Use write for new files or full file rewrites.",
1486
- "For partial edits, prefer the edit tool to avoid overwriting unchanged content."
1487
- ],
1488
- async execute(args, toolContext) {
1489
- const parsed = WriteArgsValidator.Parse(args);
1490
- const resolved = resolveCoreToolPath(context, parsed.path);
1491
- await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(resolved.absolutePath), { recursive: true });
1492
- await (0, import_promises4.writeFile)(resolved.absolutePath, parsed.content, "utf8");
1493
- return toolResult({
1494
- context: toolContext,
1495
- toolName: "write",
1496
- ok: true,
1497
- content: {
1498
- path: resolved.relativePath,
1499
- bytesWritten: new TextEncoder().encode(parsed.content).byteLength
1500
- }
1501
- });
1502
- }
1503
- };
1504
- }
1505
- function createEditTool(context) {
1506
- return {
1507
- name: "edit",
1508
- description: "Edits a workspace text file by replacing exact oldText with newText.",
1509
- parameters: EditArgsSchema,
1510
- promptSnippet: "Replace exact text in a file (oldText \u2192 newText).",
1511
- promptGuidelines: [
1512
- "Read the file first to get the exact oldText to replace.",
1513
- "oldText must be an exact match including whitespace and indentation.",
1514
- "If oldText appears multiple times, set replaceAll=true or provide more surrounding context."
1515
- ],
1516
- async execute(args, toolContext) {
1517
- const parsed = EditArgsValidator.Parse(args);
1518
- if (!parsed.oldText) {
1519
- return toolResult({
1520
- context: toolContext,
1521
- toolName: "edit",
1522
- ok: false,
1523
- content: null,
1524
- error: "oldText must not be empty."
1525
- });
1526
- }
1527
- const resolved = resolveCoreToolPath(context, parsed.path);
1528
- const current = await (0, import_promises4.readFile)(resolved.absolutePath, "utf8");
1529
- if (!current.includes(parsed.oldText)) {
1530
- return toolResult({
1531
- context: toolContext,
1532
- toolName: "edit",
1533
- ok: false,
1534
- content: null,
1535
- error: `oldText not found in ${resolved.relativePath}.`
1536
- });
1537
- }
1538
- const matches = current.split(parsed.oldText).length - 1;
1539
- if (matches > 1 && !parsed.replaceAll) {
1540
- return toolResult({
1541
- context: toolContext,
1542
- toolName: "edit",
1543
- ok: false,
1544
- content: null,
1545
- error: `oldText appears ${matches} times in ${resolved.relativePath}; set replaceAll=true or provide more context.`
1546
- });
1547
- }
1548
- const replacements = parsed.replaceAll ? matches : 1;
1549
- const next = parsed.replaceAll ? current.split(parsed.oldText).join(parsed.newText) : current.replace(parsed.oldText, parsed.newText);
1550
- await (0, import_promises4.writeFile)(resolved.absolutePath, next, "utf8");
1551
- return toolResult({
1552
- context: toolContext,
1553
- toolName: "edit",
1554
- ok: true,
1555
- content: {
1556
- path: resolved.relativePath,
1557
- replacements,
1558
- bytesWritten: new TextEncoder().encode(next).byteLength
1559
- }
1560
- });
1561
- }
1562
- };
1563
- }
1564
- function createBashTool(context) {
1565
- return {
1566
- name: "bash",
1567
- description: "Runs a bash command within the workspace.",
1568
- parameters: BashArgsSchema,
1569
- promptSnippet: "Execute a bash command (30s timeout, 64KB output limit).",
1570
- promptGuidelines: [
1571
- "Use bash for build commands, tests, git operations, and system tools.",
1572
- "Prefer dedicated tools (read/write/edit) for file operations.",
1573
- "Set timeoutMs for long-running commands."
1574
- ],
1575
- async execute(args, toolContext, signal) {
1576
- const parsed = BashArgsValidator.Parse(args);
1577
- const timeoutMs = parsed.timeoutMs ?? context.bashTimeoutMs;
1578
- const maxOutputBytes = parsed.maxOutputBytes ?? context.maxBashOutputBytes;
1579
- const invalidTimeout = positiveNumber(timeoutMs, "timeoutMs");
1580
- const invalidOutputLimit = positiveNumber(maxOutputBytes, "maxOutputBytes");
1581
- if (invalidTimeout || invalidOutputLimit) {
1582
- return toolResult({
1583
- context: toolContext,
1584
- toolName: "bash",
1585
- ok: false,
1586
- content: null,
1587
- error: invalidTimeout ?? invalidOutputLimit
1588
- });
1589
- }
1590
- const cwd = resolveCoreToolPath(context, parsed.cwd ?? ".");
1591
- let timedOut = false;
1592
- let aborted = false;
1593
- const proc = Bun.spawn(["bash", "-lc", parsed.command], {
1594
- cwd: cwd.absolutePath,
1595
- stdout: "pipe",
1596
- stderr: "pipe"
1597
- });
1598
- const kill = () => {
1599
- proc.kill();
1600
- };
1601
- const timeout = setTimeout(() => {
1602
- timedOut = true;
1603
- kill();
1604
- }, timeoutMs);
1605
- const onAbort = () => {
1606
- aborted = true;
1607
- kill();
1608
- };
1609
- signal?.addEventListener("abort", onAbort, { once: true });
1610
- try {
1611
- const [stdout, stderr, exitCode] = await Promise.all([
1612
- captureStream(proc.stdout, maxOutputBytes),
1613
- captureStream(proc.stderr, maxOutputBytes),
1614
- proc.exited
1615
- ]);
1616
- const ok = exitCode === 0 && !timedOut && !aborted;
1617
- return toolResult({
1618
- context: toolContext,
1619
- toolName: "bash",
1620
- ok,
1621
- content: {
1622
- command: parsed.command,
1623
- cwd: cwd.relativePath,
1624
- exitCode,
1625
- stdout: stdout.text,
1626
- stderr: stderr.text,
1627
- stdoutTruncated: stdout.truncated,
1628
- stderrTruncated: stderr.truncated
1629
- },
1630
- ...ok ? {} : {
1631
- error: timedOut ? `Command timed out after ${timeoutMs}ms.` : aborted ? "Command aborted." : `Command exited with ${exitCode}.`
1632
- }
1633
- });
1634
- } finally {
1635
- clearTimeout(timeout);
1636
- signal?.removeEventListener("abort", onAbort);
1637
- }
1638
- }
1639
- };
1640
- }
1641
- function createCoreTools(input = {}) {
1642
- const context = createCoreToolContext(input);
1643
- return [
1644
- createReadTool(context),
1645
- createWriteTool(context),
1646
- createEditTool(context),
1647
- createBashTool(context)
1648
- ];
1649
- }
1650
-
1651
- // src/harness/context/system-prompt.ts
1652
- init_resource_formatter();
1653
- function buildSystemPrompt(options) {
1654
- const { systemPrompt, promptGuidelines, appendSystemPrompt, tools, skills, cwd } = options;
1655
- const date = createTimestamp();
1656
- const guidelinesList = [];
1657
- const guidelinesSet = /* @__PURE__ */ new Set();
1658
- const addGuideline = (guideline) => {
1659
- const normalized = guideline.trim();
1660
- if (normalized.length === 0 || guidelinesSet.has(normalized)) return;
1661
- guidelinesSet.add(normalized);
1662
- guidelinesList.push(normalized);
1663
- };
1664
- const visibleTools = (tools ?? []).filter((t) => !!t.promptSnippet);
1665
- const toolsList = visibleTools.length > 0 ? visibleTools.map((t) => `- ${t.name}: ${t.promptSnippet}`).join("\n") : "(none)";
1666
- for (const tool of tools ?? []) {
1667
- for (const g of tool.promptGuidelines ?? []) {
1668
- addGuideline(g);
1669
- }
1670
- }
1671
- const skillsBlock = skills && skills.length > 0 ? buildSkillsDescription(skills) : "";
1672
- for (const g of promptGuidelines ?? []) {
1673
- addGuideline(g);
1674
- }
1675
- const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
1676
- const skillsSection = skillsBlock ? `The following skills provide specialized instructions for specific tasks.
1677
- Read the full skill file when the task matches its description.
1678
- When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md) and use that absolute path in tool commands.
1679
-
1680
- ${skillsBlock}
1681
- ` : "";
1682
- let prompt = `${systemPrompt}
1683
-
1684
- **Important:** Tool and skill availability varies by phase. Only use tools that are available in your current phase context.
1685
-
1686
- Available tools:
1687
- ${toolsList}
1688
-
1689
- ${skillsSection}
1690
- Guidelines:
1691
- ${guidelines}`;
1692
- if (date || cwd) {
1693
- const contextParts = [];
1694
- if (date) contextParts.push(`Current date: ${date}`);
1695
- if (cwd) contextParts.push(`Working directory: ${cwd}`);
1696
- prompt += `
1697
-
1698
- ${contextParts.join("\n")}`;
1699
- }
1700
- if (appendSystemPrompt) {
1701
- prompt += `
1702
-
1703
- ${appendSystemPrompt}`;
1704
- }
1705
- return prompt;
1706
- }
1707
-
1708
- // src/harness/context/prompt-builder.ts
1709
- function serializeSkills(skills) {
1710
- return skills.map((skill) => ({
1711
- name: skill.name,
1712
- description: skill.description,
1713
- filePath: skill.filePath,
1714
- disableModelInvocation: skill.disableModelInvocation
1715
- }));
1716
- }
1717
- function latestUserInput(messages) {
1718
- for (let index = messages.length - 1; index >= 0; index -= 1) {
1719
- const message = messages[index];
1720
- if (message.role === "user") {
1721
- return messageContentText(message.content);
1722
- }
1723
- }
1724
- return "";
1725
- }
1726
- function conversationMessages(messages) {
1727
- return messages.flatMap((message) => {
1728
- if (message.role === "user") {
1729
- return [{ role: "user", content: message.content }];
1730
- }
1731
- if (message.role === "assistant") {
1732
- return [{ role: "assistant", content: message.content }];
1733
- }
1734
- if (message.role === "tool") {
1735
- return [{ role: "tool", content: message.content }];
1736
- }
1737
- return [];
1738
- });
1739
- }
1740
- function buildModelRequest(input, options) {
1741
- const toolMeta = input.tools.map((t) => ({
1742
- name: t.name,
1743
- description: t.description,
1744
- promptSnippet: t.promptSnippet,
1745
- promptGuidelines: t.promptGuidelines
1746
- }));
1747
- let systemText = buildSystemPrompt({
1748
- systemPrompt: input.systemPrompt,
1749
- tools: toolMeta,
1750
- skills: input.skills.length > 0 ? serializeSkills(input.skills) : void 0,
1751
- promptGuidelines: input.promptGuidelines,
1752
- appendSystemPrompt: input.appendSystemPrompt
1753
- });
1754
- const messages = [...conversationMessages(input.messages)];
1755
- const modelTools = input.tools.map((t) => ({
1756
- name: t.name,
1757
- description: t.description,
1758
- parameters: t.parameters
1759
- }));
1760
- return {
1761
- model: options?.model ?? { provider: "", id: "" },
1762
- system: systemText,
1763
- messages,
1764
- tools: modelTools.length > 0 ? modelTools : void 0
1765
- };
1766
- }
1767
-
1768
- // src/harness/context/compaction.ts
1769
- function needsCompaction(messages, options = {}) {
1770
- const maxMessages = options.maxMessages ?? 50;
1771
- return messages.length > maxMessages;
1772
- }
1773
- function buildSummary(messages) {
1774
- const parts = [];
1775
- let currentRole;
1776
- let currentContent = [];
1777
- function flush() {
1778
- if (currentRole && currentContent.length > 0) {
1779
- const combined = currentContent.join("\n");
1780
- const truncated = combined.length > 500 ? combined.slice(0, 500) + "..." : combined;
1781
- parts.push(`[${currentRole}]: ${truncated}`);
1782
- }
1783
- currentContent = [];
1784
- }
1785
- for (const msg of messages) {
1786
- if (msg.role !== currentRole) {
1787
- flush();
1788
- currentRole = msg.role;
1789
- }
1790
- currentContent.push(messageContentText(msg.content));
1791
- }
1792
- flush();
1793
- return parts.join("\n\n");
1794
- }
1795
- function compactMessages(messages, options = {}) {
1796
- if (!needsCompaction(messages, options)) {
1797
- return { compacted: false, messages };
1798
- }
1799
- const keepRecent = options.keepRecent ?? 10;
1800
- const minCompact = options.minCompact ?? 20;
1801
- const firstUserIdx = messages.findIndex(
1802
- (m) => m.role === "user"
1803
- );
1804
- const recentStart = Math.max(messages.length - keepRecent, firstUserIdx + 1);
1805
- const oldMessages = messages.slice(firstUserIdx >= 0 ? firstUserIdx + 1 : 0, recentStart);
1806
- if (oldMessages.length < minCompact) {
1807
- return { compacted: false, messages };
1808
- }
1809
- const recentMessages = messages.slice(recentStart);
1810
- const summary = buildSummary(oldMessages);
1811
- const result = [];
1812
- if (firstUserIdx >= 0) {
1813
- result.push(messages[firstUserIdx]);
1814
- }
1815
- result.push(
1816
- createMessage("assistant", `[Context compaction summary]
1817
-
1818
- ${summary}`, {
1819
- type: "compaction_summary",
1820
- compactedCount: oldMessages.length
1821
- })
1822
- );
1823
- result.push(...recentMessages);
1824
- return {
1825
- compacted: true,
1826
- messages: result,
1827
- summarizedCount: oldMessages.length,
1828
- summary
1829
- };
1830
- }
1831
-
1832
- // src/loop/runners.ts
1833
- init_resource_formatter();
1834
- function resolvePhaseOutput(result) {
1835
- return result ? { ...result } : { message: "Phase completed.", route: "stop" };
1836
- }
1837
- function removePhaseMessage(messages, msgId) {
1838
- if (!msgId) return;
1839
- const idx = messages.findIndex((m) => m.id === msgId);
1840
- if (idx !== -1) messages.splice(idx, 1);
1841
- }
1842
- function normalizePayload(payload) {
1843
- if (typeof payload === "string") {
1844
- try {
1845
- return JSON.parse(payload);
1846
- } catch {
1847
- }
1848
- }
1849
- return payload;
1850
- }
1851
- function applyFirstDecision(route, output) {
1852
- const first = route.decision[0];
1853
- if (!first) return;
1854
- output.route = first.phase;
1855
- if (first.reason) output.routeReason = first.reason;
1856
- if (first.payload !== void 0) output.payload = normalizePayload(first.payload);
1857
- }
1858
- function injectPhaseContent(phase, output, messageManager) {
1859
- try {
1860
- const phaseContent = phase.filePath ? readPhaseContent(phase) : phase.content ?? phase.description ?? "";
1861
- const content = buildPhaseDirectiveMessage(
1862
- { name: phase.id, content: phaseContent },
1863
- output,
1864
- `phase_${phase.id}`
1865
- );
1866
- const msgId = messageManager.start("tool", content, { phase: phase.id });
1867
- messageManager.end(msgId);
1868
- return msgId;
1869
- } catch {
1870
- return void 0;
1871
- }
1872
- }
1873
- function buildInstanceIds(phases) {
1874
- const counts = /* @__PURE__ */ new Map();
1875
- for (const p of phases) {
1876
- counts.set(p, (counts.get(p) ?? 0) + 1);
1877
- }
1878
- const hasDupes = [...counts.values()].some((c) => c > 1);
1879
- if (!hasDupes) return phases;
1880
- const idx = /* @__PURE__ */ new Map();
1881
- return phases.map((p) => {
1882
- const n = (idx.get(p) ?? 0) + 1;
1883
- idx.set(p, n);
1884
- return `${p}#${n}`;
1885
- });
1886
- }
1887
- function emitTurn(context, emitFn, type, extra) {
1888
- emitFn?.({
1889
- type,
1890
- content: snapshotMessages(context.messages),
1891
- ...extra,
1892
- ts: createTimestamp()
1893
- });
1894
- }
1895
- function createRunResult(config, state, outcome) {
1896
- return {
1897
- sessionId: config.sessionId,
1898
- messages: snapshotMessages(config.context.messages),
1899
- outcome,
1900
- metrics: state.metrics
1901
- };
1902
- }
1903
- async function completeRun(config, state, outcome) {
1904
- state.metrics.endedAt = createTimestamp();
1905
- state.metrics.durationMs = Date.now() - state.metrics.startedAtMs;
1906
- await config.onOutcome?.(outcome);
1907
- return createRunResult(config, state, outcome);
1908
- }
1909
- function buildToolsWithRouting(config, availablePhases) {
1910
- const tools = [...config.context.tools];
1911
- if (availablePhases.length > 0) {
1912
- tools.push(createRouteTool(availablePhases));
1913
- }
1914
- return tools;
1915
- }
1916
- function createMessageManager(context, emitFn, onMessage) {
1917
- const activeMessages = /* @__PURE__ */ new Map();
1918
- return {
1919
- visible: () => [...context.messages],
1920
- start(role, content, metadata) {
1921
- const msg = createMessage(role, content, metadata);
1922
- activeMessages.set(msg.id, msg);
1923
- emitFn?.({ type: "message_start", message: snapshotMessage(msg), ts: createTimestamp() });
1924
- return msg.id;
1925
- },
1926
- async update(messageId, delta) {
1927
- const msg = activeMessages.get(messageId);
1928
- if (!msg) return;
1929
- msg.content = typeof msg.content === "string" ? msg.content + delta : [...msg.content, { type: "text", text: delta }];
1930
- emitFn?.({ type: "message_update", message: snapshotMessage(msg), delta, ts: createTimestamp() });
1931
- },
1932
- replaceContent(messageId, content) {
1933
- const msg = activeMessages.get(messageId);
1934
- if (!msg) return;
1935
- msg.content = content;
1936
- },
1937
- async end(messageId) {
1938
- const msg = activeMessages.get(messageId);
1939
- if (!msg) return;
1940
- activeMessages.delete(messageId);
1941
- context.messages.push(msg);
1942
- emitFn?.({ type: "message_end", message: snapshotMessage(msg), ts: createTimestamp() });
1943
- await onMessage?.(msg);
1944
- }
1945
- };
1946
- }
1947
- function createToolExecutionManager(emitFn) {
1948
- return {
1949
- async start(toolCallId, toolName, args) {
1950
- emitFn?.({ type: "tool_execution_start", toolCallId, toolName, args, ts: createTimestamp() });
1951
- },
1952
- async end(toolCallId, toolName, result, isError) {
1953
- emitFn?.({ type: "tool_execution_end", toolCallId, toolName, result, isError, ts: createTimestamp() });
1954
- }
1955
- };
1956
- }
1957
- function createToolResultContent(result) {
1958
- return [
1959
- {
1960
- type: "tool_result",
1961
- toolUseId: result.toolCallId,
1962
- content: JSON.stringify(result),
1963
- isError: !result.ok
1964
- }
1965
- ];
1966
- }
1967
- async function executePhaseWithModel(ctx) {
1968
- const executableToolNames = new Set(
1969
- ctx.context.tools.filter((tool) => tool.name !== PhaseRouteTool).map((tool) => tool.name)
1970
- );
1971
- let output = {
1972
- message: "",
1973
- route: "stop",
1974
- phase: ctx.phase.name,
1975
- toolCalls: []
1976
- };
1977
- while (true) {
1978
- const roundContext = {
1979
- ...ctx.context,
1980
- messages: ctx.messageManager.visible()
1981
- };
1982
- const collected = await ctx.execution.invokeModel(roundContext);
1983
- output = {
1984
- message: collected.text,
1985
- route: "stop",
1986
- phase: ctx.phase.name,
1987
- toolCalls: collected.toolCalls
1988
- };
1989
- const executableToolCalls = collected.toolCalls.filter(
1990
- (toolCall) => executableToolNames.has(toolCall.name)
1991
- );
1992
- if (executableToolCalls.length === 0) {
1993
- return output;
1994
- }
1995
- for (const toolCall of executableToolCalls) {
1996
- const result = await ctx.execution.executeTool(roundContext, toolCall);
1997
- const messageId = ctx.messageManager.start("tool", createToolResultContent(result), {
1998
- phase: ctx.phase.id
1999
- });
2000
- await ctx.messageManager.end(messageId);
2001
- }
2002
- }
2003
- }
2004
- async function runTurn(context, emitFn, fn) {
2005
- emitTurn(context, emitFn, "turn_start");
2006
- try {
2007
- return await fn();
2008
- } finally {
2009
- emitTurn(context, emitFn, "turn_end");
2010
- }
2011
- }
2012
- async function startPhaseLoop(config, state) {
2013
- const entryPhaseId = config.phases?.entryPhaseId ?? "default";
2014
- const phases = /* @__PURE__ */ new Map();
2015
- phases.set("default", {
2016
- id: "default",
2017
- name: "Execution Phase",
2018
- description: "Executes concrete task operations and produces artifacts.",
2019
- filePath: "",
2020
- baseDir: "",
2021
- content: "Execute tasks using current context.\nNo planning. No evaluation.\nRoute to next phase or stop when done."
2022
- });
2023
- if (config.phases) {
2024
- for (const [id, phase] of config.phases.phases) {
2025
- phases.set(id, phase);
2026
- }
2027
- }
2028
- const registry = {
2029
- phases,
2030
- entryPhaseId
2031
- };
2032
- return runPhaseLoop(config, state, registry);
2033
- }
2034
- async function runPhaseLoop(config, state, registry) {
2035
- let currentPhaseId = registry.entryPhaseId;
2036
- let isContinuing = false;
2037
- let previousPayload = void 0;
2038
- let previousPhaseMsgId = void 0;
2039
- let previousResults = [];
2040
- let pendingInstruction = void 0;
2041
- while (currentPhaseId) {
2042
- await reloadPhases(registry);
2043
- if (config.phases) {
2044
- config.phases.phases = registry.phases;
2045
- }
2046
- const availablePhases = [];
2047
- for (const [, phase2] of registry.phases) {
2048
- availablePhases.push({ id: phase2.id, name: phase2.name, description: phase2.description, tools: phase2.tools, skills: phase2.skills, input: phase2.input, isolated: phase2.isolated });
2049
- }
2050
- const abortResult = LoopGuard.checkAbort(config.signal);
2051
- if (abortResult.stopReason !== "none") {
2052
- return completeRun(config, state, createOutcome.aborted());
2053
- }
2054
- state.metrics.iterations++;
2055
- if (needsCompaction(config.context.messages)) {
2056
- const compacted = compactMessages(config.context.messages);
2057
- if (compacted.compacted) {
2058
- config.context.messages = compacted.messages;
2059
- state.metrics.compactionCount++;
2060
- config.emit?.({
2061
- type: "message_start",
2062
- message: {
2063
- id: "compaction",
2064
- role: "assistant",
2065
- content: `[Compacted ${compacted.summarizedCount} older messages to stay within context limits]`,
2066
- createdAt: createTimestamp(),
2067
- metadata: { type: "compaction_notice" }
2068
- },
2069
- ts: createTimestamp()
2070
- });
2071
- }
2072
- }
2073
- const phase = registry.phases.get(currentPhaseId);
2074
- if (!phase) {
2075
- throw new Error(`Phase "${currentPhaseId}" not found`);
2076
- }
2077
- state.currentPhase = currentPhaseId;
2078
- const allTools = buildToolsWithRouting(config, availablePhases);
2079
- const messageManager = createMessageManager(config.context, config.emit, config.onMessage);
2080
- const toolExecutionManager = createToolExecutionManager(config.emit);
2081
- const execution = createPhaseExecution(config, state, allTools, phase, messageManager, toolExecutionManager, registry);
2082
- const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
2083
- const phaseSkills = phase.skills ? config.context.skills.filter((s) => phase.skills.includes(s.name)) : config.context.skills;
2084
- let phaseContext = {
2085
- systemPrompt: config.context.systemPrompt,
2086
- messages: messageManager.visible(),
2087
- tools: phaseTools,
2088
- skills: phaseSkills,
2089
- state: {
2090
- current: currentPhaseId,
2091
- available: Array.from(registry.phases.keys()),
2092
- iterations: state.metrics.iterations,
2093
- payload: previousPayload
2094
- }
2095
- };
2096
- const enteringNewPhase = !isContinuing;
2097
- if (enteringNewPhase) {
2098
- config.emit?.({ type: "phase_start", phase: currentPhaseId, ts: createTimestamp() });
2099
- }
2100
- isContinuing = false;
2101
- if (config.beforePhase) {
2102
- const extBefore = await config.beforePhase(currentPhaseId, phaseContext);
2103
- if (extBefore.abort) {
2104
- config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
2105
- return completeRun(config, state, extBefore.abort);
2106
- }
2107
- if (extBefore.skip) {
2108
- config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
2109
- if (extBefore.skip.route === "stop") {
2110
- return completeRun(config, state, {
2111
- id: "skip",
2112
- message: extBefore.skip.message || "Skipped."
2113
- });
2114
- }
2115
- currentPhaseId = extBefore.skip.route;
2116
- continue;
2117
- }
2118
- if (extBefore.input) {
2119
- phaseContext = extBefore.input;
2120
- }
2121
- }
2122
- removePhaseMessage(config.context.messages, previousPhaseMsgId);
2123
- previousPhaseMsgId = void 0;
2124
- if (enteringNewPhase) {
2125
- previousPhaseMsgId = injectPhaseContent(phase, { results: previousResults, instruction: pendingInstruction }, messageManager);
2126
- previousResults = [];
2127
- pendingInstruction = void 0;
2128
- }
2129
- const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
2130
- let output = await executePhase(runtime);
2131
- let routeToolCalled = false;
2132
- let routeDecision;
2133
- if (output.toolCalls && output.toolCalls.length > 0) {
2134
- routeDecision = extractRouteCall(output.toolCalls);
2135
- if (routeDecision) {
2136
- routeToolCalled = true;
2137
- applyFirstDecision(routeDecision, output);
2138
- }
2139
- }
2140
- if (config.afterPhase) {
2141
- const extAfter = await config.afterPhase(currentPhaseId, output);
2142
- if (extAfter.abort) {
2143
- config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
2144
- return completeRun(config, state, extAfter.abort);
2145
- }
2146
- if (extAfter.retry && (phase.run || phase.factory)) {
2147
- output = await executePhase(runtime);
2148
- routeDecision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
2149
- if (routeDecision) applyFirstDecision(routeDecision, output);
2150
- }
2151
- if (extAfter.output) {
2152
- output = extAfter.output;
2153
- }
2154
- }
2155
- if (output.route === "continue") {
2156
- isContinuing = true;
2157
- continue;
2158
- }
2159
- config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
2160
- if (routeDecision && routeDecision.decision.length > 1) {
2161
- const contextSnapshot = snapshotMessages(config.context.messages);
2162
- const parallelTasks = /* @__PURE__ */ new Map();
2163
- const instanceIds = buildInstanceIds(routeDecision.decision.map((t) => t.phase));
2164
- for (let i = 0; i < routeDecision.decision.length; i++) {
2165
- const target = routeDecision.decision[i];
2166
- const pt = registry.phases.get(target.phase);
2167
- if (!pt) continue;
2168
- const instanceId = instanceIds[i];
2169
- const context = pt.isolated ? [] : contextSnapshot;
2170
- const payload = target.payload !== void 0 ? normalizePayload(target.payload) : void 0;
2171
- const promise = executeParallelPhase(config, state, registry, pt, payload, context, availablePhases, currentPhaseId);
2172
- parallelTasks.set(instanceId, { promise, phaseId: target.phase });
2173
- }
2174
- const successfulResults = await waitForBackgroundTasks(parallelTasks);
2175
- previousResults = successfulResults.map((r) => ({ name: r.instanceId, output: r.payload }));
2176
- pendingInstruction = routeDecision.instruction;
2177
- const entryPhaseId = phase.target ?? registry.entryPhaseId ?? "default";
2178
- if (entryPhaseId === "stop") {
2179
- return completeRun(config, state, createOutcome.default(output, config.context.messages));
2180
- }
2181
- currentPhaseId = entryPhaseId;
2182
- previousPayload = void 0;
2183
- continue;
2184
- }
2185
- let nextRoute;
2186
- if (phase.target) {
2187
- nextRoute = phase.target;
2188
- } else if (output.route) {
2189
- nextRoute = output.route;
2190
- } else {
2191
- nextRoute = "stop";
2192
- }
2193
- if (nextRoute === "stop") {
2194
- if (routeToolCalled) removePhaseMessage(config.context.messages, previousPhaseMsgId);
2195
- return completeRun(config, state, createOutcome.default(output, config.context.messages));
2196
- }
2197
- const targetPhaseId = nextRoute;
2198
- if (!registry.phases.has(targetPhaseId)) {
2199
- removePhaseMessage(config.context.messages, previousPhaseMsgId);
2200
- return completeRun(config, state, createOutcome.phaseNotFound(output));
2201
- }
2202
- state.metrics.phaseTransitions.push({
2203
- from: currentPhaseId,
2204
- to: targetPhaseId,
2205
- ts: createTimestamp()
2206
- });
2207
- previousPayload = output.payload;
2208
- previousResults = output.payload !== void 0 ? [{ name: phase.id, output: output.payload }] : [];
2209
- currentPhaseId = targetPhaseId;
2210
- }
2211
- throw new Error("Phase machine exited without a stop or abort transition.");
2212
- }
2213
- var DEFAULT_MAX_RETRIES = 3;
2214
- var DEFAULT_BASE_DELAY_MS = 1e3;
2215
- var DEFAULT_MAX_DELAY_MS = 3e4;
2216
- function isRetryableError(error) {
2217
- if (!(error instanceof Error)) return false;
2218
- const message = error.message.toLowerCase();
2219
- if (message.includes("rate limit") || message.includes("429")) return true;
2220
- if (message.includes("overloaded") || message.includes("529")) return true;
2221
- if (message.includes("server error") || message.includes("500")) return true;
2222
- if (message.includes("bad gateway") || message.includes("502")) return true;
2223
- if (message.includes("service unavailable") || message.includes("503")) return true;
2224
- if (message.includes("gateway timeout") || message.includes("504")) return true;
2225
- if (message.includes("econnreset") || message.includes("econnrefused")) return true;
2226
- if ("code" in error) {
2227
- const code = error.code;
2228
- if (code === "invalid_model_schema" || code === "empty_response") return true;
2229
- }
2230
- return false;
2231
- }
2232
- function getRetryDelay(attempt, baseMs, maxMs) {
2233
- const exponential = baseMs * Math.pow(2, attempt);
2234
- const jitter = exponential * (0.5 + Math.random() * 0.5);
2235
- return Math.min(jitter, maxMs);
2236
- }
2237
- async function withRetry(fn, options = {}) {
2238
- const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
2239
- const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
2240
- const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
2241
- let lastError;
2242
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
2243
- try {
2244
- return await fn();
2245
- } catch (error) {
2246
- lastError = error;
2247
- if (attempt >= maxRetries || !isRetryableError(error)) {
2248
- throw error;
2249
- }
2250
- if (options.signal?.aborted) {
2251
- throw error;
2252
- }
2253
- const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
2254
- options.onRetry?.(attempt, error, delayMs);
2255
- await new Promise((resolve6) => setTimeout(resolve6, delayMs));
2256
- if (options.signal?.aborted) {
2257
- throw lastError;
2258
- }
2259
- }
2260
- }
2261
- throw lastError;
2262
- }
2263
- async function executePhase(ctx) {
2264
- const { phase, config, execution, registry, context } = ctx;
2265
- if (phase.factory) {
2266
- const api = createExtensionAPI(void 0, void 0, {
2267
- registerPhase: () => {
2268
- },
2269
- registerProvider: () => {
2270
- },
2271
- unregisterProvider: () => {
2272
- },
2273
- registerTool: () => {
2274
- },
2275
- context: {
2276
- cwd: process.cwd(),
2277
- signal: config.signal,
2278
- isIdle: () => false,
2279
- abort: () => config.signal?.dispatchEvent(new Event("abort")),
2280
- exec: async () => ({ exitCode: 1, stdout: "", stderr: "not available in phase context" }),
2281
- getSystemPrompt: () => config.context.systemPrompt,
2282
- setSystemPrompt: () => {
2283
- },
2284
- getMessages: () => context.messages,
2285
- addMessage: (role, content) => {
2286
- config.context.messages.push(createMessage(role, content));
2287
- },
2288
- getAvailableTools: () => config.context.tools.map((t) => ({ name: t.name, description: t.description })),
2289
- getAvailableSkills: () => config.context.skills.map((s) => ({ name: s.name, description: s.description })),
2290
- getPhaseContent: (id) => registry.phases.get(id)?.content ?? "",
2291
- getAvailablePhases: () => Array.from(registry.phases.keys())
2292
- },
2293
- phase: context,
2294
- session: config.context
2295
- });
2296
- await phase.factory(api);
2297
- return {
2298
- message: api.phase.getMessage() ?? `${phase.name} phase completed.`,
2299
- route: api.phase.getNextPhase() || "stop",
2300
- phase: phase.name,
2301
- payload: api.phase.getPayload()
2302
- };
2303
- }
2304
- if (phase.run) {
2305
- const output = resolvePhaseOutput(await phase.run(context, execution));
2306
- output.phase = phase.name;
2307
- if (output.message === "Phase completed.") {
2308
- output.message = `${phase.name} phase completed.`;
2309
- }
2310
- return output;
2311
- }
2312
- return executePhaseWithModel(ctx);
2313
- }
2314
- async function executeToolCall(input) {
2315
- if (input.config.runtime?.tools) {
2316
- return input.config.runtime.tools({
2317
- config: input.config,
2318
- toolCall: input.toolCall
2319
- });
2320
- }
2321
- const toolContext = {
2322
- skills: input.config.context.skills,
2323
- toolCallId: input.toolCall.id
2324
- };
2325
- return executeRuntimeToolCall({
2326
- tools: input.tools,
2327
- toolCall: input.toolCall,
2328
- toolContext,
2329
- beforeToolCall: input.config.beforeToolCall,
2330
- afterToolCall: input.config.afterToolCall,
2331
- signal: input.config.signal
2332
- });
2333
- }
2334
- function createPhaseExecution(config, state, allTools, phase, messageManager, toolExecutionManager, registry) {
2335
- return {
2336
- snapshot() {
2337
- return {
2338
- systemPrompt: config.context.systemPrompt,
2339
- messages: [...config.context.messages],
2340
- currentPhase: state.currentPhase ?? "",
2341
- availablePhases: Array.from(registry.phases.keys()),
2342
- turnNumber: state.metrics.iterations
2343
- };
2344
- },
2345
- restore(snapshot) {
2346
- config.context.systemPrompt = snapshot.systemPrompt;
2347
- config.context.messages.length = 0;
2348
- config.context.messages.push(...snapshot.messages);
2349
- state.currentPhase = snapshot.currentPhase;
2350
- state.metrics.iterations = snapshot.turnNumber;
2351
- },
2352
- async invokeModel(phaseContext) {
2353
- if (config.beforePrompt) {
2354
- phaseContext = await config.beforePrompt(phase.id, phaseContext);
2355
- }
2356
- const request = buildModelRequest({
2357
- systemPrompt: phaseContext.systemPrompt,
2358
- messages: phaseContext.messages,
2359
- tools: phaseContext.tools,
2360
- skills: phaseContext.skills,
2361
- promptGuidelines: phaseContext.promptGuidelines,
2362
- appendSystemPrompt: phaseContext.appendSystemPrompt
2363
- }, { model: phase.model ?? config.model });
2364
- if (!request.tools) {
2365
- if (phaseContext.tools.length > 0) {
2366
- request.tools = phaseContext.tools.map((t) => ({
2367
- name: t.name,
2368
- description: t.description,
2369
- parameters: t.parameters
2370
- }));
2371
- }
2372
- }
2373
- const result = await runTurn(
2374
- config.context,
2375
- config.emit,
2376
- () => withRetry(
2377
- () => invokeModel({
2378
- config,
2379
- message: messageManager,
2380
- request,
2381
- phaseId: phase.id
2382
- }),
2383
- {
2384
- signal: config.signal,
2385
- onRetry: (attempt, error, delayMs) => {
2386
- state.metrics.retryCount++;
2387
- const errMsg = error instanceof Error ? error.message : String(error);
2388
- config.emit?.({
2389
- type: "message_start",
2390
- message: {
2391
- id: `retry_${attempt}`,
2392
- role: "assistant",
2393
- content: `[Retry ${attempt + 1}/${DEFAULT_MAX_RETRIES}] Transient error: ${errMsg}. Retrying in ${Math.round(delayMs)}ms...`,
2394
- createdAt: createTimestamp(),
2395
- metadata: { type: "retry_notice" }
2396
- },
2397
- ts: createTimestamp()
2398
- });
2399
- }
2400
- }
2401
- )
2402
- );
2403
- await config.onModelTranscript?.(result.transcript, { phase: phase.id, model: phase.model ?? config.model });
2404
- return result;
2405
- },
2406
- async executeTool(_context, toolCall) {
2407
- return runTurn(config.context, config.emit, async () => {
2408
- await toolExecutionManager.start(toolCall.id, toolCall.name, toolCall.args);
2409
- const result = await executeToolCall({ config, tools: allTools, toolCall });
2410
- await toolExecutionManager.end(result.toolCallId, result.toolName, result, !result.ok);
2411
- return result;
2412
- });
2413
- }
2414
- };
2415
- }
2416
- async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, currentPhaseId) {
2417
- const messages = [...context];
2418
- const allTools = buildToolsWithRouting(config, availablePhases);
2419
- const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
2420
- const phaseSkills = phase.skills ? config.context.skills.filter((s) => phase.skills.includes(s.name)) : config.context.skills;
2421
- const systemPrompt = phase.isolated ? readPhaseContent(phase) : config.context.systemPrompt;
2422
- const phaseContext = {
2423
- systemPrompt,
2424
- messages,
2425
- tools: phaseTools,
2426
- skills: phaseSkills,
2427
- state: {
2428
- current: phase.id,
2429
- available: Array.from(registry.phases.keys()),
2430
- iterations: 0,
2431
- payload
2432
- }
2433
- };
2434
- const messageManager = createMessageManager({ messages }, config.emit, config.onMessage);
2435
- const phaseMsgId = phase.isolated ? void 0 : injectPhaseContent(
2436
- phase,
2437
- { results: payload !== void 0 ? [{ name: currentPhaseId, output: payload }] : [] },
2438
- messageManager
2439
- );
2440
- const toolExecutionManager = createToolExecutionManager(config.emit);
2441
- const execution = createPhaseExecution(config, state, phaseTools, phase, messageManager, toolExecutionManager, registry);
2442
- const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
2443
- const output = await executePhase(runtime);
2444
- if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
2445
- const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
2446
- const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
2447
- return { instanceId: "", phaseId: phase.id, payload: resultPayload, content: output.message };
2448
- }
2449
- async function waitForBackgroundTasks(backgroundTasks) {
2450
- const entries = Array.from(backgroundTasks.entries());
2451
- const results = await Promise.allSettled(entries.map(([, t]) => t.promise));
2452
- const successful = [];
2453
- for (let i = 0; i < results.length; i++) {
2454
- const r = results[i];
2455
- if (r.status === "fulfilled") {
2456
- successful.push({ ...r.value, instanceId: entries[i][0] });
2457
- }
2458
- }
2459
- backgroundTasks.clear();
2460
- return successful;
2461
- }
2462
-
2463
- // src/agent-loop.ts
2464
- async function runAgentLoop(input) {
2465
- const sessionId = input.sessionId ?? createId("ses");
2466
- const config = {
2467
- ...input,
2468
- sessionId,
2469
- maxAttempts: input.maxAttempts ?? 2
2470
- };
2471
- const state = {
2472
- currentPhase: config.sessionState?.currentPhase ?? "",
2473
- attempt: config.sessionState?.attempt ?? 0,
2474
- status: config.sessionState?.status ?? "running",
2475
- metrics: config.sessionState?.metrics ?? {
2476
- iterations: 0,
2477
- phaseTransitions: [],
2478
- compactionCount: 0,
2479
- retryCount: 0,
2480
- startedAt: createTimestamp(),
2481
- startedAtMs: Date.now()
2482
- }
2483
- };
2484
- const emitEvent = (event) => {
2485
- config.emit?.(event);
2486
- };
2487
- emitEvent({ type: "agent_start", sessionId, ts: createTimestamp() });
2488
- try {
2489
- const abortResult = LoopGuard.checkAbort(config.signal);
2490
- if (abortResult.stopReason !== "none") {
2491
- return completeRun2(config, state, createOutcome.aborted());
2492
- }
2493
- return await startPhaseLoop(config, state);
2494
- } finally {
2495
- emitEvent({
2496
- type: "agent_end",
2497
- sessionId,
2498
- messages: snapshotMessages(config.context.messages),
2499
- ts: createTimestamp()
2500
- });
2501
- }
2502
- }
2503
- async function completeRun2(config, state, outcome) {
2504
- state.metrics.endedAt = createTimestamp();
2505
- state.metrics.durationMs = Date.now() - state.metrics.startedAtMs;
2506
- await config.onOutcome?.(outcome);
2507
- return {
2508
- sessionId: config.sessionId,
2509
- messages: snapshotMessages(config.context.messages),
2510
- outcome,
2511
- metrics: state.metrics
2512
- };
2513
- }
2514
-
2515
- // src/agent.ts
2516
- init_loader2();
2517
-
2518
- // src/harness/skills.ts
2519
- var import_node_fs5 = require("fs");
2520
- var import_promises5 = require("fs/promises");
2521
- var import_node_path6 = require("path");
2522
- init_path();
2523
- init_loader();
2524
- init_resource_formatter();
2525
- var SKILL_MARKER = "SKILL.md";
2526
- function resolveSkillPath(input, workspace = resolveWorkspacePaths()) {
2527
- return resolveResourcePath(input, "skills", SKILL_MARKER, workspace);
2528
- }
2529
- function readSkillContent(skill) {
2530
- return formatResourceOutput({
2531
- type: "skill",
2532
- name: skill.name,
2533
- location: skill.filePath,
2534
- content: skill.content,
2535
- baseDir: skill.baseDir
2536
- });
2537
- }
2538
- async function loadSkill(path, workspace) {
2539
- const resolved = resolveSkillPath(path, workspace);
2540
- const { frontmatter, body } = await loadMarkdown(resolved);
2541
- return {
2542
- name: frontmatter.name ?? inferResourceName(resolved, SKILL_MARKER),
2543
- description: frontmatter.description ?? "",
2544
- filePath: resolved,
2545
- baseDir: (0, import_node_path6.dirname)(resolved),
2546
- content: body,
2547
- disableModelInvocation: frontmatter["disable-model-invocation"] === true
2548
- };
2549
- }
2550
- async function loadSkills(workspace, paths) {
2551
- if (paths && paths.length > 0) {
2552
- return Promise.all(paths.map((path) => loadSkill(path, workspace)));
2553
- }
2554
- const ws = workspace ?? resolveWorkspacePaths();
2555
- const skillsDir = (0, import_node_path6.join)(ws.rowanDir, "skills");
2556
- if (!(0, import_node_fs5.existsSync)(skillsDir)) {
2557
- return [];
2558
- }
2559
- const entries = await (0, import_promises5.readdir)(skillsDir, { withFileTypes: true });
2560
- const skills = [];
2561
- for (const entry of entries) {
2562
- if (!entry.isDirectory()) continue;
2563
- const skillFile = (0, import_node_path6.join)(skillsDir, entry.name, SKILL_MARKER);
2564
- if (!(0, import_node_fs5.existsSync)(skillFile)) continue;
2565
- try {
2566
- const skill = await loadSkill(entry.name, ws);
2567
- skills.push(skill);
2568
- } catch (error) {
2569
- console.warn(`Failed to load skill "${entry.name}":`, error);
2570
- }
2571
- }
2572
- return skills;
2573
- }
2574
-
2575
- // src/agent.ts
2576
- init_path();
2577
- var Agent = class {
2578
- state;
2579
- options;
2580
- listeners = /* @__PURE__ */ new Set();
2581
- pendingListenerTasks = /* @__PURE__ */ new Set();
2582
- listenerErrors = [];
2583
- activeRun;
2584
- constructor(options) {
2585
- this.options = {
2586
- ...options,
2587
- context: cloneAgentContext(options.context)
2588
- };
2589
- this.state = {
2590
- ...this.options.sessionId ? { sessionId: this.options.sessionId } : {},
2591
- context: cloneAgentContext(this.options.context),
2592
- model: this.options.model,
2593
- tools: this.options.context.tools ?? [],
2594
- isRunning: false
2595
- };
2596
- }
2597
- subscribe(listener) {
2598
- this.listeners.add(listener);
2599
- return () => {
2600
- this.listeners.delete(listener);
2601
- };
2602
- }
2603
- emitToListeners(event) {
2604
- for (const listener of this.listeners) {
2605
- try {
2606
- const result = listener(event);
2607
- if (result && typeof result === "object" && "then" in result) {
2608
- const task = Promise.resolve(result).catch((error) => {
2609
- this.listenerErrors.push(error);
2610
- }).finally(() => {
2611
- this.pendingListenerTasks.delete(task);
2612
- });
2613
- this.pendingListenerTasks.add(task);
2614
- }
2615
- } catch (error) {
2616
- this.listenerErrors.push(error);
2617
- }
2618
- }
2619
- }
2620
- async flushEvents() {
2621
- while (this.pendingListenerTasks.size > 0) {
2622
- await Promise.all([...this.pendingListenerTasks]);
2623
- }
2624
- for (const listener of this.listeners) {
2625
- try {
2626
- await listener.flush?.();
2627
- } catch (error) {
2628
- this.listenerErrors.push(error);
2629
- }
2630
- }
2631
- if (this.listenerErrors.length > 0) {
2632
- const [error] = this.listenerErrors;
2633
- throw error instanceof Error ? error : new Error(String(error));
2634
- }
2635
- }
2636
- processEvents(event) {
2637
- switch (event.type) {
2638
- case "message_start":
2639
- this.state.currentResult = void 0;
2640
- break;
2641
- case "message_end":
2642
- break;
2643
- case "agent_end":
2644
- break;
2645
- }
2646
- this.emitToListeners(event);
2647
- this.options.extensionRunnerRef?.current?.emitAgentEvent(event);
2648
- }
2649
- /**
2650
- * Hook for before_tool_call — called before a tool executes.
2651
- * Extensions can block execution by setting allow=false.
2652
- */
2653
- async handleBeforeToolCall(tool, args) {
2654
- const runner = this.options.extensionRunnerRef?.current;
2655
- if (!runner) return { allow: true };
2656
- return runner.emitBeforeToolCall(tool, args);
2657
- }
2658
- /**
2659
- * Hook for after_tool_call — called after a tool executes.
2660
- * Extensions can mutate the result.
2661
- */
2662
- async handleAfterToolCall(tool, result) {
2663
- const runner = this.options.extensionRunnerRef?.current;
2664
- if (!runner) return result;
2665
- return runner.emitAfterToolCall(tool, result);
2666
- }
2667
- /**
2668
- * Hook for before_phase — called before a phase executes.
2669
- * Extensions can abort, skip, or replace the phase input.
2670
- */
2671
- async handleBeforePhase(phaseId, input) {
2672
- const runner = this.options.extensionRunnerRef?.current;
2673
- if (!runner) return {};
2674
- return runner.emitBeforePhase(phaseId, input);
2675
- }
2676
- /**
2677
- * Hook for after_phase — called after a phase executes.
2678
- * Extensions can abort, retry, or replace the output.
2679
- */
2680
- async handleAfterPhase(phaseId, output) {
2681
- const runner = this.options.extensionRunnerRef?.current;
2682
- if (!runner) return {};
2683
- return runner.emitAfterPhase(phaseId, output);
2684
- }
2685
- /**
2686
- * Hook for before_prompt — called before model request, allowing extensions to transform PhaseContext.
2687
- * Extensions can transform the PhaseContext (messages, tools, systemPrompt, etc.).
2688
- */
2689
- async handleBeforePrompt(phaseId, input) {
2690
- const runner = this.options.extensionRunnerRef?.current;
2691
- if (!runner) return input;
2692
- return runner.emitBeforePrompt(phaseId, input);
2693
- }
2694
- handleRunFailure(error, aborted) {
2695
- const message = error instanceof Error ? error.message : "Agent run failed.";
2696
- this.state.error = message;
2697
- }
2698
- /**
2699
- * Discover and load phases and skills from the workspace.
2700
- *
2701
- * - Auto-discovers file-based phases from .rowan/phases/
2702
- * - Auto-discovers skills from .rowan/skills/
2703
- * - Merges with CLI-provided phases and skills
2704
- */
2705
- async discoverResources(context) {
2706
- const workspace = resolveWorkspacePaths({ cwd: this.options.cwd, rowanDir: this.options.rowanDir });
2707
- const filePhases = await loadPhases(workspace);
2708
- let phases;
2709
- const providedPhases = this.options.phases;
2710
- if (providedPhases && providedPhases.phases.size > 0) {
2711
- const merged = new Map(filePhases.phases);
2712
- for (const [id, phase] of providedPhases.phases) {
2713
- merged.set(id, phase);
2714
- }
2715
- phases = {
2716
- phases: merged,
2717
- entryPhaseId: providedPhases.entryPhaseId ?? filePhases.entryPhaseId
2718
- };
2719
- } else if (filePhases.phases.size > 0) {
2720
- phases = filePhases;
2721
- }
2722
- const fileSkills = await loadSkills(workspace);
2723
- const skillMap = /* @__PURE__ */ new Map();
2724
- for (const skill of fileSkills) {
2725
- skillMap.set(skill.name, skill);
2726
- }
2727
- for (const skill of context.skills) {
2728
- skillMap.set(skill.name, skill);
2729
- }
2730
- const skills = Array.from(skillMap.values());
2731
- return { phases, skills };
2732
- }
2733
- async runWithLifecycle(executor) {
2734
- if (this.activeRun) {
2735
- throw new Error("Agent is already running.");
2736
- }
2737
- let resolvePromise;
2738
- const abortController = new AbortController();
2739
- const promise = new Promise((resolve6) => {
2740
- resolvePromise = resolve6;
2741
- });
2742
- this.activeRun = { promise, resolve: resolvePromise, abortController };
2743
- this.state.isRunning = true;
2744
- try {
2745
- const result = await executor(abortController.signal);
2746
- resolvePromise(result);
2747
- return result;
2748
- } catch (error) {
2749
- this.handleRunFailure(error, abortController.signal.aborted);
2750
- resolvePromise(void 0);
2751
- throw error;
2752
- } finally {
2753
- this.finishRun();
2754
- }
2755
- }
2756
- finishRun() {
2757
- this.state.isRunning = false;
2758
- this.activeRun = void 0;
2759
- }
2760
- async run(config) {
2761
- const resolved = this.resolveRunConfig(config);
2762
- const previousSessionId = this.state.sessionId ?? this.options.sessionId;
2763
- const sessionId = resolved.sessionId ?? this.state.sessionId;
2764
- const hadExistingSession = Boolean(sessionId && previousSessionId === sessionId);
2765
- this.options = resolved;
2766
- if (sessionId) {
2767
- this.state.sessionId = sessionId;
2768
- }
2769
- this.state.context = cloneAgentContext(resolved.context);
2770
- this.state.model = resolved.model;
2771
- this.state.tools = resolved.context.tools ?? [];
2772
- this.state.currentResult = void 0;
2773
- this.state.error = void 0;
2774
- const { phases, skills } = await this.discoverResources(resolved.context);
2775
- const contextWithSkills = {
2776
- ...resolved.context,
2777
- skills
2778
- };
2779
- return this.runWithLifecycle(async (signal) => {
2780
- const emit = (event) => {
2781
- this.processEvents(event);
2782
- };
2783
- const beforeToolCall = async (input) => {
2784
- if (resolved.beforeToolCall) {
2785
- const userResult = await resolved.beforeToolCall(input);
2786
- if (!userResult.allow) return userResult;
2787
- }
2788
- const extResult = await this.handleBeforeToolCall(input.tool, input.args);
2789
- if (!extResult.allow) {
2790
- return { allow: false, reason: extResult.reason ?? "Blocked by extension" };
2791
- }
2792
- return { allow: true };
2793
- };
2794
- const afterToolCall = async (input) => {
2795
- let result2 = input.result;
2796
- if (resolved.afterToolCall) {
2797
- result2 = await resolved.afterToolCall({ tool: input.tool, result: result2 });
2798
- }
2799
- return this.handleAfterToolCall(input.tool, result2);
2800
- };
2801
- const result = await runAgentLoop({
2802
- context: contextWithSkills,
2803
- sessionId: sessionId ?? createId("ses"),
2804
- model: resolved.model,
2805
- stream: resolved.stream,
2806
- maxAttempts: resolved.maxAttempts,
2807
- signal,
2808
- beforeToolCall,
2809
- afterToolCall,
2810
- beforePhase: (phaseId, input) => this.handleBeforePhase(phaseId, input),
2811
- afterPhase: (phaseId, output) => this.handleAfterPhase(phaseId, output),
2812
- beforePrompt: (phaseId, input) => this.handleBeforePrompt(phaseId, input),
2813
- phases,
2814
- emit,
2815
- onMessage: resolved.onMessage,
2816
- onOutcome: resolved.onOutcome,
2817
- onModelTranscript: resolved.onModelTranscript
2818
- });
2819
- this.state.sessionId = result.sessionId;
2820
- this.state.context = {
2821
- ...cloneAgentContext(resolved.context),
2822
- messages: snapshotMessages(result.messages)
2823
- };
2824
- this.state.currentResult = result;
2825
- this.options = {
2826
- ...resolved,
2827
- sessionId: result.sessionId,
2828
- context: cloneAgentContext(this.state.context)
2829
- };
2830
- return result;
2831
- });
2832
- }
2833
- abort(reason = "Aborted by caller.") {
2834
- this.activeRun?.abortController.abort(reason);
2835
- }
2836
- /**
2837
- * Get formatted skill content for LLM consumption.
2838
- *
2839
- * Finds the skill by name and returns the formatted content using `formatSkillInvocation`.
2840
- * This is a programmatic API for developers to invoke skills directly.
2841
- *
2842
- * @param name - The skill name to look up
2843
- * @param additionalInstructions - Optional additional instructions to append
2844
- * @returns Formatted skill content string
2845
- */
2846
- skill(name, additionalInstructions) {
2847
- const skill = this.state.context.skills.find((s) => s.name === name);
2848
- if (!skill) {
2849
- throw new Error(`Unknown skill: ${name}`);
2850
- }
2851
- const content = readSkillContent(skill);
2852
- return additionalInstructions ? `${content}
2853
-
2854
- ${additionalInstructions}` : content;
2855
- }
2856
- /**
2857
- * Get formatted phase content for LLM consumption.
2858
- *
2859
- * Finds the phase by name and returns the formatted content.
2860
- * This is a programmatic API for developers to invoke phases directly.
2861
- *
2862
- * @param name - The phase name to look up
2863
- * @returns Formatted phase content string, or empty string if not found
2864
- */
2865
- async phase(name) {
2866
- const workspace = resolveWorkspacePaths({ cwd: this.options.cwd, rowanDir: this.options.rowanDir });
2867
- const { loadPhase: loadPhase2, readPhaseContent: readPhaseContent2 } = await Promise.resolve().then(() => (init_loader2(), loader_exports));
2868
- try {
2869
- const phase = await loadPhase2(name, workspace);
2870
- return readPhaseContent2(phase);
2871
- } catch {
2872
- return "";
2873
- }
2874
- }
2875
- async waitForIdle() {
2876
- if (!this.activeRun) {
2877
- return;
2878
- }
2879
- await this.activeRun.promise.catch(() => void 0);
2880
- await this.flushEvents().catch(() => void 0);
2881
- }
2882
- resolveRunConfig(config) {
2883
- const context = cloneAgentContext(config?.context ?? this.createContextSnapshot());
2884
- return {
2885
- ...this.options,
2886
- ...config,
2887
- context,
2888
- sessionId: config?.sessionId ?? this.state.sessionId ?? this.options.sessionId
2889
- };
2890
- }
2891
- createContextSnapshot() {
2892
- return cloneAgentContext(this.state.context);
2893
- }
2894
- };
2895
- function cloneAgentContext(context) {
2896
- return {
2897
- systemPrompt: context.systemPrompt,
2898
- messages: snapshotMessages(context.messages),
2899
- tools: context.tools.slice(),
2900
- skills: context.skills.slice()
2901
- };
2902
- }
2903
-
2904
- // src/harness/session/session.ts
2905
- var import_typebox3 = __toESM(require("typebox"), 1);
2906
- var SESSION_SCHEMA_VERSION = "0.4.4";
2907
- var AgentMessageSchema = import_typebox3.default.Object({
2908
- id: import_typebox3.default.String(),
2909
- role: import_typebox3.default.Union([
2910
- import_typebox3.default.Literal("system"),
2911
- import_typebox3.default.Literal("user"),
2912
- import_typebox3.default.Literal("assistant"),
2913
- import_typebox3.default.Literal("tool")
2914
- ]),
2915
- content: import_typebox3.default.Union([import_typebox3.default.String(), import_typebox3.default.Array(import_typebox3.default.Unknown())]),
2916
- createdAt: import_typebox3.default.String(),
2917
- metadata: import_typebox3.default.Optional(import_typebox3.default.Record(import_typebox3.default.String(), import_typebox3.default.Unknown()))
2918
- });
2919
- var SkillSchema = import_typebox3.default.Object({
2920
- name: import_typebox3.default.String(),
2921
- description: import_typebox3.default.String(),
2922
- filePath: import_typebox3.default.String(),
2923
- baseDir: import_typebox3.default.String(),
2924
- content: import_typebox3.default.String(),
2925
- disableModelInvocation: import_typebox3.default.Boolean()
2926
- });
2927
- function createMessage2(role, content, metadata) {
2928
- return {
2929
- id: createId("msg"),
2930
- role,
2931
- content,
2932
- createdAt: createTimestamp(),
2933
- ...metadata ? { metadata } : {}
2934
- };
2935
- }
2936
- function createSession(input) {
2937
- const createdAt = createTimestamp();
2938
- const messages = [
2939
- createMessage2("user", input.input)
2940
- ];
2941
- return {
2942
- version: SESSION_SCHEMA_VERSION,
2943
- id: input.id ?? createId("ses"),
2944
- ...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
2945
- systemPrompt: input.systemPrompt,
2946
- input: input.input,
2947
- messages,
2948
- log: [],
2949
- skills: input.skills ?? [],
2950
- createdAt,
2951
- updatedAt: createdAt,
2952
- ...input.title ? { title: input.title } : {}
2953
- };
2954
- }
2955
- function appendUserTurn(session, input) {
2956
- session.messages.push(createMessage2("user", input));
2957
- session.updatedAt = createTimestamp();
2958
- return session;
2959
- }
2960
-
2961
- // src/harness/session/session-manager.ts
2962
- var SESSION_MANAGER_SCHEMA_VERSION = "0.4.4";
2963
- function clone(value) {
2964
- return JSON.parse(JSON.stringify(value));
2965
- }
2966
- function createSessionHeader(input) {
2967
- const createdAt = createTimestamp();
2968
- return {
2969
- type: "header",
2970
- id: input.id ?? createId("ses"),
2971
- version: SESSION_MANAGER_SCHEMA_VERSION,
2972
- createdAt,
2973
- updatedAt: createdAt,
2974
- systemPrompt: input.systemPrompt,
2975
- input: input.input,
2976
- ...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
2977
- skills: input.skills?.map(clone) ?? [],
2978
- ...input.title ? { title: input.title } : {},
2979
- currentLeafId: null
2980
- };
2981
- }
2982
- function filterExecutionTurns(steps, filter = {}) {
2983
- return steps.filter((step) => {
2984
- if (filter.phase && step.phase !== filter.phase) return false;
2985
- if (filter.afterMs !== void 0 && step.requestedAtMs < filter.afterMs) return false;
2986
- return true;
2987
- }).map(clone);
2988
- }
2989
- var InMemorySessionManager = class _InMemorySessionManager {
2990
- constructor(header, entries = []) {
2991
- this.header = header;
2992
- this.entries = entries;
2993
- }
2994
- header;
2995
- entries;
2996
- static create(input) {
2997
- return new _InMemorySessionManager(createSessionHeader(input));
2998
- }
2999
- static fromRecords(records) {
3000
- const [header, ...entries] = records;
3001
- if (!header || header.type !== "header") {
3002
- throw new Error("Session records must start with a header.");
3003
- }
3004
- return new _InMemorySessionManager(clone(header), clone(entries));
3005
- }
3006
- getSessionId() {
3007
- return this.header.id;
3008
- }
3009
- getSessionFile() {
3010
- return void 0;
3011
- }
3012
- async getHeader() {
3013
- return clone(this.header);
3014
- }
3015
- async appendMessage(message) {
3016
- return this.appendEntry({ type: "message", message: clone(message) });
3017
- }
3018
- async appendOutcome(outcome) {
3019
- return this.appendEntry({ type: "outcome", outcome: clone(outcome) });
3020
- }
3021
- async appendExecutionTurn(turn) {
3022
- return this.appendEntry({
3023
- type: "execution_turn",
3024
- turn: clone({
3025
- ...turn,
3026
- sessionId: this.header.id
3027
- })
3028
- });
3029
- }
3030
- async appendCompaction(input) {
3031
- return this.appendEntry({ type: "compaction", ...input });
3032
- }
3033
- async appendBranchSummary(input) {
3034
- return this.appendEntry({ type: "branch_summary", ...input });
3035
- }
3036
- async appendSessionInfo(input) {
3037
- this.header = {
3038
- ...this.header,
3039
- title: input.title,
3040
- updatedAt: createTimestamp()
3041
- };
3042
- return this.appendEntry({ type: "session_info", title: input.title });
3043
- }
3044
- async appendCustom(input) {
3045
- return this.appendEntry({ type: "custom", customType: input.customType, data: clone(input.data) });
3046
- }
3047
- async appendSessionState(state) {
3048
- return this.appendEntry({ type: "session_state", state: clone(state) });
3049
- }
3050
- async appendModelTranscript(transcript, meta) {
3051
- return this.appendEntry({
3052
- type: "model_transcript",
3053
- transcript: clone(transcript),
3054
- ...meta?.phase ? { phase: meta.phase } : {},
3055
- ...meta?.model ? { model: clone(meta.model) } : {}
3056
- });
3057
- }
3058
- async getSessionState() {
3059
- for (let i = this.entries.length - 1; i >= 0; i--) {
3060
- const entry = this.entries[i];
3061
- if (entry.type === "session_state") {
3062
- return clone(entry.state);
3063
- }
3064
- }
3065
- return void 0;
3066
- }
3067
- async branch(entryId) {
3068
- if (entryId !== null && !this.entries.some((entry) => entry.id === entryId)) {
3069
- throw new Error(`Session entry not found: ${entryId}`);
3070
- }
3071
- this.header = {
3072
- ...this.header,
3073
- currentLeafId: entryId,
3074
- updatedAt: createTimestamp()
3075
- };
3076
- }
3077
- async buildAgentContext(input = {}) {
3078
- const entries = this.entriesForLeaf(input.leafId ?? this.header.currentLeafId ?? null);
3079
- return {
3080
- systemPrompt: this.header.systemPrompt,
3081
- messages: entries.filter((entry) => entry.type === "message").map((entry) => entry.message).map(clone),
3082
- tools: input.tools?.slice() ?? [],
3083
- skills: input.skills?.map(clone) ?? this.header.skills.map(clone)
3084
- };
3085
- }
3086
- async listEntries() {
3087
- return this.entries.map(clone);
3088
- }
3089
- async loadExecutionTurns(filter) {
3090
- return filterExecutionTurns(
3091
- this.entries.filter((entry) => entry.type === "execution_turn").map((entry) => entry.turn),
3092
- filter
3093
- );
3094
- }
3095
- appendImportedEntry(entry) {
3096
- this.entries.push(clone(entry));
3097
- this.header = {
3098
- ...this.header,
3099
- currentLeafId: entry.id,
3100
- updatedAt: entry.timestamp
3101
- };
3102
- }
3103
- appendEntry(input) {
3104
- const timestamp = createTimestamp();
3105
- const entry = {
3106
- id: createId("entry"),
3107
- parentId: this.header.currentLeafId ?? null,
3108
- timestamp,
3109
- ...input
3110
- };
3111
- this.entries.push(entry);
3112
- this.header = {
3113
- ...this.header,
3114
- currentLeafId: entry.id,
3115
- updatedAt: timestamp
3116
- };
3117
- return entry.id;
3118
- }
3119
- entriesForLeaf(leafId) {
3120
- if (!leafId) {
3121
- return [];
3122
- }
3123
- const byId = new Map(this.entries.map((entry) => [entry.id, entry]));
3124
- const ordered = [];
3125
- let currentId = leafId;
3126
- while (currentId) {
3127
- const entry = byId.get(currentId);
3128
- if (!entry) {
3129
- throw new Error(`Session entry not found: ${currentId}`);
3130
- }
3131
- ordered.unshift(entry);
3132
- currentId = entry.parentId;
3133
- }
3134
- return ordered;
3135
- }
3136
- };
3137
- function summarizeSessionManagerRecords(records) {
3138
- const [header, ...entries] = records;
3139
- if (!header || header.type !== "header") {
3140
- throw new Error("Session records must start with a header.");
3141
- }
3142
- const messages = entries.filter((entry) => entry.type === "message").map((entry) => entry.message);
3143
- const latestMessageContent = messages.at(-1)?.content;
3144
- const latestMessage = latestMessageContent ? messageContentText(latestMessageContent) : void 0;
3145
- return {
3146
- id: header.id,
3147
- ...header.title ? { title: header.title } : {},
3148
- createdAt: header.createdAt,
3149
- updatedAt: header.updatedAt,
3150
- messageCount: messages.length,
3151
- ...latestMessage ? { latestMessage } : {}
3152
- };
3153
- }
3154
-
3155
- // src/harness/session/jsonl.ts
3156
- var import_promises6 = require("fs/promises");
3157
- var import_node_path7 = require("path");
3158
- var SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_-]+$/;
3159
- function isPathInside(parent, child) {
3160
- const relativePath = (0, import_node_path7.relative)(parent, child);
3161
- return Boolean(relativePath) && !relativePath.startsWith("..") && !relativePath.includes(`..${import_node_path7.sep}`);
3162
- }
3163
- function safeSessionPath(sessionsDir, id) {
3164
- if (!SESSION_ID_PATTERN.test(id)) {
3165
- return void 0;
3166
- }
3167
- const root = (0, import_node_path7.resolve)(sessionsDir);
3168
- const path = (0, import_node_path7.resolve)(root, `${id}.jsonl`);
3169
- return isPathInside(root, path) ? path : void 0;
3170
- }
3171
- function parseRecord(line) {
3172
- const value = JSON.parse(line);
3173
- if (!value || typeof value !== "object" || !("type" in value)) {
3174
- throw new Error("Invalid session JSONL record.");
3175
- }
3176
- return value;
3177
- }
3178
- async function readRecords(path) {
3179
- const text = await (0, import_promises6.readFile)(path, "utf8").catch((error) => {
3180
- if (error.code === "ENOENT") {
3181
- return void 0;
3182
- }
3183
- throw error;
3184
- });
3185
- if (!text) {
3186
- return void 0;
3187
- }
3188
- const records = text.split("\n").map((line) => line.trim()).filter(Boolean).map(parseRecord);
3189
- const header = records[0];
3190
- if (!header || header.type !== "header") {
3191
- throw new Error("Session JSONL must start with a header record.");
3192
- }
3193
- const lastEntry = records.slice(1).reverse().find((record) => record.type !== "header");
3194
- return [
3195
- {
3196
- ...header,
3197
- updatedAt: lastEntry?.timestamp ?? header.updatedAt,
3198
- currentLeafId: lastEntry?.id ?? header.currentLeafId ?? null
3199
- },
3200
- ...records.slice(1)
3201
- ];
3202
- }
3203
- async function appendRecord(path, record) {
3204
- await (0, import_promises6.appendFile)(path, `${JSON.stringify(record)}
3205
- `, "utf8");
3206
- }
3207
- var LocalJsonlSessionManager = class _LocalJsonlSessionManager {
3208
- constructor(sessionsDir, filePath, inner) {
3209
- this.sessionsDir = sessionsDir;
3210
- this.filePath = filePath;
3211
- this.inner = inner;
3212
- }
3213
- sessionsDir;
3214
- filePath;
3215
- inner;
3216
- static async create(sessionsDir, input) {
3217
- const header = createSessionHeader(input);
3218
- const path = safeSessionPath(sessionsDir, header.id);
3219
- if (!path) {
3220
- throw new Error(`Invalid session id: ${header.id}`);
3221
- }
3222
- const exists = await (0, import_promises6.stat)(path).then(() => true).catch((error) => {
3223
- if (error.code === "ENOENT") {
3224
- return false;
3225
- }
3226
- throw error;
3227
- });
3228
- if (exists) {
3229
- throw new Error(`Session already exists: ${header.id}`);
3230
- }
3231
- await (0, import_promises6.mkdir)(sessionsDir, { recursive: true });
3232
- await (0, import_promises6.writeFile)(path, `${JSON.stringify(header)}
3233
- `, "utf8");
3234
- return new _LocalJsonlSessionManager(
3235
- sessionsDir,
3236
- path,
3237
- InMemorySessionManager.fromRecords([header])
3238
- );
3239
- }
3240
- static async open(sessionsDir, id) {
3241
- const path = safeSessionPath(sessionsDir, id);
3242
- if (!path) {
3243
- return void 0;
3244
- }
3245
- const records = await readRecords(path);
3246
- if (!records) {
3247
- return void 0;
3248
- }
3249
- const header = records[0];
3250
- if (header.id !== id) {
3251
- throw new Error(`Session id mismatch: expected ${id}, found ${header.id}`);
3252
- }
3253
- return new _LocalJsonlSessionManager(
3254
- sessionsDir,
3255
- path,
3256
- InMemorySessionManager.fromRecords(records)
3257
- );
3258
- }
3259
- static async list(sessionsDir) {
3260
- const entries = await (0, import_promises6.readdir)(sessionsDir, { withFileTypes: true }).catch((error) => {
3261
- if (error.code === "ENOENT") {
3262
- return [];
3263
- }
3264
- throw error;
3265
- });
3266
- const sessions = await Promise.all(
3267
- entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map(async (entry) => {
3268
- const path = (0, import_node_path7.join)(sessionsDir, entry.name);
3269
- const records = await readRecords(path);
3270
- return records ? summarizeSessionManagerRecords(records) : void 0;
3271
- })
3272
- );
3273
- return sessions.filter((session) => Boolean(session)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
3274
- }
3275
- static async delete(sessionsDir, id) {
3276
- const path = safeSessionPath(sessionsDir, id);
3277
- if (!path) {
3278
- return false;
3279
- }
3280
- return (0, import_promises6.unlink)(path).then(() => true).catch((error) => {
3281
- if (error.code === "ENOENT") {
3282
- return false;
3283
- }
3284
- throw error;
3285
- });
3286
- }
3287
- getSessionId() {
3288
- return this.inner.getSessionId();
3289
- }
3290
- getSessionFile() {
3291
- return this.filePath;
3292
- }
3293
- async getHeader() {
3294
- return this.inner.getHeader();
3295
- }
3296
- async appendMessage(message) {
3297
- return this.appendThroughInner(() => this.inner.appendMessage(message));
3298
- }
3299
- async appendOutcome(outcome) {
3300
- return this.appendThroughInner(() => this.inner.appendOutcome(outcome));
3301
- }
3302
- async appendExecutionTurn(turn) {
3303
- return this.appendThroughInner(() => this.inner.appendExecutionTurn(turn));
3304
- }
3305
- async appendCompaction(input) {
3306
- return this.appendThroughInner(() => this.inner.appendCompaction(input));
3307
- }
3308
- async appendBranchSummary(input) {
3309
- return this.appendThroughInner(() => this.inner.appendBranchSummary(input));
3310
- }
3311
- async appendSessionInfo(input) {
3312
- return this.appendThroughInner(() => this.inner.appendSessionInfo(input));
3313
- }
3314
- async appendCustom(input) {
3315
- return this.appendThroughInner(() => this.inner.appendCustom(input));
3316
- }
3317
- async appendSessionState(state) {
3318
- return this.appendThroughInner(() => this.inner.appendSessionState(state));
3319
- }
3320
- async appendModelTranscript(transcript, meta) {
3321
- return this.appendThroughInner(() => this.inner.appendModelTranscript(transcript, meta));
3322
- }
3323
- async getSessionState() {
3324
- return this.inner.getSessionState();
3325
- }
3326
- async branch(entryId) {
3327
- await this.inner.branch(entryId);
3328
- if (entryId) {
3329
- await this.appendThroughInner(() => this.inner.appendBranchSummary({
3330
- fromId: entryId,
3331
- summary: `Selected branch at ${entryId}`
3332
- }));
3333
- }
3334
- }
3335
- async buildAgentContext(input) {
3336
- return this.inner.buildAgentContext(input);
3337
- }
3338
- async listEntries() {
3339
- return this.inner.listEntries();
3340
- }
3341
- async loadExecutionTurns(filter) {
3342
- return this.inner.loadExecutionTurns(filter);
3343
- }
3344
- async appendThroughInner(append) {
3345
- const before = await this.inner.listEntries();
3346
- const entryId = await append();
3347
- const after = await this.inner.listEntries();
3348
- const entry = after.find((candidate) => candidate.id === entryId);
3349
- if (!entry || before.some((candidate) => candidate.id === entry.id)) {
3350
- throw new Error(`Session entry was not appended: ${entryId}`);
3351
- }
3352
- await (0, import_promises6.mkdir)(this.sessionsDir, { recursive: true });
3353
- await appendRecord(this.filePath, entry);
3354
- return entryId;
3355
- }
3356
- };
3357
-
3358
- // src/harness/env/index.ts
3359
- init_path();
3360
-
3361
- // src/index.ts
3362
- init_config();
3363
-
3364
- // src/event-stream.ts
3365
- var EventStream = class {
3366
- queue = [];
3367
- waiting = [];
3368
- done = false;
3369
- finalResultPromise;
3370
- resolveFinalResult;
3371
- isComplete;
3372
- extractResult;
3373
- constructor(isComplete, extractResult) {
3374
- this.isComplete = isComplete;
3375
- this.extractResult = extractResult;
3376
- this.finalResultPromise = new Promise((resolve6) => {
3377
- this.resolveFinalResult = resolve6;
3378
- });
3379
- }
3380
- push(event) {
3381
- if (this.done) return;
3382
- if (this.isComplete(event)) {
3383
- this.done = true;
3384
- this.resolveFinalResult(this.extractResult(event));
3385
- }
3386
- const waiter = this.waiting.shift();
3387
- if (waiter) {
3388
- waiter({ value: event, done: false });
3389
- } else {
3390
- this.queue.push(event);
3391
- }
3392
- }
3393
- end(result) {
3394
- this.done = true;
3395
- if (result !== void 0) {
3396
- this.resolveFinalResult(result);
3397
- }
3398
- while (this.waiting.length > 0) {
3399
- const waiter = this.waiting.shift();
3400
- waiter({ value: void 0, done: true });
3401
- }
3402
- }
3403
- async *[Symbol.asyncIterator]() {
3404
- while (true) {
3405
- if (this.queue.length > 0) {
3406
- yield this.queue.shift();
3407
- } else if (this.done) {
3408
- return;
3409
- } else {
3410
- const result = await new Promise((resolve6) => this.waiting.push(resolve6));
3411
- if (result.done) return;
3412
- yield result.value;
3413
- }
3414
- }
3415
- }
3416
- result() {
3417
- return this.finalResultPromise;
3418
- }
3419
- };
3420
- var AgentEventStream = class extends EventStream {
3421
- constructor() {
3422
- super(
3423
- (event) => event.type === "agent_end",
3424
- (event) => event.type === "agent_end" ? event.messages : []
3425
- );
3426
- }
3427
- };
3428
-
3429
- // src/extensions/hooks.ts
3430
- var HookError = class extends Error {
3431
- constructor(eventType, message, cause) {
3432
- super(message, cause === void 0 ? void 0 : { cause });
3433
- this.eventType = eventType;
3434
- this.name = "HookError";
3435
- }
3436
- eventType;
3437
- };
3438
- var HooksManager = class {
3439
- handlers = /* @__PURE__ */ new Map();
3440
- /**
3441
- * Register a hook handler.
3442
- * Handlers execute in registration order.
3443
- *
3444
- * @param eventType - Event type
3445
- * @param handler - Handler function
3446
- */
3447
- on(eventType, handler) {
3448
- const handlers = this.handlers.get(eventType) ?? [];
3449
- handlers.push(handler);
3450
- this.handlers.set(eventType, handlers);
3451
- }
3452
- /**
3453
- * Unregister a hook handler.
3454
- */
3455
- off(eventType, handler) {
3456
- const handlers = this.handlers.get(eventType);
3457
- if (!handlers) return;
3458
- const index = handlers.indexOf(handler);
3459
- if (index >= 0) handlers.splice(index, 1);
3460
- }
3461
- /**
3462
- * Clear all handlers, or clear handlers for specified event type.
3463
- */
3464
- clear(eventType) {
3465
- if (eventType) {
3466
- this.handlers.delete(eventType);
3467
- } else {
3468
- this.handlers.clear();
3469
- }
3470
- }
3471
- /**
3472
- * Check if there are handlers registered for specified event type.
3473
- */
3474
- has(eventType) {
3475
- const handlers = this.handlers.get(eventType);
3476
- return handlers !== void 0 && handlers.length > 0;
3477
- }
3478
- /**
3479
- * Get handler count for specified event type.
3480
- */
3481
- count(eventType) {
3482
- return this.handlers.get(eventType)?.length ?? 0;
3483
- }
3484
- /**
3485
- * Emit event (listen-only, ignore return values).
3486
- * Errors are collected and thrown.
3487
- *
3488
- * @param eventType - Event type
3489
- * @param event - Event object
3490
- */
3491
- async emit(eventType, event) {
3492
- const handlers = this.handlers.get(eventType);
3493
- if (!handlers?.length) return;
3494
- const errors = [];
3495
- await Promise.allSettled(
3496
- handlers.map(async (handler) => {
3497
- try {
3498
- await handler(event);
3499
- } catch (error) {
3500
- errors.push(
3501
- error instanceof Error ? error : new Error(String(error))
3502
- );
3503
- }
3504
- })
3505
- );
3506
- if (errors.length > 0) {
3507
- throw new HookError(
3508
- eventType,
3509
- `${errors.length} handler(s) failed for "${eventType}"`,
3510
- errors[0]
3511
- );
3512
- }
3513
- }
3514
- /**
3515
- * Emit event and return first non-undefined result (short-circuit).
3516
- *
3517
- * @param eventType - Event type
3518
- * @param event - Event object
3519
- * @returns First non-undefined result, or undefined
3520
- */
3521
- async emitFirst(eventType, event) {
3522
- const handlers = this.handlers.get(eventType);
3523
- if (!handlers?.length) return void 0;
3524
- for (const handler of handlers) {
3525
- try {
3526
- const result = await handler(event);
3527
- if (result !== void 0) return result;
3528
- } catch (error) {
3529
- throw new HookError(
3530
- eventType,
3531
- `Handler failed for "${eventType}"`,
3532
- error instanceof Error ? error : new Error(String(error))
3533
- );
3534
- }
3535
- }
3536
- return void 0;
3537
- }
3538
- };
3539
- var _globalHooks;
3540
- function getGlobalHooks() {
3541
- _globalHooks ??= new HooksManager();
3542
- return _globalHooks;
3543
- }
3544
- function resetGlobalHooks() {
3545
- _globalHooks = void 0;
3546
- }
3547
-
3548
- // src/extensions/runner.ts
3549
- var import_node_child_process = require("child_process");
3550
- var import_models2 = require("@rowan-agent/models");
3551
-
3552
- // src/extensions/types.ts
3553
- function createSourceInfo(extensionPath, options = {}) {
3554
- const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
3555
- const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
3556
- return {
3557
- source,
3558
- baseDir: options.baseDir,
3559
- displayName
3560
- };
3561
- }
3562
- function createExtension(extensionPath, resolvedPath, sourceInfo) {
3563
- return {
3564
- path: extensionPath,
3565
- resolvedPath,
3566
- sourceInfo,
3567
- handlers: /* @__PURE__ */ new Map(),
3568
- tools: /* @__PURE__ */ new Map(),
3569
- phases: /* @__PURE__ */ new Map()
3570
- };
3571
- }
3572
- function createExtensionRuntime() {
3573
- const state = {};
3574
- const assertActive = () => {
3575
- if (state.staleMessage) {
3576
- throw new Error(state.staleMessage);
3577
- }
3578
- };
3579
- const runtime = {
3580
- assertActive,
3581
- invalidate: (message) => {
3582
- state.staleMessage ??= message ?? "This extension context is stale after session replacement or reload. Do not use a captured extension API after the runner has been replaced.";
3583
- },
3584
- pendingProviderRegistrations: [],
3585
- // Pre-bind: queue registrations so bind() can flush them once the
3586
- // model registry is available. bind() replaces both with direct calls.
3587
- registerProvider: (name, config, extensionPath = "<unknown>") => {
3588
- runtime.pendingProviderRegistrations.push({ name, config, extensionPath });
3589
- },
3590
- unregisterProvider: (name) => {
3591
- runtime.pendingProviderRegistrations = runtime.pendingProviderRegistrations.filter(
3592
- (r) => r.name !== name
3593
- );
3594
- }
3595
- };
3596
- return runtime;
3597
- }
3598
-
3599
- // src/extensions/runner.ts
3600
- init_config();
3601
-
3602
- // src/extensions/event-bus.ts
3603
- function createEventBus() {
3604
- const listeners = /* @__PURE__ */ new Map();
3605
- return {
3606
- on(event, listener) {
3607
- const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
3608
- set.add(listener);
3609
- listeners.set(event, set);
3610
- return () => {
3611
- set.delete(listener);
3612
- if (set.size === 0) listeners.delete(event);
3613
- };
3614
- },
3615
- emit(event, ...args) {
3616
- const set = listeners.get(event);
3617
- if (!set) return;
3618
- for (const listener of set) {
3619
- try {
3620
- listener(...args);
3621
- } catch (err) {
3622
- console.error(`[event-bus] Listener error for "${event}":`, err);
3623
- }
3624
- }
3625
- },
3626
- off(event) {
3627
- if (event) {
3628
- listeners.delete(event);
3629
- } else {
3630
- listeners.clear();
3631
- }
3632
- },
3633
- has(event) {
3634
- const set = listeners.get(event);
3635
- return set !== void 0 && set.size > 0;
3636
- },
3637
- count(event) {
3638
- return listeners.get(event)?.size ?? 0;
3639
- }
3640
- };
3641
- }
3642
-
3643
- // src/extensions/runner.ts
3644
- async function execCommand(command, args, cwd, options) {
3645
- return new Promise((resolve6, reject) => {
3646
- const child = (0, import_node_child_process.execFile)(
3647
- command,
3648
- args,
3649
- {
3650
- cwd: options?.cwd ?? cwd,
3651
- env: options?.env ? { ...process.env, ...options.env } : void 0,
3652
- timeout: options?.timeout,
3653
- maxBuffer: 10 * 1024 * 1024
3654
- },
3655
- (error, stdout, stderr) => {
3656
- if (error && error.killed && options?.signal?.aborted) {
3657
- reject(new Error("Command was aborted"));
3658
- return;
3659
- }
3660
- resolve6({
3661
- exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
3662
- stdout: stdout ?? "",
3663
- stderr: stderr ?? ""
3664
- });
3665
- }
3666
- );
3667
- if (options?.signal) {
3668
- options.signal.addEventListener(
3669
- "abort",
3670
- () => {
3671
- child.kill("SIGTERM");
3672
- },
3673
- { once: true }
3674
- );
3675
- }
3676
- });
3677
- }
3678
- function applyProviderRegistration(config) {
3679
- if (config.streamSimple) {
3680
- (0, import_models2.registerApiProvider)({ protocol: config.protocol, stream: config.streamSimple });
3681
- }
3682
- for (const modelConfig of config.models) {
3683
- (0, import_models2.registerModel)({
3684
- id: modelConfig.id,
3685
- name: modelConfig.name,
3686
- protocol: config.protocol,
3687
- provider: config.id,
3688
- baseUrl: config.baseUrl,
3689
- reasoning: modelConfig.reasoning,
3690
- input: modelConfig.input,
3691
- cost: modelConfig.cost,
3692
- contextWindow: modelConfig.contextWindow,
3693
- maxTokens: modelConfig.maxTokens,
3694
- ...config.headers ? { headers: config.headers } : {}
3695
- });
3696
- }
3697
- }
3698
- function applyProviderUnregistration(name) {
3699
- (0, import_models2.unregisterProviderModels)(name);
3700
- }
3701
- var ExtensionRunner = class {
3702
- hooks;
3703
- runtime;
3704
- events;
3705
- cwd;
3706
- abortController = new AbortController();
3707
- _idle = true;
3708
- // Per-extension tracking
3709
- extensions = [];
3710
- // Phase management
3711
- phases = /* @__PURE__ */ new Map();
3712
- _phaseCache = null;
3713
- // Provider management
3714
- pendingProviders = [];
3715
- bound = false;
3716
- // Error listeners
3717
- errorListeners = /* @__PURE__ */ new Set();
3718
- // Loaded extension metadata (pre-initialization form)
3719
- loadedExtensions = [];
3720
- /** Current agent context — set by the agent before each phase */
3721
- currentContext;
3722
- constructor(options) {
3723
- this.hooks = new HooksManager();
3724
- this.runtime = createExtensionRuntime();
3725
- this.events = createEventBus();
3726
- this.cwd = options?.cwd ?? process.cwd();
3727
- }
3728
- /** Whether the agent is currently idle (not streaming). */
3729
- get isIdle() {
3730
- return this._idle;
3731
- }
3732
- /** Set idle state — called by the agent loop. */
3733
- setIdle(idle) {
3734
- this._idle = idle;
3735
- }
3736
- /** Abort signal for the current runner instance. */
3737
- get signal() {
3738
- return this.abortController.signal;
3739
- }
3740
- /** Abort the current runner operation. */
3741
- abort() {
3742
- this.abortController.abort();
3743
- }
3744
- // ---------------------------------------------------------------------------
3745
- // Error handling
3746
- // ---------------------------------------------------------------------------
3747
- /**
3748
- * Register an error listener.
3749
- * Returns an unsubscribe function.
3750
- */
3751
- onError(listener) {
3752
- this.errorListeners.add(listener);
3753
- return () => this.errorListeners.delete(listener);
3754
- }
3755
- /**
3756
- * Emit a structured extension error to all listeners.
3757
- */
3758
- emitError(error) {
3759
- for (const listener of this.errorListeners) {
3760
- try {
3761
- listener(error);
3762
- } catch (err) {
3763
- console.error("[extension-runner] Error listener failed:", err);
3764
- }
3765
- }
3766
- }
3767
- // ---------------------------------------------------------------------------
3768
- // Lifecycle: invalidate / assertActive
3769
- // ---------------------------------------------------------------------------
3770
- /**
3771
- * Mark all extension contexts as stale.
3772
- * After calling this, any captured ExtensionAPI or ExtensionContext will throw
3773
- * on use. Used during session replacement or reload.
3774
- */
3775
- invalidate(message) {
3776
- this.runtime.invalidate(message);
3777
- }
3778
- // ---------------------------------------------------------------------------
3779
- // Direct hook API
3780
- // ---------------------------------------------------------------------------
3781
- /**
3782
- * Subscribe to a specific hook event type.
3783
- * Returns an unsubscribe function.
3784
- *
3785
- * @example
3786
- * ```ts
3787
- * const unsub = runner.on("before_tool_call", (event) => {
3788
- * return { allow: false, reason: "Blocked" };
3789
- * });
3790
- * unsub(); // Cancel subscription
3791
- * ```
3792
- */
3793
- on(type, handler) {
3794
- this.hooks.on(type, handler);
3795
- return () => this.hooks.off(type, handler);
3796
- }
3797
- /**
3798
- * Subscribe to all events (read-only).
3799
- * Returns an unsubscribe function.
3800
- *
3801
- * @example
3802
- * ```ts
3803
- * const unsub = runner.subscribe((event) => {
3804
- * console.log(event.type);
3805
- * });
3806
- * ```
3807
- */
3808
- subscribe(listener) {
3809
- const handlers = /* @__PURE__ */ new Map();
3810
- for (const eventType of this.getAllEventTypes()) {
3811
- const handler = (event) => listener(event);
3812
- handlers.set(eventType, handler);
3813
- this.hooks.on(eventType, handler);
3814
- }
3815
- return () => {
3816
- for (const [eventType, handler] of handlers) {
3817
- this.hooks.off(eventType, handler);
3818
- }
3819
- };
3820
- }
3821
- getAllEventTypes() {
3822
- return [
3823
- "before_phase",
3824
- "after_phase",
3825
- "before_prompt",
3826
- "before_tool_call",
3827
- "after_tool_call",
3828
- "agent_start",
3829
- "agent_end",
3830
- "turn_start",
3831
- "turn_end",
3832
- "message_start",
3833
- "message_update",
3834
- "message_end",
3835
- "tool_execution_start",
3836
- "tool_execution_update",
3837
- "tool_execution_end",
3838
- "queue_update",
3839
- "save_point",
3840
- "abort",
3841
- "settled"
3842
- ];
3843
- }
3844
- // ---------------------------------------------------------------------------
3845
- // Extension loading
3846
- // ---------------------------------------------------------------------------
3847
- /**
3848
- * Load and initialize extensions.
3849
- * Creates Extension tracking objects and calls each factory with an ExtensionAPI.
3850
- */
3851
- async loadExtensions(extensions) {
3852
- for (const ext of extensions) {
3853
- try {
3854
- const sourceInfo = createSourceInfo(ext.path, {
3855
- source: ext.path.startsWith("<builtin:") ? "builtin" : "local",
3856
- baseDir: ext.resolvedPath.startsWith("<") ? void 0 : ext.resolvedPath
3857
- });
3858
- const extension = createExtension(ext.path, ext.resolvedPath, sourceInfo);
3859
- const api = this.createExtensionAPI(extension, ext.manifest);
3860
- await ext.factory(api);
3861
- this.extensions.push(extension);
3862
- this.loadedExtensions.push(ext);
3863
- this._phaseCache = null;
3864
- } catch (error) {
3865
- const message = error instanceof Error ? error.message : String(error);
3866
- this.emitError({
3867
- extensionPath: ext.path,
3868
- event: "load",
3869
- error: message,
3870
- stack: error instanceof Error ? error.stack : void 0
3871
- });
3872
- throw error;
3873
- }
3874
- }
3875
- }
3876
- // ---------------------------------------------------------------------------
3877
- // Tool management
3878
- // ---------------------------------------------------------------------------
3879
- /**
3880
- * Get all registered tools from all extensions (first registration per name wins).
3881
- */
3882
- getAllRegisteredTools() {
3883
- const toolsByName = /* @__PURE__ */ new Map();
3884
- for (const ext of this.extensions) {
3885
- for (const tool of ext.tools.values()) {
3886
- if (!toolsByName.has(tool.definition.name)) {
3887
- toolsByName.set(tool.definition.name, tool);
3888
- }
3889
- }
3890
- }
3891
- return Array.from(toolsByName.values());
3892
- }
3893
- /**
3894
- * Get a tool definition by name. Returns undefined if not found.
3895
- */
3896
- getToolDefinition(toolName) {
3897
- for (const ext of this.extensions) {
3898
- const tool = ext.tools.get(toolName);
3899
- if (tool) return tool.definition;
3900
- }
3901
- return void 0;
3902
- }
3903
- // ---------------------------------------------------------------------------
3904
- // Handler queries
3905
- // ---------------------------------------------------------------------------
3906
- /**
3907
- * Check if there are handlers registered for specified event type.
3908
- */
3909
- hasHandlers(eventType) {
3910
- return this.hooks.has(eventType);
3911
- }
3912
- /**
3913
- * Get the number of handlers for specified event type.
3914
- */
3915
- handlerCount(eventType) {
3916
- return this.hooks.count(eventType);
3917
- }
3918
- // ---------------------------------------------------------------------------
3919
- // Phase management
3920
- // ---------------------------------------------------------------------------
3921
- getPhase(id) {
3922
- const reg = this.getRegisteredPhase(id);
3923
- if (!reg) return void 0;
3924
- return this.adaptToPhase(reg);
3925
- }
3926
- getPhases() {
3927
- return [...this.collectRegisteredPhases().values()].map(
3928
- (p) => this.adaptToPhase(p)
3929
- );
3930
- }
3931
- createPhaseRegistry(input = {}) {
3932
- const registered = this.collectRegisteredPhases();
3933
- const phases = /* @__PURE__ */ new Map();
3934
- for (const [id, reg] of registered) {
3935
- phases.set(id, this.adaptToPhase(reg));
3936
- }
3937
- const entryPhaseId = input.entryPhaseId ?? null;
3938
- return { phases, entryPhaseId };
3939
- }
3940
- /** Adapt an extension RegisteredPhase to the core Phase type. */
3941
- adaptToPhase(reg) {
3942
- const def = reg.definition;
3943
- return {
3944
- id: def.id,
3945
- name: def.name ?? def.id,
3946
- description: def.description ?? "",
3947
- tools: def.tools,
3948
- skills: def.skills,
3949
- target: def.target,
3950
- input: def.input,
3951
- run: def.run,
3952
- filePath: "",
3953
- baseDir: "",
3954
- content: "",
3955
- ...def.model ? { model: parseModelRef(def.model) } : {}
3956
- };
3957
- }
3958
- // ---------------------------------------------------------------------------
3959
- // Lifecycle
3960
- // ---------------------------------------------------------------------------
3961
- /**
3962
- * Bind the runner — flushes pending provider registrations and
3963
- * replaces runtime stubs with real implementations.
3964
- */
3965
- bind() {
3966
- if (this.bound) return;
3967
- this.bound = true;
3968
- this.flushPendingProviders();
3969
- this.runtime.registerProvider = (_name, config) => {
3970
- applyProviderRegistration(config);
3971
- };
3972
- this.runtime.unregisterProvider = (_name) => {
3973
- applyProviderUnregistration(_name);
3974
- };
3975
- }
3976
- // ---------------------------------------------------------------------------
3977
- // Unified hook emission
3978
- // ---------------------------------------------------------------------------
3979
- /**
3980
- * Generic emit — fire-and-forget for any event type.
3981
- */
3982
- async emit(eventType, event) {
3983
- await this.hooks.emit(eventType, event);
3984
- }
3985
- /**
3986
- * Unified hook emission — returns the first non-undefined result.
3987
- */
3988
- async emitHook(type, event) {
3989
- return this.hooks.emitFirst(type, event);
3990
- }
3991
- // ---------------------------------------------------------------------------
3992
- // Phase hooks (with inline processing)
3993
- // ---------------------------------------------------------------------------
3994
- async emitBeforePhase(phaseId, input) {
3995
- const result = await this.emitHook("before_phase", {
3996
- type: "before_phase",
3997
- phaseId,
3998
- input
3999
- });
4000
- return result ?? {};
4001
- }
4002
- async emitAfterPhase(phaseId, output) {
4003
- const result = await this.emitHook("after_phase", {
4004
- type: "after_phase",
4005
- phaseId,
4006
- output
4007
- });
4008
- return result ?? {};
4009
- }
4010
- async emitBeforePrompt(phaseId, input) {
4011
- const result = await this.emitHook("before_prompt", {
4012
- type: "before_prompt",
4013
- phaseId,
4014
- input
4015
- });
4016
- return result?.input ?? input;
4017
- }
4018
- async emitBeforeToolCall(tool, args) {
4019
- const result = await this.emitHook("before_tool_call", {
4020
- type: "before_tool_call",
4021
- tool,
4022
- args
4023
- });
4024
- return result ?? { allow: true };
4025
- }
4026
- async emitAfterToolCall(tool, result) {
4027
- const hookResult = await this.emitHook("after_tool_call", {
4028
- type: "after_tool_call",
4029
- tool,
4030
- result
4031
- });
4032
- return hookResult?.result ?? result;
4033
- }
4034
- // ---------------------------------------------------------------------------
4035
- // Agent event hooks (fire-and-forget)
4036
- // ---------------------------------------------------------------------------
4037
- async emitAgentStart(sessionId) {
4038
- await this.hooks.emit("agent_start", { type: "agent_start", sessionId });
4039
- }
4040
- async emitAgentEnd(sessionId, outcome, messages) {
4041
- await this.hooks.emit("agent_end", { type: "agent_end", sessionId, outcome, messages });
4042
- }
4043
- async emitTurnStart(messages) {
4044
- await this.hooks.emit("turn_start", { type: "turn_start", messages });
4045
- }
4046
- async emitTurnEnd(messages, outcome) {
4047
- await this.hooks.emit("turn_end", { type: "turn_end", messages, outcome });
4048
- }
4049
- async emitMessageStart(message) {
4050
- await this.hooks.emit("message_start", { type: "message_start", message });
4051
- }
4052
- async emitMessageUpdate(message, delta) {
4053
- await this.hooks.emit("message_update", { type: "message_update", message, delta });
4054
- }
4055
- async emitMessageEnd(message) {
4056
- await this.hooks.emit("message_end", { type: "message_end", message });
4057
- }
4058
- async emitToolExecutionStart(toolCallId, toolName, args) {
4059
- await this.hooks.emit("tool_execution_start", {
4060
- type: "tool_execution_start",
4061
- toolCallId,
4062
- toolName,
4063
- args
4064
- });
4065
- }
4066
- async emitToolExecutionUpdate(toolCallId, toolName, _progress) {
4067
- await this.hooks.emit("tool_execution_update", {
4068
- type: "tool_execution_update",
4069
- toolCallId,
4070
- toolName
4071
- });
4072
- }
4073
- async emitToolExecutionEnd(toolCallId, toolName, result) {
4074
- await this.hooks.emit("tool_execution_end", {
4075
- type: "tool_execution_end",
4076
- toolCallId,
4077
- toolName,
4078
- result
4079
- });
4080
- }
4081
- async emitSavePoint(hadPendingMutations) {
4082
- await this.hooks.emit("save_point", { type: "save_point", hadPendingMutations });
4083
- }
4084
- async emitAbort(reason) {
4085
- await this.hooks.emit("abort", { type: "abort", reason });
4086
- }
4087
- async emitSettled() {
4088
- await this.hooks.emit("settled", { type: "settled" });
4089
- }
4090
- // ---------------------------------------------------------------------------
4091
- // AgentEvent bridge (backward compatibility)
4092
- // ---------------------------------------------------------------------------
4093
- /**
4094
- * Emit an AgentEvent by routing to the appropriate typed hook.
4095
- */
4096
- async emitAgentEvent(event) {
4097
- switch (event.type) {
4098
- case "agent_start":
4099
- await this.emitAgentStart(event.sessionId);
4100
- break;
4101
- case "agent_end":
4102
- await this.emitAgentEnd(event.sessionId, event.outcome, event.messages);
4103
- break;
4104
- case "turn_start":
4105
- await this.emitTurnStart(event.messages);
4106
- break;
4107
- case "turn_end":
4108
- await this.emitTurnEnd(event.messages, event.outcome);
4109
- break;
4110
- case "message_start":
4111
- await this.emitMessageStart(event.message);
4112
- break;
4113
- case "message_update":
4114
- await this.emitMessageUpdate(event.message, event.delta);
4115
- break;
4116
- case "message_end":
4117
- await this.emitMessageEnd(event.message);
4118
- break;
4119
- case "tool_execution_start":
4120
- await this.emitToolExecutionStart(event.toolCallId, event.toolName, event.args);
4121
- break;
4122
- case "tool_execution_update":
4123
- await this.emitToolExecutionUpdate(event.toolCallId, event.toolName);
4124
- break;
4125
- case "tool_execution_end":
4126
- await this.emitToolExecutionEnd(event.toolCallId, event.toolName, event.result);
4127
- break;
4128
- }
4129
- }
4130
- // ---------------------------------------------------------------------------
4131
- // Internal helpers
4132
- // ---------------------------------------------------------------------------
4133
- /**
4134
- * Create an ExtensionAPI for a specific extension.
4135
- * Registration methods write to the extension tracking object.
4136
- * Action methods delegate to the shared runtime.
4137
- */
4138
- createExtensionAPI(extension, manifest) {
4139
- const runner = this;
4140
- const extContext = {
4141
- get cwd() {
4142
- return runner.cwd;
4143
- },
4144
- get signal() {
4145
- return runner.abortController.signal;
4146
- },
4147
- isIdle() {
4148
- return runner._idle;
4149
- },
4150
- abort() {
4151
- runner.abortController.abort();
4152
- },
4153
- exec(command, args, options) {
4154
- return execCommand(command, args, runner.cwd, options);
4155
- },
4156
- manifest,
4157
- getSystemPrompt() {
4158
- return runner.currentContext?.systemPrompt ?? "";
4159
- },
4160
- setSystemPrompt(prompt) {
4161
- if (runner.currentContext) runner.currentContext.systemPrompt = prompt;
4162
- },
4163
- getMessages() {
4164
- return runner.currentContext?.messages ?? [];
4165
- },
4166
- addMessage(role, content) {
4167
- runner.currentContext?.messages.push({ role, content });
4168
- },
4169
- getAvailableTools() {
4170
- return (runner.currentContext?.tools ?? []).map((t) => ({ name: t.name, description: t.description }));
4171
- },
4172
- getAvailableSkills() {
4173
- return (runner.currentContext?.skills ?? []).map((s) => ({ name: s.name, description: s.description }));
4174
- },
4175
- getSkillContent(skillName) {
4176
- const skill = runner.currentContext?.skills.find((s) => s.name === skillName);
4177
- return skill?.content ?? "";
4178
- },
4179
- getAvailablePhases() {
4180
- return [...runner.phases.keys()];
4181
- },
4182
- getPhaseContent(phaseId) {
4183
- return runner.phases.get(phaseId)?.definition.description ?? "";
4184
- }
4185
- };
4186
- return createExtensionAPI(this.hooks, extension.path, {
4187
- registerPhase: (registration) => this.registerPhase(extension, registration),
4188
- registerProvider: (config) => this.registerProvider(config),
4189
- unregisterProvider: (name) => this.unregisterProvider(name),
4190
- registerTool: (tool) => this.registerTool(extension, tool),
4191
- context: extContext,
4192
- manifest
4193
- }, this.runtime, this.events);
4194
- }
4195
- registerTool(extension, tool) {
4196
- for (const ext of this.extensions) {
4197
- if (ext.tools.has(tool.name)) {
4198
- this.emitError({
4199
- extensionPath: extension.path,
4200
- event: "register_tool",
4201
- error: `Tool "${tool.name}" is already registered by extension ${ext.path}`
4202
- });
4203
- return;
4204
- }
4205
- }
4206
- const sourceInfo = createSourceInfo(extension.path);
4207
- extension.tools.set(tool.name, {
4208
- definition: tool,
4209
- sourceInfo
4210
- });
4211
- }
4212
- registerPhase(extension, registration) {
4213
- if (!registration.id) {
4214
- throw new Error(`Phase registration requires an "id" field.`);
4215
- }
4216
- if (!registration.name) {
4217
- throw new Error(`Phase registration "${registration.id}" requires a "name" field.`);
4218
- }
4219
- if (!registration.description) {
4220
- throw new Error(`Phase registration "${registration.id}" requires a "description" field.`);
4221
- }
4222
- if (this.phases.has(registration.id)) {
4223
- throw new Error(`Duplicate phase id: ${registration.id}`);
4224
- }
4225
- const definition = {
4226
- id: registration.id,
4227
- name: registration.name,
4228
- description: registration.description,
4229
- run: registration.run,
4230
- ...registration.model ? { model: registration.model } : {}
4231
- };
4232
- const registered = {
4233
- definition,
4234
- source: { extensionPath: extension.path }
4235
- };
4236
- this.phases.set(registration.id, registered);
4237
- extension.phases.set(registration.id, registered);
4238
- this._phaseCache = null;
4239
- }
4240
- registerProvider(config) {
4241
- if (this.bound) {
4242
- applyProviderRegistration(config);
4243
- } else {
4244
- this.pendingProviders.push({ kind: "register", config });
4245
- }
4246
- }
4247
- unregisterProvider(name) {
4248
- if (this.bound) {
4249
- applyProviderUnregistration(name);
4250
- } else {
4251
- this.pendingProviders.push({ kind: "unregister", name });
4252
- }
4253
- }
4254
- flushPendingProviders() {
4255
- for (const action of this.pendingProviders) {
4256
- if (action.kind === "register") {
4257
- applyProviderRegistration(action.config);
4258
- } else {
4259
- applyProviderUnregistration(action.name);
4260
- }
4261
- }
4262
- this.pendingProviders.length = 0;
4263
- }
4264
- getRegisteredPhase(id) {
4265
- return this.collectRegisteredPhases().get(id);
4266
- }
4267
- collectRegisteredPhases() {
4268
- if (this._phaseCache) return this._phaseCache;
4269
- this._phaseCache = new Map(this.phases);
4270
- return this._phaseCache;
4271
- }
4272
- };
4273
- function createExtensionRunner(options) {
4274
- return new ExtensionRunner(options);
4275
- }
4276
-
4277
- // src/extensions/loader.ts
4278
- var import_node_fs6 = require("fs");
4279
- var import_promises7 = require("fs/promises");
4280
- var import_node_url = require("url");
4281
- var import_node_path8 = require("path");
4282
- var import_jiti = require("jiti");
4283
- var import_meta2 = {};
4284
- var ROWAN_DIR = ".rowan";
4285
- var EXTENSIONS_DIR = "extensions";
4286
- function isExtensionFile(path) {
4287
- const extension = (0, import_node_path8.extname)(path).toLowerCase();
4288
- return extension === ".ts" || extension === ".js";
4289
- }
4290
- function isSyntheticPath(path) {
4291
- return path.startsWith("<") && path.endsWith(">");
4292
- }
4293
- function readManifestSync(dir) {
4294
- const manifestPath = (0, import_node_path8.join)(dir, "package.json");
4295
- if (!(0, import_node_fs6.existsSync)(manifestPath)) {
4296
- return void 0;
4297
- }
4298
- try {
4299
- const content = (0, import_node_fs6.readFileSync)(manifestPath, "utf8");
4300
- const pkg = JSON.parse(content);
4301
- const rowan = pkg.rowan;
4302
- if (!rowan) return void 0;
4303
- return {
4304
- entry: rowan.extensions?.[0],
4305
- phase: rowan.phase
4306
- };
4307
- } catch {
4308
- return void 0;
4309
- }
4310
- }
4311
- function jitiAliases() {
4312
- const moduleUrl = import_meta2.url;
4313
- if (!moduleUrl) {
4314
- return {};
4315
- }
4316
- const agentSourcePath = (0, import_node_url.fileURLToPath)(new URL("../index.ts", moduleUrl));
4317
- if (!(0, import_node_fs6.existsSync)(agentSourcePath)) {
4318
- return {};
4319
- }
4320
- return { "@rowan-agent/agent": agentSourcePath };
4321
- }
4322
- var sharedJiti;
4323
- function getJiti() {
4324
- sharedJiti ??= (0, import_jiti.createJiti)(import_meta2.url || process.cwd(), {
4325
- moduleCache: false,
4326
- alias: jitiAliases()
4327
- });
4328
- return sharedJiti;
4329
- }
4330
- async function loadExtensionModule(extensionPath) {
4331
- const jiti = getJiti();
4332
- const module2 = await jiti.import(extensionPath, { default: true });
4333
- return typeof module2 === "function" ? module2 : void 0;
4334
- }
4335
- async function readPackageManifest(path) {
4336
- if (!(0, import_node_fs6.existsSync)(path)) {
4337
- return void 0;
4338
- }
4339
- const content = await (0, import_promises7.readFile)(path, "utf8");
4340
- return JSON.parse(content);
4341
- }
4342
- async function resolveExtensionEntries(dir) {
4343
- const manifest = await readPackageManifest((0, import_node_path8.join)(dir, "package.json"));
4344
- const declared = manifest?.rowan?.extensions;
4345
- if (declared && declared.length > 0) {
4346
- return declared.map((entry) => (0, import_node_path8.resolve)(dir, entry));
4347
- }
4348
- for (const name of ["index.ts", "index.js"]) {
4349
- const entry = (0, import_node_path8.join)(dir, name);
4350
- if ((0, import_node_fs6.existsSync)(entry)) {
4351
- return [entry];
4352
- }
4353
- }
4354
- return void 0;
4355
- }
4356
- async function discoverExtensionsInDir(dir) {
4357
- const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true }).catch((error) => {
4358
- if (error.code === "ENOENT") {
4359
- return [];
4360
- }
4361
- throw error;
4362
- });
4363
- const paths = [];
4364
- for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4365
- const entryPath = (0, import_node_path8.join)(dir, entry.name);
4366
- if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
4367
- paths.push(entryPath);
4368
- continue;
4369
- }
4370
- if (entry.isDirectory() || entry.isSymbolicLink()) {
4371
- const info = entry.isSymbolicLink() ? await (0, import_promises7.stat)(entryPath).catch(() => void 0) : void 0;
4372
- if (entry.isDirectory() || info?.isDirectory()) {
4373
- const resolvedEntries = await resolveExtensionEntries(entryPath);
4374
- if (resolvedEntries) {
4375
- paths.push(...resolvedEntries);
4376
- }
4377
- }
4378
- }
4379
- }
4380
- return paths;
4381
- }
4382
- function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
4383
- const resolvedCwd = (0, import_node_path8.resolve)(cwd);
4384
- const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : (0, import_node_path8.resolve)(resolvedCwd, extensionPath);
4385
- const name = isSyntheticPath(extensionPath) ? extensionPath : (0, import_node_path8.dirname)(resolvedPath).split("/").pop() ?? "unknown";
4386
- const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : (0, import_node_path8.dirname)(resolvedPath);
4387
- const manifest = readManifestSync(manifestDir);
4388
- return {
4389
- path: extensionPath,
4390
- resolvedPath,
4391
- name,
4392
- factory,
4393
- manifest
4394
- };
4395
- }
4396
- async function loadExtensions(paths, cwd) {
4397
- const extensions = [];
4398
- const errors = [];
4399
- const resolvedCwd = (0, import_node_path8.resolve)(cwd);
4400
- for (const path of paths) {
4401
- const resolvedPath = (0, import_node_path8.resolve)(resolvedCwd, path);
4402
- try {
4403
- const factory = await loadExtensionModule(resolvedPath);
4404
- if (!factory) {
4405
- errors.push({
4406
- path: resolvedPath,
4407
- error: `Extension does not export a valid factory function: ${path}`
4408
- });
4409
- continue;
4410
- }
4411
- extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
4412
- } catch (error) {
4413
- errors.push({
4414
- path: resolvedPath,
4415
- error: error instanceof Error ? error.message : String(error)
4416
- });
4417
- }
4418
- }
4419
- return { extensions, errors };
4420
- }
4421
- async function discoverAndLoadExtensions(cwd) {
4422
- const extensionsDir = (0, import_node_path8.join)((0, import_node_path8.resolve)(cwd), ROWAN_DIR, EXTENSIONS_DIR);
4423
- const paths = await discoverExtensionsInDir(extensionsDir);
4424
- return loadExtensions(paths, cwd);
4425
- }
4426
-
4427
- // src/index.ts
4428
- init_loader2();
4429
-
4430
- // src/harness/context/index.ts
4431
- init_resource_formatter();
4432
- // Annotate the CommonJS export names for ESM import in node:
4433
- 0 && (module.exports = {
4434
- Agent,
4435
- AgentEventStream,
4436
- EventStream,
4437
- ExtensionRunner,
4438
- HooksManager,
4439
- LocalJsonlSessionManager,
4440
- appendUserTurn,
4441
- buildModelRequest,
4442
- buildSystemPrompt,
4443
- conversationMessages,
4444
- createCoreTools,
4445
- createEventBus,
4446
- createExtension,
4447
- createExtensionAPI,
4448
- createExtensionRunner,
4449
- createExtensionRuntime,
4450
- createId,
4451
- createMessage,
4452
- createSession,
4453
- createSourceInfo,
4454
- createTimestamp,
4455
- discoverAndLoadExtensions,
4456
- getGlobalHooks,
4457
- interpolateEnvVars,
4458
- latestUserInput,
4459
- loadConfigFile,
4460
- loadExtensionFromFactory,
4461
- loadExtensions,
4462
- loadPhases,
4463
- loadSkill,
4464
- loadSkills,
4465
- messageContentText,
4466
- parseModelRef,
4467
- registerConfigModels,
4468
- resetGlobalHooks,
4469
- resolveDefaultModel,
4470
- resolveInWorkspace,
4471
- resolveSkillPath,
4472
- resolveWorkspacePaths,
4473
- serializeSkills
4474
- });