@agntk/agent-harness 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +41 -0
  3. package/README.md +445 -0
  4. package/defaults/agents/summarizer.md +49 -0
  5. package/defaults/instincts/lead-with-answer.md +24 -0
  6. package/defaults/instincts/qualify-before-recommending.md +40 -0
  7. package/defaults/instincts/read-before-edit.md +23 -0
  8. package/defaults/instincts/search-before-create.md +23 -0
  9. package/defaults/playbooks/ship-feature.md +31 -0
  10. package/defaults/rules/ask-before-assuming.md +35 -0
  11. package/defaults/rules/operations.md +35 -0
  12. package/defaults/rules/respect-the-user.md +39 -0
  13. package/defaults/skills/business-analyst.md +181 -0
  14. package/defaults/skills/content-marketer.md +184 -0
  15. package/defaults/skills/research.md +34 -0
  16. package/defaults/tools/example-web-search.md +60 -0
  17. package/defaults/workflows/daily-reflection.md +54 -0
  18. package/dist/agent-framework-K4GUIICH.js +344 -0
  19. package/dist/agent-framework-K4GUIICH.js.map +1 -0
  20. package/dist/analytics-RPT73WNM.js +12 -0
  21. package/dist/analytics-RPT73WNM.js.map +1 -0
  22. package/dist/auto-processor-OLE45UI3.js +13 -0
  23. package/dist/auto-processor-OLE45UI3.js.map +1 -0
  24. package/dist/chunk-274RV3YO.js +162 -0
  25. package/dist/chunk-274RV3YO.js.map +1 -0
  26. package/dist/chunk-4CWAGBNS.js +168 -0
  27. package/dist/chunk-4CWAGBNS.js.map +1 -0
  28. package/dist/chunk-4FDUOGSZ.js +69 -0
  29. package/dist/chunk-4FDUOGSZ.js.map +1 -0
  30. package/dist/chunk-5H34JPMB.js +199 -0
  31. package/dist/chunk-5H34JPMB.js.map +1 -0
  32. package/dist/chunk-6EMOEYGU.js +102 -0
  33. package/dist/chunk-6EMOEYGU.js.map +1 -0
  34. package/dist/chunk-A7BJPQQ6.js +236 -0
  35. package/dist/chunk-A7BJPQQ6.js.map +1 -0
  36. package/dist/chunk-AGAAFJEO.js +76 -0
  37. package/dist/chunk-AGAAFJEO.js.map +1 -0
  38. package/dist/chunk-BSKDOFRT.js +65 -0
  39. package/dist/chunk-BSKDOFRT.js.map +1 -0
  40. package/dist/chunk-CHJ5GNZC.js +100 -0
  41. package/dist/chunk-CHJ5GNZC.js.map +1 -0
  42. package/dist/chunk-CSL3ERUI.js +307 -0
  43. package/dist/chunk-CSL3ERUI.js.map +1 -0
  44. package/dist/chunk-DA7IKHC4.js +229 -0
  45. package/dist/chunk-DA7IKHC4.js.map +1 -0
  46. package/dist/chunk-DGUM43GV.js +11 -0
  47. package/dist/chunk-DGUM43GV.js.map +1 -0
  48. package/dist/chunk-DTTXPHFW.js +211 -0
  49. package/dist/chunk-DTTXPHFW.js.map +1 -0
  50. package/dist/chunk-FD55B3IO.js +204 -0
  51. package/dist/chunk-FD55B3IO.js.map +1 -0
  52. package/dist/chunk-FLZU44SV.js +230 -0
  53. package/dist/chunk-FLZU44SV.js.map +1 -0
  54. package/dist/chunk-GJNNR2RA.js +200 -0
  55. package/dist/chunk-GJNNR2RA.js.map +1 -0
  56. package/dist/chunk-GNUSHD2Y.js +111 -0
  57. package/dist/chunk-GNUSHD2Y.js.map +1 -0
  58. package/dist/chunk-GUJTBGVS.js +2212 -0
  59. package/dist/chunk-GUJTBGVS.js.map +1 -0
  60. package/dist/chunk-IZ6UZ3ZL.js +207 -0
  61. package/dist/chunk-IZ6UZ3ZL.js.map +1 -0
  62. package/dist/chunk-JKMGYWXB.js +197 -0
  63. package/dist/chunk-JKMGYWXB.js.map +1 -0
  64. package/dist/chunk-KFX54TQM.js +165 -0
  65. package/dist/chunk-KFX54TQM.js.map +1 -0
  66. package/dist/chunk-M7NXUK55.js +199 -0
  67. package/dist/chunk-M7NXUK55.js.map +1 -0
  68. package/dist/chunk-MPZ3BPUI.js +374 -0
  69. package/dist/chunk-MPZ3BPUI.js.map +1 -0
  70. package/dist/chunk-OC6YSTDX.js +119 -0
  71. package/dist/chunk-OC6YSTDX.js.map +1 -0
  72. package/dist/chunk-RC6MEZB6.js +469 -0
  73. package/dist/chunk-RC6MEZB6.js.map +1 -0
  74. package/dist/chunk-RY3ZFII7.js +3440 -0
  75. package/dist/chunk-RY3ZFII7.js.map +1 -0
  76. package/dist/chunk-TAT6JU3X.js +167 -0
  77. package/dist/chunk-TAT6JU3X.js.map +1 -0
  78. package/dist/chunk-UDZIS2AQ.js +79 -0
  79. package/dist/chunk-UDZIS2AQ.js.map +1 -0
  80. package/dist/chunk-UPLBF4RZ.js +115 -0
  81. package/dist/chunk-UPLBF4RZ.js.map +1 -0
  82. package/dist/chunk-UWQTZMNI.js +154 -0
  83. package/dist/chunk-UWQTZMNI.js.map +1 -0
  84. package/dist/chunk-W4T7PGI2.js +346 -0
  85. package/dist/chunk-W4T7PGI2.js.map +1 -0
  86. package/dist/chunk-XTBKL5BI.js +111 -0
  87. package/dist/chunk-XTBKL5BI.js.map +1 -0
  88. package/dist/chunk-YIJY5DBV.js +399 -0
  89. package/dist/chunk-YIJY5DBV.js.map +1 -0
  90. package/dist/chunk-YUFNYN2H.js +242 -0
  91. package/dist/chunk-YUFNYN2H.js.map +1 -0
  92. package/dist/chunk-Z2PUCXTZ.js +94 -0
  93. package/dist/chunk-Z2PUCXTZ.js.map +1 -0
  94. package/dist/chunk-ZZJOFKAT.js +13 -0
  95. package/dist/chunk-ZZJOFKAT.js.map +1 -0
  96. package/dist/cli/index.js +3661 -0
  97. package/dist/cli/index.js.map +1 -0
  98. package/dist/config-WVMRUOCA.js +13 -0
  99. package/dist/config-WVMRUOCA.js.map +1 -0
  100. package/dist/context-loader-3ORBPMHJ.js +13 -0
  101. package/dist/context-loader-3ORBPMHJ.js.map +1 -0
  102. package/dist/conversation-QDEIDQPH.js +22 -0
  103. package/dist/conversation-QDEIDQPH.js.map +1 -0
  104. package/dist/cost-tracker-RS3W7SVY.js +24 -0
  105. package/dist/cost-tracker-RS3W7SVY.js.map +1 -0
  106. package/dist/delegate-VJCJLYEK.js +29 -0
  107. package/dist/delegate-VJCJLYEK.js.map +1 -0
  108. package/dist/emotional-state-VQVRA6ED.js +206 -0
  109. package/dist/emotional-state-VQVRA6ED.js.map +1 -0
  110. package/dist/env-discovery-2BLVMAIM.js +251 -0
  111. package/dist/env-discovery-2BLVMAIM.js.map +1 -0
  112. package/dist/export-6GCYHEHQ.js +165 -0
  113. package/dist/export-6GCYHEHQ.js.map +1 -0
  114. package/dist/graph-YUIPOSOO.js +14 -0
  115. package/dist/graph-YUIPOSOO.js.map +1 -0
  116. package/dist/harness-LCHA3DWP.js +10 -0
  117. package/dist/harness-LCHA3DWP.js.map +1 -0
  118. package/dist/harness-WE4SLCML.js +26 -0
  119. package/dist/harness-WE4SLCML.js.map +1 -0
  120. package/dist/health-NZ6WNIMV.js +23 -0
  121. package/dist/health-NZ6WNIMV.js.map +1 -0
  122. package/dist/index.d.ts +3612 -0
  123. package/dist/index.js +13501 -0
  124. package/dist/index.js.map +1 -0
  125. package/dist/indexer-LONANRRM.js +16 -0
  126. package/dist/indexer-LONANRRM.js.map +1 -0
  127. package/dist/instinct-learner-SRM72DHF.js +20 -0
  128. package/dist/instinct-learner-SRM72DHF.js.map +1 -0
  129. package/dist/intake-4M3HNU43.js +21 -0
  130. package/dist/intake-4M3HNU43.js.map +1 -0
  131. package/dist/intelligence-HJOCA4SJ.js +1081 -0
  132. package/dist/intelligence-HJOCA4SJ.js.map +1 -0
  133. package/dist/journal-WANJL3MI.js +24 -0
  134. package/dist/journal-WANJL3MI.js.map +1 -0
  135. package/dist/loader-C3TKIKZR.js +23 -0
  136. package/dist/loader-C3TKIKZR.js.map +1 -0
  137. package/dist/mcp-WTQJJZAO.js +15 -0
  138. package/dist/mcp-WTQJJZAO.js.map +1 -0
  139. package/dist/mcp-discovery-WPAQFL6S.js +377 -0
  140. package/dist/mcp-discovery-WPAQFL6S.js.map +1 -0
  141. package/dist/mcp-installer-6O2XXD3V.js +394 -0
  142. package/dist/mcp-installer-6O2XXD3V.js.map +1 -0
  143. package/dist/metrics-KXGNFAAB.js +20 -0
  144. package/dist/metrics-KXGNFAAB.js.map +1 -0
  145. package/dist/primitive-registry-I6VTIR4W.js +512 -0
  146. package/dist/primitive-registry-I6VTIR4W.js.map +1 -0
  147. package/dist/project-discovery-C4UMD7JI.js +246 -0
  148. package/dist/project-discovery-C4UMD7JI.js.map +1 -0
  149. package/dist/provider-LQHQX7Z7.js +26 -0
  150. package/dist/provider-LQHQX7Z7.js.map +1 -0
  151. package/dist/provider-SXPQZ74H.js +28 -0
  152. package/dist/provider-SXPQZ74H.js.map +1 -0
  153. package/dist/rate-limiter-RLRVM325.js +22 -0
  154. package/dist/rate-limiter-RLRVM325.js.map +1 -0
  155. package/dist/rule-engine-YGQ3RYZM.js +182 -0
  156. package/dist/rule-engine-YGQ3RYZM.js.map +1 -0
  157. package/dist/scaffold-A3VRRCBV.js +347 -0
  158. package/dist/scaffold-A3VRRCBV.js.map +1 -0
  159. package/dist/scheduler-XHHIVHRI.js +397 -0
  160. package/dist/scheduler-XHHIVHRI.js.map +1 -0
  161. package/dist/search-V3W5JMJG.js +75 -0
  162. package/dist/search-V3W5JMJG.js.map +1 -0
  163. package/dist/semantic-search-2DTOO5UX.js +241 -0
  164. package/dist/semantic-search-2DTOO5UX.js.map +1 -0
  165. package/dist/serve-DTQ3HENY.js +291 -0
  166. package/dist/serve-DTQ3HENY.js.map +1 -0
  167. package/dist/sessions-CZGVXKQE.js +21 -0
  168. package/dist/sessions-CZGVXKQE.js.map +1 -0
  169. package/dist/sources-RW5DT56F.js +32 -0
  170. package/dist/sources-RW5DT56F.js.map +1 -0
  171. package/dist/starter-packs-76YUVHEU.js +893 -0
  172. package/dist/starter-packs-76YUVHEU.js.map +1 -0
  173. package/dist/state-GMXILIHW.js +13 -0
  174. package/dist/state-GMXILIHW.js.map +1 -0
  175. package/dist/state-merge-NKO5FRBA.js +174 -0
  176. package/dist/state-merge-NKO5FRBA.js.map +1 -0
  177. package/dist/telemetry-UC6PBXC7.js +22 -0
  178. package/dist/telemetry-UC6PBXC7.js.map +1 -0
  179. package/dist/tool-executor-MJ7IG7PQ.js +28 -0
  180. package/dist/tool-executor-MJ7IG7PQ.js.map +1 -0
  181. package/dist/tools-DZ4KETET.js +20 -0
  182. package/dist/tools-DZ4KETET.js.map +1 -0
  183. package/dist/types-EW7AIB3R.js +18 -0
  184. package/dist/types-EW7AIB3R.js.map +1 -0
  185. package/dist/types-WGDLSPO6.js +16 -0
  186. package/dist/types-WGDLSPO6.js.map +1 -0
  187. package/dist/universal-installer-QGS4SJGX.js +578 -0
  188. package/dist/universal-installer-QGS4SJGX.js.map +1 -0
  189. package/dist/validator-7WXMDIHH.js +22 -0
  190. package/dist/validator-7WXMDIHH.js.map +1 -0
  191. package/dist/verification-gate-FYXUX6LH.js +246 -0
  192. package/dist/verification-gate-FYXUX6LH.js.map +1 -0
  193. package/dist/versioning-Z3XNE2Q2.js +271 -0
  194. package/dist/versioning-Z3XNE2Q2.js.map +1 -0
  195. package/dist/watcher-ISJC7YKL.js +109 -0
  196. package/dist/watcher-ISJC7YKL.js.map +1 -0
  197. package/dist/web-server-DD7ZOP46.js +28 -0
  198. package/dist/web-server-DD7ZOP46.js.map +1 -0
  199. package/package.json +76 -0
  200. package/sources.yaml +121 -0
  201. package/templates/assistant/CORE.md +24 -0
  202. package/templates/assistant/SYSTEM.md +24 -0
  203. package/templates/assistant/config.yaml +51 -0
  204. package/templates/base/CORE.md +17 -0
  205. package/templates/base/SYSTEM.md +24 -0
  206. package/templates/base/config.yaml +51 -0
  207. package/templates/claude-opus/config.yaml +51 -0
  208. package/templates/code-reviewer/CORE.md +25 -0
  209. package/templates/code-reviewer/SYSTEM.md +30 -0
  210. package/templates/code-reviewer/config.yaml +51 -0
  211. package/templates/gpt4/config.yaml +51 -0
  212. package/templates/local/config.yaml +51 -0
@@ -0,0 +1,2212 @@
1
+ import {
2
+ CONFIG_DEFAULTS,
3
+ CORE_PRIMITIVE_DIRS,
4
+ FrontmatterSchema,
5
+ HarnessConfigSchema
6
+ } from "./chunk-KFX54TQM.js";
7
+ import {
8
+ generate,
9
+ getModel,
10
+ streamGenerateWithDetails
11
+ } from "./chunk-FD55B3IO.js";
12
+
13
+ // src/core/harness.ts
14
+ import { existsSync as existsSync11 } from "fs";
15
+ import { resolve } from "path";
16
+
17
+ // src/core/config.ts
18
+ import { readFileSync, existsSync } from "fs";
19
+ import { join } from "path";
20
+ import YAML from "yaml";
21
+ var CONFIG_FILENAMES = ["config.yaml", "config.yml", "harness.yaml", "harness.yml"];
22
+ function loadConfig(dir, overrides) {
23
+ let raw = {};
24
+ for (const filename of CONFIG_FILENAMES) {
25
+ const configPath = join(dir, filename);
26
+ if (existsSync(configPath)) {
27
+ const content = readFileSync(configPath, "utf-8");
28
+ raw = YAML.parse(content) || {};
29
+ break;
30
+ }
31
+ }
32
+ let merged = deepMerge(
33
+ CONFIG_DEFAULTS,
34
+ raw
35
+ );
36
+ if (overrides) {
37
+ merged = deepMerge(
38
+ merged,
39
+ overrides
40
+ );
41
+ }
42
+ const result = HarnessConfigSchema.safeParse(merged);
43
+ if (!result.success) {
44
+ const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
45
+ throw new Error(`Invalid config:
46
+ ${issues}`);
47
+ }
48
+ return result.data;
49
+ }
50
+ function writeDefaultConfig(_dir, agentName = "my-agent") {
51
+ return `# Agent Harness Configuration
52
+ agent:
53
+ name: ${agentName}
54
+ version: "0.1.0"
55
+
56
+ model:
57
+ provider: openrouter
58
+ id: anthropic/claude-sonnet-4
59
+ max_tokens: 200000
60
+ max_retries: 2
61
+ # timeout_ms: 60000
62
+
63
+ runtime:
64
+ scratchpad_budget: 10000
65
+ timezone: America/New_York
66
+ # heartbeat: "0 6-23 * * *" # reserved, not yet implemented
67
+ # daily_summary: "0 22 * * *" # reserved, not yet implemented
68
+
69
+ memory:
70
+ session_retention_days: 7
71
+ journal_retention_days: 365
72
+
73
+ channels:
74
+ primary: cli
75
+
76
+ extensions:
77
+ directories: []
78
+
79
+ # rate_limits:
80
+ # per_minute: 10
81
+ # per_hour: 100
82
+ # per_day: 500
83
+
84
+ # budget:
85
+ # daily_limit_usd: 5.00
86
+ # monthly_limit_usd: 100.00
87
+ # enforce: true
88
+ `;
89
+ }
90
+ function deepMerge(target, source) {
91
+ const result = { ...target };
92
+ for (const key of Object.keys(source)) {
93
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) {
94
+ result[key] = deepMerge(
95
+ target[key],
96
+ source[key]
97
+ );
98
+ } else {
99
+ result[key] = source[key];
100
+ }
101
+ }
102
+ return result;
103
+ }
104
+
105
+ // src/core/logger.ts
106
+ var LEVEL_ORDER = {
107
+ debug: 0,
108
+ info: 1,
109
+ warn: 2,
110
+ error: 3,
111
+ silent: 4
112
+ };
113
+ var globalLevel = "info";
114
+ function shouldLog(level) {
115
+ return LEVEL_ORDER[level] >= LEVEL_ORDER[globalLevel];
116
+ }
117
+ function formatMessage(prefix, level, msg) {
118
+ const tag = prefix ? `[${prefix}]` : "";
119
+ if (level === "debug") return `${tag} ${msg}`.trimStart();
120
+ if (level === "warn") return `${tag} WARN: ${msg}`.trimStart();
121
+ if (level === "error") return `${tag} ERROR: ${msg}`.trimStart();
122
+ return `${tag} ${msg}`.trimStart();
123
+ }
124
+ function createLoggerWithPrefix(prefix) {
125
+ return {
126
+ debug(msg, ...args) {
127
+ if (shouldLog("debug")) console.error(formatMessage(prefix, "debug", msg), ...args);
128
+ },
129
+ info(msg, ...args) {
130
+ if (shouldLog("info")) console.error(formatMessage(prefix, "info", msg), ...args);
131
+ },
132
+ warn(msg, ...args) {
133
+ if (shouldLog("warn")) console.error(formatMessage(prefix, "warn", msg), ...args);
134
+ },
135
+ error(msg, ...args) {
136
+ if (shouldLog("error")) console.error(formatMessage(prefix, "error", msg), ...args);
137
+ },
138
+ setLevel(level) {
139
+ globalLevel = level;
140
+ },
141
+ getLevel() {
142
+ return globalLevel;
143
+ },
144
+ child(childPrefix) {
145
+ const combined = prefix ? `${prefix}:${childPrefix}` : childPrefix;
146
+ return createLoggerWithPrefix(combined);
147
+ }
148
+ };
149
+ }
150
+ function createLogger(prefix = "") {
151
+ return createLoggerWithPrefix(prefix);
152
+ }
153
+ function setGlobalLogLevel(level) {
154
+ globalLevel = level;
155
+ }
156
+ function getGlobalLogLevel() {
157
+ return globalLevel;
158
+ }
159
+ var log = createLogger("harness");
160
+
161
+ // src/runtime/context-loader.ts
162
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
163
+ import { join as join3 } from "path";
164
+
165
+ // src/primitives/loader.ts
166
+ import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync2 } from "fs";
167
+ import { join as join2, extname } from "path";
168
+ import matter from "gray-matter";
169
+ var L0_REGEX = /<!--\s*L0:\s*([\s\S]*?)\s*-->/;
170
+ var L1_REGEX = /<!--\s*L1:\s*([\s\S]*?)\s*-->/;
171
+ function parseHarnessDocument(filePath) {
172
+ const raw = readFileSync2(filePath, "utf-8");
173
+ const { data, content } = matter(raw);
174
+ const normalized = { ...data };
175
+ for (const key of ["created", "updated"]) {
176
+ if (normalized[key] instanceof Date) {
177
+ normalized[key] = normalized[key].toISOString().split("T")[0];
178
+ }
179
+ }
180
+ let frontmatter;
181
+ try {
182
+ frontmatter = FrontmatterSchema.parse(normalized);
183
+ } catch {
184
+ const id = filePath.split("/").pop()?.replace(".md", "") || "unknown";
185
+ frontmatter = FrontmatterSchema.parse({ id });
186
+ }
187
+ const l0Match = content.match(L0_REGEX);
188
+ const l1Match = content.match(L1_REGEX);
189
+ const l0 = l0Match ? l0Match[1].trim() : "";
190
+ const l1 = l1Match ? l1Match[1].trim() : "";
191
+ const body = content.replace(L0_REGEX, "").replace(L1_REGEX, "").trim();
192
+ return {
193
+ path: filePath,
194
+ frontmatter,
195
+ l0,
196
+ l1,
197
+ body,
198
+ raw
199
+ };
200
+ }
201
+ function loadDirectory(dirPath) {
202
+ return loadDirectoryWithErrors(dirPath).docs;
203
+ }
204
+ function loadDirectoryWithErrors(dirPath) {
205
+ if (!existsSync2(dirPath)) return { docs: [], errors: [] };
206
+ const files = readdirSync(dirPath);
207
+ const docs = [];
208
+ const errors = [];
209
+ for (const file of files) {
210
+ if (extname(file) !== ".md") continue;
211
+ if (file.startsWith("_")) continue;
212
+ if (file.startsWith(".")) continue;
213
+ const filePath = join2(dirPath, file);
214
+ try {
215
+ const doc = parseHarnessDocument(filePath);
216
+ if (doc.frontmatter.status !== "archived" && doc.frontmatter.status !== "deprecated") {
217
+ docs.push(doc);
218
+ }
219
+ } catch (err) {
220
+ errors.push({
221
+ path: filePath,
222
+ error: err instanceof Error ? err.message : String(err)
223
+ });
224
+ }
225
+ }
226
+ return { docs, errors };
227
+ }
228
+ function loadAllPrimitives(harnessDir, extraDirs) {
229
+ return loadAllPrimitivesWithErrors(harnessDir, extraDirs).primitives;
230
+ }
231
+ function loadAllPrimitivesWithErrors(harnessDir, extraDirs) {
232
+ const primitives = /* @__PURE__ */ new Map();
233
+ const allErrors = [];
234
+ const directories = [...CORE_PRIMITIVE_DIRS];
235
+ if (extraDirs) {
236
+ for (const dir of extraDirs) {
237
+ if (!directories.includes(dir)) {
238
+ directories.push(dir);
239
+ }
240
+ }
241
+ }
242
+ for (const dir of directories) {
243
+ const { docs, errors } = loadDirectoryWithErrors(join2(harnessDir, dir));
244
+ primitives.set(dir, docs);
245
+ allErrors.push(...errors);
246
+ }
247
+ return { primitives, errors: allErrors };
248
+ }
249
+ function estimateTokens(text) {
250
+ return Math.ceil(text.length / 4);
251
+ }
252
+ function getAtLevel(doc, level) {
253
+ switch (level) {
254
+ case 0:
255
+ return doc.l0 || doc.frontmatter.id;
256
+ case 1:
257
+ return doc.l1 || doc.l0 || doc.body.slice(0, 400);
258
+ case 2:
259
+ return doc.body;
260
+ }
261
+ }
262
+
263
+ // src/runtime/context-loader.ts
264
+ function buildSystemPrompt(harnessDir, config) {
265
+ const maxTokens = config.model.max_tokens;
266
+ const budget = {
267
+ max_tokens: maxTokens,
268
+ used_tokens: 0,
269
+ remaining: maxTokens,
270
+ loaded_files: []
271
+ };
272
+ const warnings = [];
273
+ const sections = [];
274
+ const corePath = join3(harnessDir, "CORE.md");
275
+ if (existsSync3(corePath)) {
276
+ const core = readFileSync3(corePath, "utf-8");
277
+ sections.push(`# CORE IDENTITY
278
+
279
+ ${core}`);
280
+ budget.used_tokens += estimateTokens(core);
281
+ budget.loaded_files.push("CORE.md");
282
+ }
283
+ const statePath = join3(harnessDir, "state.md");
284
+ if (existsSync3(statePath)) {
285
+ const state = readFileSync3(statePath, "utf-8");
286
+ sections.push(`# CURRENT STATE
287
+
288
+ ${state}`);
289
+ budget.used_tokens += estimateTokens(state);
290
+ budget.loaded_files.push("state.md");
291
+ }
292
+ const systemPath = join3(harnessDir, "SYSTEM.md");
293
+ if (existsSync3(systemPath)) {
294
+ const system = readFileSync3(systemPath, "utf-8");
295
+ sections.push(`# SYSTEM
296
+
297
+ ${system}`);
298
+ budget.used_tokens += estimateTokens(system);
299
+ budget.loaded_files.push("SYSTEM.md");
300
+ }
301
+ const extDirs = config.extensions?.directories ?? [];
302
+ const { primitives, errors: parseErrors } = loadAllPrimitivesWithErrors(harnessDir, extDirs);
303
+ if (parseErrors.length > 0) {
304
+ for (const pe of parseErrors) {
305
+ log.warn(`Failed to parse primitive: ${pe.path} \u2014 ${pe.error}`);
306
+ }
307
+ warnings.push(`${parseErrors.length} primitive file(s) failed to parse`);
308
+ }
309
+ const targetBudget = maxTokens * 0.15;
310
+ const priorityOrder = ["rules", "instincts", "skills", "playbooks", "tools", "workflows", "agents"];
311
+ for (const dir of extDirs) {
312
+ if (!priorityOrder.includes(dir)) {
313
+ priorityOrder.push(dir);
314
+ }
315
+ }
316
+ const allDocs = [];
317
+ for (const category of priorityOrder) {
318
+ const docs = primitives.get(category);
319
+ if (!docs || docs.length === 0) continue;
320
+ for (const doc of docs) {
321
+ allDocs.push({ category, doc });
322
+ }
323
+ }
324
+ if (allDocs.length === 0) {
325
+ warnings.push("No primitives found \u2014 add rules, instincts, or skills to improve agent behavior");
326
+ }
327
+ const primitiveBudget = targetBudget - budget.used_tokens;
328
+ let totalL2Demand = 0;
329
+ for (const { doc } of allDocs) {
330
+ totalL2Demand += estimateTokens(getAtLevel(doc, 2));
331
+ }
332
+ let globalLevel2;
333
+ if (totalL2Demand <= primitiveBudget) {
334
+ globalLevel2 = 2;
335
+ } else {
336
+ let totalL1Demand = 0;
337
+ for (const { doc } of allDocs) {
338
+ totalL1Demand += estimateTokens(getAtLevel(doc, 1));
339
+ }
340
+ globalLevel2 = totalL1Demand <= primitiveBudget ? 1 : 0;
341
+ }
342
+ for (const category of priorityOrder) {
343
+ const docs = primitives.get(category);
344
+ if (!docs || docs.length === 0) continue;
345
+ const categoryLabel = category.toUpperCase();
346
+ const categoryDocs = [];
347
+ for (const doc of docs) {
348
+ let level = globalLevel2;
349
+ let content = getAtLevel(doc, level);
350
+ let tokens = estimateTokens(content);
351
+ while (budget.used_tokens + tokens > targetBudget && level > 0) {
352
+ level = level - 1;
353
+ content = getAtLevel(doc, level);
354
+ tokens = estimateTokens(content);
355
+ }
356
+ categoryDocs.push(`### ${doc.frontmatter.id}
357
+ ${content}`);
358
+ budget.used_tokens += tokens;
359
+ budget.loaded_files.push(doc.path);
360
+ }
361
+ if (categoryDocs.length > 0) {
362
+ sections.push(`# ${categoryLabel}
363
+
364
+ ${categoryDocs.join("\n\n")}`);
365
+ }
366
+ }
367
+ const scratchPath = join3(harnessDir, "memory", "scratch.md");
368
+ if (existsSync3(scratchPath)) {
369
+ const scratch = readFileSync3(scratchPath, "utf-8");
370
+ if (scratch.trim()) {
371
+ sections.push(`# SCRATCH (Current Working Memory)
372
+
373
+ ${scratch}`);
374
+ budget.used_tokens += estimateTokens(scratch);
375
+ budget.loaded_files.push("memory/scratch.md");
376
+ }
377
+ }
378
+ budget.remaining = maxTokens - budget.used_tokens;
379
+ const usagePercent = budget.used_tokens / maxTokens * 100;
380
+ if (usagePercent > 12) {
381
+ warnings.push(
382
+ `System prompt using ${usagePercent.toFixed(1)}% of total context (${budget.used_tokens}/${maxTokens} tokens) \u2014 some primitives may be truncated`
383
+ );
384
+ log.warn(
385
+ `Context budget high: ${budget.used_tokens}/${maxTokens} tokens (${usagePercent.toFixed(1)}%), ${budget.loaded_files.length} files loaded`
386
+ );
387
+ }
388
+ if (globalLevel2 < 2) {
389
+ const levelName = globalLevel2 === 0 ? "L0 (summary only)" : "L1 (paragraph summary)";
390
+ warnings.push(`Primitives loaded at ${levelName} due to budget constraints`);
391
+ }
392
+ return {
393
+ systemPrompt: sections.join("\n\n---\n\n"),
394
+ budget,
395
+ parseErrors,
396
+ warnings
397
+ };
398
+ }
399
+
400
+ // src/runtime/state.ts
401
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
402
+ import { join as join5 } from "path";
403
+
404
+ // src/runtime/file-lock.ts
405
+ import { writeFileSync, unlinkSync, readFileSync as readFileSync4, existsSync as existsSync4, mkdirSync } from "fs";
406
+ import { join as join4, basename } from "path";
407
+ var DEFAULT_STALE_MS = 3e4;
408
+ var DEFAULT_RETRY_MS = 50;
409
+ var DEFAULT_WAIT_MS = 5e3;
410
+ function getLockDir(harnessDir) {
411
+ return join4(harnessDir, "memory");
412
+ }
413
+ function getLockPath(harnessDir, filePath) {
414
+ const lockDir = getLockDir(harnessDir);
415
+ const lockName = basename(filePath).replace(/\.[^.]+$/, "") + ".lock";
416
+ return join4(lockDir, lockName);
417
+ }
418
+ function readLockInfo(lockPath) {
419
+ if (!existsSync4(lockPath)) return null;
420
+ try {
421
+ const content = readFileSync4(lockPath, "utf-8");
422
+ const parsed = JSON.parse(content);
423
+ if (typeof parsed === "object" && parsed !== null && "pid" in parsed && "acquired" in parsed) {
424
+ return parsed;
425
+ }
426
+ return null;
427
+ } catch {
428
+ return null;
429
+ }
430
+ }
431
+ function isStale(info, staleMs) {
432
+ const age = Date.now() - new Date(info.acquired).getTime();
433
+ if (age > staleMs) return true;
434
+ try {
435
+ process.kill(info.pid, 0);
436
+ return false;
437
+ } catch {
438
+ return true;
439
+ }
440
+ }
441
+ function tryLock(harnessDir, filePath, options) {
442
+ const staleMs = options?.staleMs ?? DEFAULT_STALE_MS;
443
+ const lockPath = getLockPath(harnessDir, filePath);
444
+ const lockDir = getLockDir(harnessDir);
445
+ if (!existsSync4(lockDir)) {
446
+ mkdirSync(lockDir, { recursive: true });
447
+ }
448
+ const existing = readLockInfo(lockPath);
449
+ if (existing) {
450
+ if (isStale(existing, staleMs)) {
451
+ try {
452
+ unlinkSync(lockPath);
453
+ } catch {
454
+ }
455
+ } else {
456
+ return false;
457
+ }
458
+ }
459
+ const info = {
460
+ pid: process.pid,
461
+ acquired: (/* @__PURE__ */ new Date()).toISOString(),
462
+ file: filePath
463
+ };
464
+ try {
465
+ writeFileSync(lockPath, JSON.stringify(info), { flag: "wx" });
466
+ return true;
467
+ } catch {
468
+ return false;
469
+ }
470
+ }
471
+ function releaseLock(harnessDir, filePath) {
472
+ const lockPath = getLockPath(harnessDir, filePath);
473
+ if (!existsSync4(lockPath)) return;
474
+ const info = readLockInfo(lockPath);
475
+ if (info && info.pid === process.pid) {
476
+ try {
477
+ unlinkSync(lockPath);
478
+ } catch {
479
+ }
480
+ }
481
+ }
482
+ async function acquireLock(harnessDir, filePath, options) {
483
+ const retryMs = options?.retryIntervalMs ?? DEFAULT_RETRY_MS;
484
+ const waitMs = options?.waitMs ?? DEFAULT_WAIT_MS;
485
+ const deadline = Date.now() + waitMs;
486
+ while (Date.now() < deadline) {
487
+ if (tryLock(harnessDir, filePath, options)) {
488
+ return true;
489
+ }
490
+ await new Promise((resolve2) => setTimeout(resolve2, retryMs));
491
+ }
492
+ return false;
493
+ }
494
+ async function withFileLock(harnessDir, filePath, fn, options) {
495
+ const acquired = await acquireLock(harnessDir, filePath, options);
496
+ try {
497
+ return await fn();
498
+ } finally {
499
+ if (acquired) {
500
+ releaseLock(harnessDir, filePath);
501
+ }
502
+ }
503
+ }
504
+ function withFileLockSync(harnessDir, filePath, fn, options) {
505
+ const acquired = tryLock(harnessDir, filePath, options);
506
+ try {
507
+ return fn();
508
+ } finally {
509
+ if (acquired) {
510
+ releaseLock(harnessDir, filePath);
511
+ }
512
+ }
513
+ }
514
+ function isLocked(harnessDir, filePath, options) {
515
+ const staleMs = options?.staleMs ?? DEFAULT_STALE_MS;
516
+ const lockPath = getLockPath(harnessDir, filePath);
517
+ const info = readLockInfo(lockPath);
518
+ if (!info) return false;
519
+ return !isStale(info, staleMs);
520
+ }
521
+ function breakLock(harnessDir, filePath) {
522
+ const lockPath = getLockPath(harnessDir, filePath);
523
+ if (!existsSync4(lockPath)) return false;
524
+ try {
525
+ unlinkSync(lockPath);
526
+ return true;
527
+ } catch {
528
+ return false;
529
+ }
530
+ }
531
+
532
+ // src/runtime/state.ts
533
+ var DEFAULT_STATE = {
534
+ mode: "idle",
535
+ goals: [],
536
+ active_workflows: [],
537
+ last_interaction: (/* @__PURE__ */ new Date()).toISOString(),
538
+ unfinished_business: []
539
+ };
540
+ function loadState(harnessDir) {
541
+ const statePath = join5(harnessDir, "state.md");
542
+ if (!existsSync5(statePath)) {
543
+ return { ...DEFAULT_STATE };
544
+ }
545
+ const content = readFileSync5(statePath, "utf-8");
546
+ return parseStateMd(content);
547
+ }
548
+ function saveState(harnessDir, state) {
549
+ const statePath = join5(harnessDir, "state.md");
550
+ const content = renderStateMd(state);
551
+ withFileLockSync(harnessDir, statePath, () => {
552
+ writeFileSync2(statePath, content, "utf-8");
553
+ });
554
+ }
555
+ function parseStateMd(content) {
556
+ const state = { ...DEFAULT_STATE };
557
+ const modeMatch = content.match(/## Mode\s*\n(.+)/);
558
+ if (modeMatch) state.mode = modeMatch[1].trim();
559
+ const goalsMatch = content.match(/## Goals\s*\n([\s\S]*?)(?=\n## |\n$|$)/);
560
+ if (goalsMatch) {
561
+ state.goals = goalsMatch[1].split("\n").filter((l) => l.startsWith("- ")).map((l) => l.replace(/^- /, "").trim());
562
+ }
563
+ const workflowsMatch = content.match(/## Active Workflows\s*\n([\s\S]*?)(?=\n## |\n$|$)/);
564
+ if (workflowsMatch) {
565
+ state.active_workflows = workflowsMatch[1].split("\n").filter((l) => l.startsWith("- ")).map((l) => l.replace(/^- /, "").trim());
566
+ }
567
+ const lastMatch = content.match(/## Last Interaction\s*\n(.+)/);
568
+ if (lastMatch) state.last_interaction = lastMatch[1].trim();
569
+ const unfinishedMatch = content.match(/## Unfinished Business\s*\n([\s\S]*?)(?=\n## |\n$|$)/);
570
+ if (unfinishedMatch) {
571
+ state.unfinished_business = unfinishedMatch[1].split("\n").filter((l) => l.startsWith("- ")).map((l) => l.replace(/^- /, "").trim());
572
+ }
573
+ return state;
574
+ }
575
+ function renderStateMd(state) {
576
+ const lines = [
577
+ "# Agent State",
578
+ "",
579
+ "## Mode",
580
+ state.mode,
581
+ "",
582
+ "## Goals",
583
+ ...state.goals.map((g) => `- ${g}`),
584
+ "",
585
+ "## Active Workflows",
586
+ ...state.active_workflows.map((w) => `- ${w}`),
587
+ "",
588
+ "## Last Interaction",
589
+ state.last_interaction,
590
+ "",
591
+ "## Unfinished Business",
592
+ ...state.unfinished_business.map((u) => `- ${u}`),
593
+ ""
594
+ ];
595
+ return lines.join("\n");
596
+ }
597
+
598
+ // src/runtime/sessions.ts
599
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync6, readdirSync as readdirSync2, unlinkSync as unlinkSync2, copyFileSync } from "fs";
600
+ import { join as join6 } from "path";
601
+ import { randomUUID } from "crypto";
602
+ function createSessionId() {
603
+ const now = /* @__PURE__ */ new Date();
604
+ const date = now.toISOString().split("T")[0];
605
+ const short = randomUUID().slice(0, 8);
606
+ return `${date}-${short}`;
607
+ }
608
+ function formatToolCalls(toolCalls) {
609
+ if (!toolCalls || toolCalls.length === 0) return "";
610
+ const lines = ["\n## Tools Used\n"];
611
+ for (const tc of toolCalls) {
612
+ lines.push(`### ${tc.toolName}`);
613
+ const argsStr = JSON.stringify(tc.args, null, 2);
614
+ lines.push(`**Args:** \`${argsStr.length > 200 ? argsStr.slice(0, 200) + "..." : argsStr}\``);
615
+ if (tc.result !== null && tc.result !== void 0) {
616
+ const resultStr = typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result);
617
+ lines.push(`**Result:** ${resultStr.length > 300 ? resultStr.slice(0, 300) + "..." : resultStr}`);
618
+ }
619
+ lines.push("");
620
+ }
621
+ return lines.join("\n");
622
+ }
623
+ function writeSession(harnessDir, session) {
624
+ const sessionsDir = join6(harnessDir, "memory", "sessions");
625
+ if (!existsSync6(sessionsDir)) {
626
+ mkdirSync2(sessionsDir, { recursive: true });
627
+ }
628
+ const filePath = join6(sessionsDir, `${session.id}.md`);
629
+ const tags = session.delegated_to ? `[session, delegation, ${session.delegated_to}]` : "[session]";
630
+ const modelLine = session.model_id ? `
631
+ **Model:** ${session.model_id}` : "";
632
+ const delegateLine = session.delegated_to ? `
633
+ **Delegated to:** ${session.delegated_to}` : "";
634
+ const toolSection = formatToolCalls(session.tool_calls);
635
+ const content = `---
636
+ id: ${session.id}
637
+ tags: ${tags}
638
+ created: ${session.started}
639
+ updated: ${session.ended}
640
+ author: agent
641
+ status: active
642
+ duration_minutes: ${Math.round((new Date(session.ended).getTime() - new Date(session.started).getTime()) / 6e4)}
643
+ ---
644
+
645
+ <!-- L0: Session ${session.id} \u2014 ${session.summary.slice(0, 60)} -->
646
+ <!-- L1: ${session.summary} -->
647
+
648
+ # Session: ${session.id}
649
+
650
+ **Started:** ${session.started}
651
+ **Ended:** ${session.ended}
652
+ **Tokens:** ${session.tokens_used}
653
+ **Steps:** ${session.steps}${modelLine}${delegateLine}
654
+
655
+ ## Prompt
656
+ ${session.prompt}
657
+
658
+ ## Summary
659
+ ${session.summary}
660
+ ${toolSection}`;
661
+ withFileLockSync(harnessDir, filePath, () => {
662
+ writeFileSync3(filePath, content, "utf-8");
663
+ });
664
+ return filePath;
665
+ }
666
+ function archiveOldFiles(harnessDir, sessionRetentionDays, journalRetentionDays) {
667
+ const result = {
668
+ sessionsArchived: 0,
669
+ journalsArchived: 0,
670
+ sessionFiles: [],
671
+ journalFiles: []
672
+ };
673
+ const now = Date.now();
674
+ const sessionsDir = join6(harnessDir, "memory", "sessions");
675
+ if (existsSync6(sessionsDir)) {
676
+ const cutoff = now - sessionRetentionDays * 24 * 60 * 60 * 1e3;
677
+ const files = readdirSync2(sessionsDir).filter(
678
+ (f) => f.endsWith(".md") && !f.startsWith(".") && !f.startsWith("_")
679
+ );
680
+ for (const file of files) {
681
+ const dateStr = extractDateFromFilename(file);
682
+ if (dateStr && new Date(dateStr).getTime() < cutoff) {
683
+ const yearMonth = dateStr.slice(0, 7);
684
+ const archiveDir = join6(sessionsDir, "archive", yearMonth);
685
+ mkdirSync2(archiveDir, { recursive: true });
686
+ copyFileSync(join6(sessionsDir, file), join6(archiveDir, file));
687
+ unlinkSync2(join6(sessionsDir, file));
688
+ result.sessionsArchived++;
689
+ result.sessionFiles.push(file);
690
+ }
691
+ }
692
+ }
693
+ const journalDir = join6(harnessDir, "memory", "journal");
694
+ if (existsSync6(journalDir)) {
695
+ const cutoff = now - journalRetentionDays * 24 * 60 * 60 * 1e3;
696
+ const files = readdirSync2(journalDir).filter(
697
+ (f) => f.endsWith(".md") && !f.startsWith(".") && !f.startsWith("_")
698
+ );
699
+ for (const file of files) {
700
+ const dateStr = extractDateFromFilename(file);
701
+ if (dateStr && new Date(dateStr).getTime() < cutoff) {
702
+ const yearMonth = dateStr.slice(0, 7);
703
+ const archiveDir = join6(journalDir, "archive", yearMonth);
704
+ mkdirSync2(archiveDir, { recursive: true });
705
+ copyFileSync(join6(journalDir, file), join6(archiveDir, file));
706
+ unlinkSync2(join6(journalDir, file));
707
+ result.journalsArchived++;
708
+ result.journalFiles.push(file);
709
+ }
710
+ }
711
+ }
712
+ return result;
713
+ }
714
+ function cleanupOldFiles(harnessDir, sessionRetentionDays, journalRetentionDays) {
715
+ const result = {
716
+ sessionsRemoved: 0,
717
+ journalsRemoved: 0,
718
+ sessionFiles: [],
719
+ journalFiles: []
720
+ };
721
+ const now = Date.now();
722
+ const sessionsDir = join6(harnessDir, "memory", "sessions");
723
+ if (existsSync6(sessionsDir)) {
724
+ const cutoff = now - sessionRetentionDays * 24 * 60 * 60 * 1e3;
725
+ const files = readdirSync2(sessionsDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
726
+ for (const file of files) {
727
+ const dateStr = extractDateFromFilename(file);
728
+ if (dateStr && new Date(dateStr).getTime() < cutoff) {
729
+ unlinkSync2(join6(sessionsDir, file));
730
+ result.sessionsRemoved++;
731
+ result.sessionFiles.push(file);
732
+ }
733
+ }
734
+ }
735
+ const journalDir = join6(harnessDir, "memory", "journal");
736
+ if (existsSync6(journalDir)) {
737
+ const cutoff = now - journalRetentionDays * 24 * 60 * 60 * 1e3;
738
+ const files = readdirSync2(journalDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
739
+ for (const file of files) {
740
+ const dateStr = extractDateFromFilename(file);
741
+ if (dateStr && new Date(dateStr).getTime() < cutoff) {
742
+ unlinkSync2(join6(journalDir, file));
743
+ result.journalsRemoved++;
744
+ result.journalFiles.push(file);
745
+ }
746
+ }
747
+ }
748
+ return result;
749
+ }
750
+ function extractDateFromFilename(filename) {
751
+ const match = filename.match(/^(\d{4}-\d{2}-\d{2})/);
752
+ if (!match) return null;
753
+ const date = new Date(match[1]);
754
+ return isNaN(date.getTime()) ? null : match[1];
755
+ }
756
+ function listSessions(harnessDir) {
757
+ const sessionsDir = join6(harnessDir, "memory", "sessions");
758
+ if (!existsSync6(sessionsDir)) return [];
759
+ return readdirSync2(sessionsDir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).sort().reverse().map((f) => ({
760
+ id: f.replace(".md", ""),
761
+ date: extractDateFromFilename(f) || "unknown",
762
+ path: join6(sessionsDir, f)
763
+ }));
764
+ }
765
+ function listExpiredFiles(harnessDir, sessionRetentionDays, journalRetentionDays) {
766
+ const now = Date.now();
767
+ const sessionFiles = [];
768
+ const journalFiles = [];
769
+ const sessionsDir = join6(harnessDir, "memory", "sessions");
770
+ if (existsSync6(sessionsDir)) {
771
+ const cutoff = now - sessionRetentionDays * 24 * 60 * 60 * 1e3;
772
+ const files = readdirSync2(sessionsDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
773
+ for (const file of files) {
774
+ const dateStr = extractDateFromFilename(file);
775
+ if (dateStr && new Date(dateStr).getTime() < cutoff) {
776
+ sessionFiles.push(file);
777
+ }
778
+ }
779
+ }
780
+ const journalDir = join6(harnessDir, "memory", "journal");
781
+ if (existsSync6(journalDir)) {
782
+ const cutoff = now - journalRetentionDays * 24 * 60 * 60 * 1e3;
783
+ const files = readdirSync2(journalDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
784
+ for (const file of files) {
785
+ const dateStr = extractDateFromFilename(file);
786
+ if (dateStr && new Date(dateStr).getTime() < cutoff) {
787
+ journalFiles.push(file);
788
+ }
789
+ }
790
+ }
791
+ return { sessionFiles, journalFiles };
792
+ }
793
+
794
+ // src/runtime/cost-tracker.ts
795
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
796
+ import { join as join7 } from "path";
797
+ var COST_FILE = "costs.json";
798
+ var MAX_ENTRIES = 5e3;
799
+ var DEFAULT_PRICING = [
800
+ // Anthropic via OpenRouter
801
+ { model_pattern: "anthropic/claude-sonnet-4", input_per_million: 3, output_per_million: 15 },
802
+ { model_pattern: "anthropic/claude-opus-4", input_per_million: 15, output_per_million: 75 },
803
+ { model_pattern: "anthropic/claude-haiku-3.5", input_per_million: 0.8, output_per_million: 4 },
804
+ // Direct Anthropic
805
+ { model_pattern: "claude-sonnet-4", input_per_million: 3, output_per_million: 15 },
806
+ { model_pattern: "claude-opus-4", input_per_million: 15, output_per_million: 75 },
807
+ { model_pattern: "claude-haiku-3.5", input_per_million: 0.8, output_per_million: 4 },
808
+ // OpenAI
809
+ { model_pattern: "openai/gpt-4o", input_per_million: 2.5, output_per_million: 10 },
810
+ { model_pattern: "gpt-4o", input_per_million: 2.5, output_per_million: 10 },
811
+ { model_pattern: "openai/gpt-4o-mini", input_per_million: 0.15, output_per_million: 0.6 },
812
+ { model_pattern: "gpt-4o-mini", input_per_million: 0.15, output_per_million: 0.6 },
813
+ // Local models
814
+ { model_pattern: "local/", input_per_million: 0, output_per_million: 0 }
815
+ ];
816
+ function getStorePath(harnessDir) {
817
+ return join7(harnessDir, "memory", COST_FILE);
818
+ }
819
+ function loadCosts(harnessDir) {
820
+ const storePath = getStorePath(harnessDir);
821
+ if (!existsSync7(storePath)) {
822
+ return { entries: [], updated: (/* @__PURE__ */ new Date()).toISOString() };
823
+ }
824
+ try {
825
+ const content = readFileSync6(storePath, "utf-8");
826
+ const parsed = JSON.parse(content);
827
+ if (typeof parsed === "object" && parsed !== null && "entries" in parsed && Array.isArray(parsed.entries)) {
828
+ return parsed;
829
+ }
830
+ return { entries: [], updated: (/* @__PURE__ */ new Date()).toISOString() };
831
+ } catch {
832
+ return { entries: [], updated: (/* @__PURE__ */ new Date()).toISOString() };
833
+ }
834
+ }
835
+ function saveCosts(harnessDir, store) {
836
+ const memoryDir = join7(harnessDir, "memory");
837
+ if (!existsSync7(memoryDir)) {
838
+ mkdirSync3(memoryDir, { recursive: true });
839
+ }
840
+ if (store.entries.length > MAX_ENTRIES) {
841
+ store.entries = store.entries.slice(store.entries.length - MAX_ENTRIES);
842
+ }
843
+ store.updated = (/* @__PURE__ */ new Date()).toISOString();
844
+ writeFileSync4(getStorePath(harnessDir), JSON.stringify(store, null, 2), "utf-8");
845
+ }
846
+ function findPricing(modelId, customPricing) {
847
+ const allPricing = [...customPricing ?? [], ...DEFAULT_PRICING];
848
+ for (const pricing of allPricing) {
849
+ if (modelId === pricing.model_pattern || modelId.startsWith(pricing.model_pattern)) {
850
+ return pricing;
851
+ }
852
+ }
853
+ return null;
854
+ }
855
+ function calculateCost(modelId, inputTokens, outputTokens, customPricing) {
856
+ const pricing = findPricing(modelId, customPricing);
857
+ if (!pricing) return 0;
858
+ const inputCost = inputTokens / 1e6 * pricing.input_per_million;
859
+ const outputCost = outputTokens / 1e6 * pricing.output_per_million;
860
+ return Math.round((inputCost + outputCost) * 1e6) / 1e6;
861
+ }
862
+ function recordCost(harnessDir, entry, customPricing) {
863
+ const store = loadCosts(harnessDir);
864
+ const costUsd = entry.cost_usd ?? calculateCost(
865
+ entry.model_id,
866
+ entry.input_tokens,
867
+ entry.output_tokens,
868
+ customPricing
869
+ );
870
+ const fullEntry = {
871
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
872
+ model_id: entry.model_id,
873
+ provider: entry.provider,
874
+ input_tokens: entry.input_tokens,
875
+ output_tokens: entry.output_tokens,
876
+ cost_usd: costUsd,
877
+ source: entry.source
878
+ };
879
+ store.entries.push(fullEntry);
880
+ saveCosts(harnessDir, store);
881
+ return fullEntry;
882
+ }
883
+ function getSpending(harnessDir, from, to) {
884
+ const store = loadCosts(harnessDir);
885
+ const fromDate = from ?? (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
886
+ const toDate = to ?? new Date(Date.now() + 864e5).toISOString().split("T")[0];
887
+ const filtered = store.entries.filter(
888
+ (e) => e.timestamp >= fromDate && e.timestamp < toDate + "T99"
889
+ );
890
+ const byModel = {};
891
+ const byProvider = {};
892
+ let totalCost = 0;
893
+ let totalInput = 0;
894
+ let totalOutput = 0;
895
+ for (const entry of filtered) {
896
+ totalCost += entry.cost_usd;
897
+ totalInput += entry.input_tokens;
898
+ totalOutput += entry.output_tokens;
899
+ if (!byModel[entry.model_id]) {
900
+ byModel[entry.model_id] = { cost_usd: 0, input_tokens: 0, output_tokens: 0, count: 0 };
901
+ }
902
+ byModel[entry.model_id].cost_usd += entry.cost_usd;
903
+ byModel[entry.model_id].input_tokens += entry.input_tokens;
904
+ byModel[entry.model_id].output_tokens += entry.output_tokens;
905
+ byModel[entry.model_id].count += 1;
906
+ if (!byProvider[entry.provider]) {
907
+ byProvider[entry.provider] = { cost_usd: 0, count: 0 };
908
+ }
909
+ byProvider[entry.provider].cost_usd += entry.cost_usd;
910
+ byProvider[entry.provider].count += 1;
911
+ }
912
+ return {
913
+ total_cost_usd: Math.round(totalCost * 1e6) / 1e6,
914
+ total_input_tokens: totalInput,
915
+ total_output_tokens: totalOutput,
916
+ entries: filtered.length,
917
+ by_model: byModel,
918
+ by_provider: byProvider
919
+ };
920
+ }
921
+ function checkBudget(harnessDir, budget) {
922
+ const now = /* @__PURE__ */ new Date();
923
+ const today = now.toISOString().split("T")[0];
924
+ const monthStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-01`;
925
+ const dailySpending = getSpending(harnessDir, today);
926
+ const monthlySpending = getSpending(harnessDir, monthStart);
927
+ const alerts = [];
928
+ const alertPct = budget.alert_threshold_pct ?? 80;
929
+ const dailyLimit = budget.daily_limit_usd ?? null;
930
+ const monthlyLimit = budget.monthly_limit_usd ?? null;
931
+ let dailyPct = null;
932
+ let dailyRemaining = null;
933
+ if (dailyLimit !== null) {
934
+ dailyPct = dailyLimit > 0 ? dailySpending.total_cost_usd / dailyLimit * 100 : 0;
935
+ dailyRemaining = Math.max(0, dailyLimit - dailySpending.total_cost_usd);
936
+ if (dailySpending.total_cost_usd >= dailyLimit) {
937
+ alerts.push(`Daily budget exceeded: $${dailySpending.total_cost_usd.toFixed(4)} / $${dailyLimit.toFixed(2)}`);
938
+ } else if (dailyPct >= alertPct) {
939
+ alerts.push(`Daily budget at ${dailyPct.toFixed(0)}%: $${dailySpending.total_cost_usd.toFixed(4)} / $${dailyLimit.toFixed(2)}`);
940
+ }
941
+ }
942
+ let monthlyPct = null;
943
+ let monthlyRemaining = null;
944
+ if (monthlyLimit !== null) {
945
+ monthlyPct = monthlyLimit > 0 ? monthlySpending.total_cost_usd / monthlyLimit * 100 : 0;
946
+ monthlyRemaining = Math.max(0, monthlyLimit - monthlySpending.total_cost_usd);
947
+ if (monthlySpending.total_cost_usd >= monthlyLimit) {
948
+ alerts.push(`Monthly budget exceeded: $${monthlySpending.total_cost_usd.toFixed(4)} / $${monthlyLimit.toFixed(2)}`);
949
+ } else if (monthlyPct >= alertPct) {
950
+ alerts.push(`Monthly budget at ${monthlyPct.toFixed(0)}%: $${monthlySpending.total_cost_usd.toFixed(4)} / $${monthlyLimit.toFixed(2)}`);
951
+ }
952
+ }
953
+ return {
954
+ daily_spent_usd: dailySpending.total_cost_usd,
955
+ daily_limit_usd: dailyLimit,
956
+ daily_remaining_usd: dailyRemaining,
957
+ daily_pct: dailyPct,
958
+ monthly_spent_usd: monthlySpending.total_cost_usd,
959
+ monthly_limit_usd: monthlyLimit,
960
+ monthly_remaining_usd: monthlyRemaining,
961
+ monthly_pct: monthlyPct,
962
+ alerts
963
+ };
964
+ }
965
+ function clearCosts(harnessDir, modelId) {
966
+ const store = loadCosts(harnessDir);
967
+ const before = store.entries.length;
968
+ if (modelId) {
969
+ store.entries = store.entries.filter((e) => e.model_id !== modelId);
970
+ } else {
971
+ store.entries = [];
972
+ }
973
+ saveCosts(harnessDir, store);
974
+ return before - store.entries.length;
975
+ }
976
+
977
+ // src/runtime/health.ts
978
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
979
+ import { join as join8 } from "path";
980
+ var HEALTH_FILE = "health.json";
981
+ function getHealthPath(harnessDir) {
982
+ return join8(harnessDir, "memory", HEALTH_FILE);
983
+ }
984
+ function defaultMetrics() {
985
+ return {
986
+ lastSuccessfulRun: null,
987
+ lastFailedRun: null,
988
+ lastError: null,
989
+ consecutiveFailures: 0,
990
+ totalRuns: 0,
991
+ totalSuccesses: 0,
992
+ totalFailures: 0,
993
+ bootedAt: null,
994
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
995
+ };
996
+ }
997
+ function loadHealth(harnessDir) {
998
+ const healthPath = getHealthPath(harnessDir);
999
+ if (!existsSync8(healthPath)) {
1000
+ return defaultMetrics();
1001
+ }
1002
+ try {
1003
+ const content = readFileSync7(healthPath, "utf-8");
1004
+ const parsed = JSON.parse(content);
1005
+ if (typeof parsed === "object" && parsed !== null && "totalRuns" in parsed) {
1006
+ return parsed;
1007
+ }
1008
+ return defaultMetrics();
1009
+ } catch {
1010
+ return defaultMetrics();
1011
+ }
1012
+ }
1013
+ function saveHealth(harnessDir, metrics) {
1014
+ const memoryDir = join8(harnessDir, "memory");
1015
+ if (!existsSync8(memoryDir)) {
1016
+ mkdirSync4(memoryDir, { recursive: true });
1017
+ }
1018
+ metrics.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1019
+ writeFileSync5(getHealthPath(harnessDir), JSON.stringify(metrics, null, 2), "utf-8");
1020
+ }
1021
+ function recordSuccess(harnessDir) {
1022
+ const metrics = loadHealth(harnessDir);
1023
+ metrics.totalRuns++;
1024
+ metrics.totalSuccesses++;
1025
+ metrics.consecutiveFailures = 0;
1026
+ metrics.lastSuccessfulRun = (/* @__PURE__ */ new Date()).toISOString();
1027
+ saveHealth(harnessDir, metrics);
1028
+ }
1029
+ function recordFailure(harnessDir, error) {
1030
+ const metrics = loadHealth(harnessDir);
1031
+ metrics.totalRuns++;
1032
+ metrics.totalFailures++;
1033
+ metrics.consecutiveFailures++;
1034
+ metrics.lastFailedRun = (/* @__PURE__ */ new Date()).toISOString();
1035
+ metrics.lastError = error ?? null;
1036
+ saveHealth(harnessDir, metrics);
1037
+ }
1038
+ function recordBoot(harnessDir) {
1039
+ const metrics = loadHealth(harnessDir);
1040
+ metrics.bootedAt = (/* @__PURE__ */ new Date()).toISOString();
1041
+ saveHealth(harnessDir, metrics);
1042
+ }
1043
+ function getHealthStatus(harnessDir) {
1044
+ const metrics = loadHealth(harnessDir);
1045
+ const checks = [];
1046
+ const requiredFiles = ["CORE.md", "config.yaml", "state.md"];
1047
+ const missingFiles = requiredFiles.filter((f) => !existsSync8(join8(harnessDir, f)));
1048
+ if (missingFiles.length === 0) {
1049
+ checks.push({ name: "core-files", status: "pass", message: "All core files present" });
1050
+ } else {
1051
+ checks.push({ name: "core-files", status: "fail", message: `Missing: ${missingFiles.join(", ")}` });
1052
+ }
1053
+ const memoryDir = join8(harnessDir, "memory");
1054
+ if (existsSync8(memoryDir)) {
1055
+ checks.push({ name: "memory-dir", status: "pass", message: "Memory directory exists" });
1056
+ } else {
1057
+ checks.push({ name: "memory-dir", status: "fail", message: "Memory directory missing" });
1058
+ }
1059
+ const apiKeys = [
1060
+ { name: "OpenRouter", envVar: "OPENROUTER_API_KEY" },
1061
+ { name: "Anthropic", envVar: "ANTHROPIC_API_KEY" },
1062
+ { name: "OpenAI", envVar: "OPENAI_API_KEY" }
1063
+ ];
1064
+ const presentKeys = apiKeys.filter((k) => process.env[k.envVar]);
1065
+ if (presentKeys.length > 0) {
1066
+ checks.push({
1067
+ name: "api-keys",
1068
+ status: "pass",
1069
+ message: `API keys: ${presentKeys.map((k) => k.name).join(", ")}`
1070
+ });
1071
+ } else {
1072
+ checks.push({ name: "api-keys", status: "warn", message: "No API keys found in environment" });
1073
+ }
1074
+ if (metrics.consecutiveFailures === 0) {
1075
+ checks.push({ name: "run-health", status: "pass", message: "No consecutive failures" });
1076
+ } else if (metrics.consecutiveFailures < 3) {
1077
+ checks.push({
1078
+ name: "run-health",
1079
+ status: "warn",
1080
+ message: `${metrics.consecutiveFailures} consecutive failure(s)`
1081
+ });
1082
+ } else {
1083
+ checks.push({
1084
+ name: "run-health",
1085
+ status: "fail",
1086
+ message: `${metrics.consecutiveFailures} consecutive failures \u2014 last error: ${metrics.lastError ?? "unknown"}`
1087
+ });
1088
+ }
1089
+ if (metrics.lastSuccessfulRun) {
1090
+ const hoursSinceSuccess = (Date.now() - new Date(metrics.lastSuccessfulRun).getTime()) / 36e5;
1091
+ if (hoursSinceSuccess < 24) {
1092
+ checks.push({ name: "last-success", status: "pass", message: `Last success: ${metrics.lastSuccessfulRun}` });
1093
+ } else {
1094
+ checks.push({
1095
+ name: "last-success",
1096
+ status: "warn",
1097
+ message: `Last success was ${Math.round(hoursSinceSuccess)}h ago`
1098
+ });
1099
+ }
1100
+ } else if (metrics.totalRuns > 0) {
1101
+ checks.push({ name: "last-success", status: "warn", message: "No successful runs recorded" });
1102
+ }
1103
+ let costToday = 0;
1104
+ let costThisMonth = 0;
1105
+ try {
1106
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1107
+ const monthStart = `${(/* @__PURE__ */ new Date()).getFullYear()}-${String((/* @__PURE__ */ new Date()).getMonth() + 1).padStart(2, "0")}-01`;
1108
+ costToday = getSpending(harnessDir, today).total_cost_usd;
1109
+ costThisMonth = getSpending(harnessDir, monthStart).total_cost_usd;
1110
+ } catch {
1111
+ }
1112
+ const failCount = checks.filter((c) => c.status === "fail").length;
1113
+ const warnCount = checks.filter((c) => c.status === "warn").length;
1114
+ let status;
1115
+ if (failCount > 0) {
1116
+ status = "unhealthy";
1117
+ } else if (warnCount > 0) {
1118
+ status = "degraded";
1119
+ } else {
1120
+ status = "healthy";
1121
+ }
1122
+ return { status, checks, metrics, costToday, costThisMonth };
1123
+ }
1124
+ function resetHealth(harnessDir) {
1125
+ saveHealth(harnessDir, defaultMetrics());
1126
+ }
1127
+
1128
+ // src/runtime/rate-limiter.ts
1129
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
1130
+ import { join as join9 } from "path";
1131
+ var RATE_FILE = "rate-limits.json";
1132
+ var MAX_EVENTS = 1e4;
1133
+ function getStorePath2(harnessDir) {
1134
+ return join9(harnessDir, "memory", RATE_FILE);
1135
+ }
1136
+ function loadRateLimits(harnessDir) {
1137
+ const storePath = getStorePath2(harnessDir);
1138
+ if (!existsSync9(storePath)) {
1139
+ return { events: [], updated: (/* @__PURE__ */ new Date()).toISOString() };
1140
+ }
1141
+ try {
1142
+ const content = readFileSync8(storePath, "utf-8");
1143
+ const parsed = JSON.parse(content);
1144
+ if (typeof parsed === "object" && parsed !== null && "events" in parsed && Array.isArray(parsed.events)) {
1145
+ return parsed;
1146
+ }
1147
+ return { events: [], updated: (/* @__PURE__ */ new Date()).toISOString() };
1148
+ } catch {
1149
+ return { events: [], updated: (/* @__PURE__ */ new Date()).toISOString() };
1150
+ }
1151
+ }
1152
+ function saveRateLimits(harnessDir, store) {
1153
+ const memoryDir = join9(harnessDir, "memory");
1154
+ if (!existsSync9(memoryDir)) {
1155
+ mkdirSync5(memoryDir, { recursive: true });
1156
+ }
1157
+ if (store.events.length > MAX_EVENTS) {
1158
+ store.events = store.events.slice(store.events.length - MAX_EVENTS);
1159
+ }
1160
+ store.updated = (/* @__PURE__ */ new Date()).toISOString();
1161
+ writeFileSync6(getStorePath2(harnessDir), JSON.stringify(store, null, 2), "utf-8");
1162
+ }
1163
+ function pruneExpired(store, now, maxWindowMs) {
1164
+ const cutoff = now - maxWindowMs;
1165
+ store.events = store.events.filter((e) => e.timestamp > cutoff);
1166
+ }
1167
+ function checkRateLimit(harnessDir, limit, now) {
1168
+ const currentTime = now ?? Date.now();
1169
+ const store = loadRateLimits(harnessDir);
1170
+ const windowStart = currentTime - limit.window_ms;
1171
+ const eventsInWindow = store.events.filter(
1172
+ (e) => e.key === limit.key && e.timestamp > windowStart
1173
+ );
1174
+ const current = eventsInWindow.length;
1175
+ const allowed = current < limit.max_requests;
1176
+ let retryAfterMs = 0;
1177
+ if (!allowed && eventsInWindow.length > 0) {
1178
+ const oldest = eventsInWindow.reduce((min, e) => Math.min(min, e.timestamp), Infinity);
1179
+ retryAfterMs = Math.max(0, oldest + limit.window_ms - currentTime);
1180
+ }
1181
+ return {
1182
+ allowed,
1183
+ key: limit.key,
1184
+ current,
1185
+ max: limit.max_requests,
1186
+ window_ms: limit.window_ms,
1187
+ retry_after_ms: retryAfterMs
1188
+ };
1189
+ }
1190
+ function recordEvent(harnessDir, key, now) {
1191
+ const currentTime = now ?? Date.now();
1192
+ const store = loadRateLimits(harnessDir);
1193
+ store.events.push({ key, timestamp: currentTime });
1194
+ pruneExpired(store, currentTime, 36e5);
1195
+ saveRateLimits(harnessDir, store);
1196
+ }
1197
+ function tryAcquire(harnessDir, limit, now) {
1198
+ const currentTime = now ?? Date.now();
1199
+ const check = checkRateLimit(harnessDir, limit, currentTime);
1200
+ if (check.allowed) {
1201
+ recordEvent(harnessDir, limit.key, currentTime);
1202
+ }
1203
+ return check;
1204
+ }
1205
+ function getUsage(harnessDir, key, windowMs, now) {
1206
+ const currentTime = now ?? Date.now();
1207
+ const store = loadRateLimits(harnessDir);
1208
+ const windowStart = currentTime - windowMs;
1209
+ const eventsInWindow = store.events.filter(
1210
+ (e) => e.key === key && e.timestamp > windowStart
1211
+ );
1212
+ if (eventsInWindow.length === 0) {
1213
+ return { count: 0, oldest: null, newest: null };
1214
+ }
1215
+ const timestamps = eventsInWindow.map((e) => e.timestamp);
1216
+ return {
1217
+ count: eventsInWindow.length,
1218
+ oldest: Math.min(...timestamps),
1219
+ newest: Math.max(...timestamps)
1220
+ };
1221
+ }
1222
+ function clearRateLimits(harnessDir, key) {
1223
+ const store = loadRateLimits(harnessDir);
1224
+ const before = store.events.length;
1225
+ if (key) {
1226
+ store.events = store.events.filter((e) => e.key !== key);
1227
+ } else {
1228
+ store.events = [];
1229
+ }
1230
+ saveRateLimits(harnessDir, store);
1231
+ return before - store.events.length;
1232
+ }
1233
+
1234
+ // src/runtime/guardrails.ts
1235
+ var RATE_KEY = "llm-calls";
1236
+ function buildRateLimits(config) {
1237
+ const limits = [];
1238
+ const rl = config.rate_limits;
1239
+ if (rl?.per_minute) {
1240
+ limits.push({ key: `${RATE_KEY}:minute`, max_requests: rl.per_minute, window_ms: 6e4 });
1241
+ }
1242
+ if (rl?.per_hour) {
1243
+ limits.push({ key: `${RATE_KEY}:hour`, max_requests: rl.per_hour, window_ms: 36e5 });
1244
+ }
1245
+ if (rl?.per_day) {
1246
+ limits.push({ key: `${RATE_KEY}:day`, max_requests: rl.per_day, window_ms: 864e5 });
1247
+ }
1248
+ return limits;
1249
+ }
1250
+ function checkGuardrails(harnessDir, config) {
1251
+ const limits = buildRateLimits(config);
1252
+ for (const limit of limits) {
1253
+ const check = checkRateLimit(harnessDir, limit);
1254
+ if (!check.allowed) {
1255
+ const windowLabel = limit.window_ms <= 6e4 ? "minute" : limit.window_ms <= 36e5 ? "hour" : "day";
1256
+ return {
1257
+ allowed: false,
1258
+ reason: `Rate limit exceeded: ${check.current}/${check.max} calls per ${windowLabel}. Retry after ${Math.ceil(check.retry_after_ms / 1e3)}s.`,
1259
+ rateLimitCheck: check,
1260
+ budgetStatus: null,
1261
+ retryAfterMs: check.retry_after_ms
1262
+ };
1263
+ }
1264
+ }
1265
+ const budgetConfig = config.budget;
1266
+ if (budgetConfig?.enforce !== false && (budgetConfig?.daily_limit_usd || budgetConfig?.monthly_limit_usd)) {
1267
+ const status = checkBudget(harnessDir, {
1268
+ daily_limit_usd: budgetConfig.daily_limit_usd,
1269
+ monthly_limit_usd: budgetConfig.monthly_limit_usd
1270
+ });
1271
+ const exceeded = status.alerts.some((a) => a.includes("exceeded"));
1272
+ if (exceeded) {
1273
+ return {
1274
+ allowed: false,
1275
+ reason: status.alerts.filter((a) => a.includes("exceeded")).join("; "),
1276
+ rateLimitCheck: null,
1277
+ budgetStatus: status,
1278
+ retryAfterMs: 0
1279
+ };
1280
+ }
1281
+ }
1282
+ const now = Date.now();
1283
+ for (const limit of limits) {
1284
+ recordEvent(harnessDir, limit.key, now);
1285
+ }
1286
+ return {
1287
+ allowed: true,
1288
+ reason: null,
1289
+ rateLimitCheck: null,
1290
+ budgetStatus: null,
1291
+ retryAfterMs: 0
1292
+ };
1293
+ }
1294
+
1295
+ // src/runtime/tool-executor.ts
1296
+ import { tool as aiTool, jsonSchema } from "ai";
1297
+
1298
+ // src/runtime/tools.ts
1299
+ import { existsSync as existsSync10 } from "fs";
1300
+ import { join as join10 } from "path";
1301
+ function extractAuth(body) {
1302
+ const authSection = body.match(/## Authentication\s*\n([\s\S]*?)(?=\n## |\n$|$)/i);
1303
+ if (!authSection) return [];
1304
+ const envVarPattern = /`([A-Z][A-Z0-9_]+)`/g;
1305
+ const vars = [];
1306
+ const seen = /* @__PURE__ */ new Set();
1307
+ let match;
1308
+ while ((match = envVarPattern.exec(authSection[1])) !== null) {
1309
+ const envVar = match[1];
1310
+ if (!seen.has(envVar)) {
1311
+ seen.add(envVar);
1312
+ vars.push({
1313
+ envVar,
1314
+ present: process.env[envVar] !== void 0 && process.env[envVar] !== ""
1315
+ });
1316
+ }
1317
+ }
1318
+ return vars;
1319
+ }
1320
+ function extractOperations(body) {
1321
+ const opsSection = body.match(/## (?:Common )?Operations\s*\n([\s\S]*?)(?=\n## |\n$|$)/i);
1322
+ if (!opsSection) return [];
1323
+ const ops = [];
1324
+ const lines = opsSection[1].split("\n");
1325
+ let currentSection = "";
1326
+ for (const line of lines) {
1327
+ const headingMatch = line.match(/^### (.+)/);
1328
+ if (headingMatch) {
1329
+ currentSection = headingMatch[1].trim();
1330
+ continue;
1331
+ }
1332
+ const opMatch = line.match(/`?(GET|POST|PUT|DELETE|PATCH)\s+(\S+?)`?(?:\s|$)/);
1333
+ if (opMatch) {
1334
+ const name = currentSection ? `${currentSection}: ${opMatch[2].split("/").pop() || opMatch[2]}` : opMatch[2].split("/").pop() || opMatch[2];
1335
+ ops.push({
1336
+ name,
1337
+ method: opMatch[1],
1338
+ endpoint: opMatch[2]
1339
+ });
1340
+ }
1341
+ }
1342
+ return ops;
1343
+ }
1344
+ function extractRateLimits(body) {
1345
+ const section = body.match(/## Rate Limits\s*\n([\s\S]*?)(?=\n## |\n$|$)/i);
1346
+ if (!section) return [];
1347
+ return section[1].split("\n").filter((l) => l.startsWith("- ")).map((l) => l.replace(/^- /, "").trim());
1348
+ }
1349
+ function extractGotchas(body) {
1350
+ const section = body.match(/## Gotchas\s*\n([\s\S]*?)(?=\n## |\n$|$)/i);
1351
+ if (!section) return [];
1352
+ return section[1].split("\n").filter((l) => l.startsWith("- ")).map((l) => l.replace(/^- /, "").trim());
1353
+ }
1354
+ function parseToolDefinition(doc) {
1355
+ return {
1356
+ id: doc.frontmatter.id,
1357
+ doc,
1358
+ tags: doc.frontmatter.tags,
1359
+ status: doc.frontmatter.status,
1360
+ auth: extractAuth(doc.body),
1361
+ operations: extractOperations(doc.body),
1362
+ rateLimits: extractRateLimits(doc.body),
1363
+ gotchas: extractGotchas(doc.body)
1364
+ };
1365
+ }
1366
+ function loadTools(harnessDir) {
1367
+ const toolsDir = join10(harnessDir, "tools");
1368
+ if (!existsSync10(toolsDir)) return [];
1369
+ const docs = loadDirectory(toolsDir);
1370
+ return docs.map(parseToolDefinition);
1371
+ }
1372
+ function getToolById(harnessDir, toolId) {
1373
+ const tools = loadTools(harnessDir);
1374
+ return tools.find((t) => t.id === toolId) ?? null;
1375
+ }
1376
+ function listToolSummaries(harnessDir) {
1377
+ const tools = loadTools(harnessDir);
1378
+ return tools.map((t) => ({
1379
+ id: t.id,
1380
+ l0: t.doc.l0,
1381
+ tags: t.tags,
1382
+ status: t.status,
1383
+ authReady: t.auth.length === 0 || t.auth.every((a) => a.present),
1384
+ operationCount: t.operations.length
1385
+ }));
1386
+ }
1387
+ function checkToolAuth(harnessDir, toolId) {
1388
+ const tools = toolId ? [getToolById(harnessDir, toolId)].filter((t) => t !== null) : loadTools(harnessDir);
1389
+ return tools.map((t) => ({
1390
+ tool: t.id,
1391
+ auth: t.auth
1392
+ }));
1393
+ }
1394
+
1395
+ // src/runtime/tool-executor.ts
1396
+ function resolveEndpoint(endpoint, input) {
1397
+ return endpoint.replace(/\{(\w+)\}/g, (_match, key) => {
1398
+ const value = input[key];
1399
+ if (value === void 0 || value === null) {
1400
+ return `{${key}}`;
1401
+ }
1402
+ return encodeURIComponent(String(value));
1403
+ });
1404
+ }
1405
+ function buildOperationSchema(operation) {
1406
+ const params = [];
1407
+ const paramRegex = /\{(\w+)\}/g;
1408
+ let match;
1409
+ while ((match = paramRegex.exec(operation.endpoint)) !== null) {
1410
+ params.push(match[1]);
1411
+ }
1412
+ const properties = {};
1413
+ for (const param of params) {
1414
+ properties[param] = { type: "string", description: `Value for ${param}` };
1415
+ }
1416
+ if (["POST", "PUT", "PATCH"].includes(operation.method)) {
1417
+ properties["body"] = { type: "string", description: "Request body (JSON string)" };
1418
+ }
1419
+ properties["query"] = { type: "string", description: "Query parameters (key=value&key2=value2)" };
1420
+ return {
1421
+ type: "object",
1422
+ properties,
1423
+ required: params
1424
+ };
1425
+ }
1426
+ async function executeHttpOperation(operation, baseUrl, authHeaders, input, timeoutMs) {
1427
+ const resolvedPath = resolveEndpoint(operation.endpoint, input);
1428
+ let url = resolvedPath.startsWith("http") ? resolvedPath : `${baseUrl}${resolvedPath}`;
1429
+ const query = input["query"];
1430
+ if (typeof query === "string" && query.length > 0) {
1431
+ const separator = url.includes("?") ? "&" : "?";
1432
+ url = `${url}${separator}${query}`;
1433
+ }
1434
+ const headers = {
1435
+ "Content-Type": "application/json",
1436
+ "Accept": "application/json",
1437
+ ...authHeaders
1438
+ };
1439
+ const fetchOptions = {
1440
+ method: operation.method,
1441
+ headers,
1442
+ signal: AbortSignal.timeout(timeoutMs)
1443
+ };
1444
+ if (["POST", "PUT", "PATCH"].includes(operation.method)) {
1445
+ const body = input["body"];
1446
+ if (typeof body === "string") {
1447
+ fetchOptions.body = body;
1448
+ } else if (body !== void 0 && body !== null) {
1449
+ fetchOptions.body = JSON.stringify(body);
1450
+ }
1451
+ }
1452
+ const response = await fetch(url, fetchOptions);
1453
+ if (!response.ok) {
1454
+ const errorText = await response.text().catch(() => "Unknown error");
1455
+ throw new Error(`HTTP ${response.status} ${response.statusText}: ${errorText.slice(0, 500)}`);
1456
+ }
1457
+ const contentType = response.headers.get("content-type") ?? "";
1458
+ if (contentType.includes("application/json")) {
1459
+ return response.json();
1460
+ }
1461
+ return response.text();
1462
+ }
1463
+ function sanitizeToolName(name) {
1464
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 64);
1465
+ }
1466
+ function extractBaseUrl(toolDef) {
1467
+ for (const op of toolDef.operations) {
1468
+ if (op.endpoint.startsWith("http")) {
1469
+ try {
1470
+ const url = new URL(op.endpoint);
1471
+ return `${url.protocol}//${url.host}`;
1472
+ } catch {
1473
+ }
1474
+ }
1475
+ }
1476
+ const urlMatch = toolDef.doc.body.match(/(?:base[_ ]?url|api[_ ]?url|endpoint)\s*[:=]\s*`?(https?:\/\/[^\s`"']+)/i);
1477
+ if (urlMatch) {
1478
+ try {
1479
+ const url = new URL(urlMatch[1]);
1480
+ return `${url.protocol}//${url.host}`;
1481
+ } catch {
1482
+ }
1483
+ }
1484
+ return "";
1485
+ }
1486
+ function buildAuthHeaders(toolDef) {
1487
+ const headers = {};
1488
+ for (const auth of toolDef.auth) {
1489
+ const value = process.env[auth.envVar];
1490
+ if (!value) continue;
1491
+ const envLower = auth.envVar.toLowerCase();
1492
+ if (envLower.includes("bot_token")) {
1493
+ headers["Authorization"] = `Bot ${value}`;
1494
+ } else if (envLower.includes("token") || envLower.includes("api_key") || envLower.includes("apikey")) {
1495
+ headers["Authorization"] = `Bearer ${value}`;
1496
+ } else {
1497
+ headers["Authorization"] = `Bearer ${value}`;
1498
+ }
1499
+ }
1500
+ return headers;
1501
+ }
1502
+ function convertToolDefinition(toolDef, config) {
1503
+ const tools = {};
1504
+ const baseUrl = extractBaseUrl(toolDef);
1505
+ const allowHttp = config.allowHttpExecution !== false;
1506
+ const timeoutMs = config.toolTimeoutMs ?? 3e4;
1507
+ for (const operation of toolDef.operations) {
1508
+ const toolName = sanitizeToolName(`${toolDef.id}_${operation.name}`);
1509
+ const opSchema = buildOperationSchema(operation);
1510
+ tools[toolName] = aiTool({
1511
+ description: `[${toolDef.id}] ${operation.method} ${operation.endpoint} \u2014 ${toolDef.doc.l0}`,
1512
+ inputSchema: jsonSchema(opSchema),
1513
+ execute: async (input) => {
1514
+ const typedInput = input;
1515
+ if (!allowHttp) {
1516
+ return { error: "HTTP tool execution is disabled" };
1517
+ }
1518
+ const missingAuth = toolDef.auth.filter((a) => !process.env[a.envVar]);
1519
+ if (missingAuth.length > 0) {
1520
+ return {
1521
+ error: `Missing required auth: ${missingAuth.map((a) => a.envVar).join(", ")}`
1522
+ };
1523
+ }
1524
+ const authHeaders = buildAuthHeaders(toolDef);
1525
+ try {
1526
+ const result = await executeHttpOperation(
1527
+ operation,
1528
+ baseUrl,
1529
+ authHeaders,
1530
+ typedInput,
1531
+ timeoutMs
1532
+ );
1533
+ return result;
1534
+ } catch (err) {
1535
+ const message = err instanceof Error ? err.message : String(err);
1536
+ log.error(`Tool ${toolName} execution failed: ${message}`);
1537
+ return { error: message };
1538
+ }
1539
+ }
1540
+ });
1541
+ }
1542
+ return tools;
1543
+ }
1544
+ function convertProgrammaticTool(pt) {
1545
+ const toolName = sanitizeToolName(pt.name);
1546
+ return {
1547
+ [toolName]: aiTool({
1548
+ description: pt.description,
1549
+ inputSchema: pt.inputSchema,
1550
+ execute: async (input) => {
1551
+ try {
1552
+ return await pt.execute(input);
1553
+ } catch (err) {
1554
+ const message = err instanceof Error ? err.message : String(err);
1555
+ log.error(`Tool ${toolName} execution failed: ${message}`);
1556
+ return { error: message };
1557
+ }
1558
+ }
1559
+ })
1560
+ };
1561
+ }
1562
+ function buildToolSet(harnessDir, config, mcpTools) {
1563
+ const executorConfig = config ?? {};
1564
+ const tools = {};
1565
+ const toolDefs = loadTools(harnessDir);
1566
+ for (const toolDef of toolDefs) {
1567
+ if (toolDef.status !== "active") continue;
1568
+ if (toolDef.operations.length === 0) continue;
1569
+ const converted = convertToolDefinition(toolDef, executorConfig);
1570
+ Object.assign(tools, converted);
1571
+ }
1572
+ if (executorConfig.tools) {
1573
+ for (const pt of executorConfig.tools) {
1574
+ const converted = convertProgrammaticTool(pt);
1575
+ Object.assign(tools, converted);
1576
+ }
1577
+ }
1578
+ if (mcpTools) {
1579
+ Object.assign(tools, mcpTools);
1580
+ }
1581
+ return tools;
1582
+ }
1583
+ function createToolCallTracker() {
1584
+ const calls = [];
1585
+ let totalDurationMs = 0;
1586
+ return {
1587
+ record(result) {
1588
+ calls.push(result);
1589
+ totalDurationMs += result.durationMs;
1590
+ },
1591
+ getRecord() {
1592
+ return { calls: [...calls], totalDurationMs };
1593
+ }
1594
+ };
1595
+ }
1596
+ function getToolSetSummary(tools) {
1597
+ return Object.entries(tools).map(([name, t]) => {
1598
+ const desc = t.description ?? "";
1599
+ return `${name}: ${desc}`;
1600
+ });
1601
+ }
1602
+
1603
+ // src/runtime/mcp.ts
1604
+ import { createMCPClient } from "@ai-sdk/mcp";
1605
+ import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
1606
+ function buildClientConfig(name, serverConfig) {
1607
+ switch (serverConfig.transport) {
1608
+ case "stdio": {
1609
+ if (!serverConfig.command) {
1610
+ throw new Error(`MCP server "${name}": stdio transport requires "command" field`);
1611
+ }
1612
+ const stderr = getGlobalLogLevel() === "debug" ? "inherit" : "pipe";
1613
+ return {
1614
+ transport: new Experimental_StdioMCPTransport({
1615
+ command: serverConfig.command,
1616
+ args: serverConfig.args,
1617
+ env: serverConfig.env ? { ...process.env, ...serverConfig.env } : void 0,
1618
+ cwd: serverConfig.cwd,
1619
+ stderr
1620
+ }),
1621
+ name: `harness-mcp-${name}`
1622
+ };
1623
+ }
1624
+ case "http": {
1625
+ if (!serverConfig.url) {
1626
+ throw new Error(`MCP server "${name}": http transport requires "url" field`);
1627
+ }
1628
+ return {
1629
+ transport: {
1630
+ type: "http",
1631
+ url: serverConfig.url,
1632
+ headers: serverConfig.headers
1633
+ },
1634
+ name: `harness-mcp-${name}`
1635
+ };
1636
+ }
1637
+ case "sse": {
1638
+ if (!serverConfig.url) {
1639
+ throw new Error(`MCP server "${name}": sse transport requires "url" field`);
1640
+ }
1641
+ return {
1642
+ transport: {
1643
+ type: "sse",
1644
+ url: serverConfig.url,
1645
+ headers: serverConfig.headers
1646
+ },
1647
+ name: `harness-mcp-${name}`
1648
+ };
1649
+ }
1650
+ default:
1651
+ throw new Error(`MCP server "${name}": unknown transport "${serverConfig.transport}"`);
1652
+ }
1653
+ }
1654
+ async function connectToServer(name, serverConfig) {
1655
+ const clientConfig = buildClientConfig(name, serverConfig);
1656
+ const client = await createMCPClient(clientConfig);
1657
+ const tools = await client.tools();
1658
+ const toolCount = Object.keys(tools).length;
1659
+ return { name, client, toolCount, tools };
1660
+ }
1661
+ function createMcpManager(config) {
1662
+ const servers = config.mcp?.servers ?? {};
1663
+ const connections = [];
1664
+ const summaries = [];
1665
+ return {
1666
+ hasServers() {
1667
+ return Object.keys(servers).length > 0;
1668
+ },
1669
+ async connect() {
1670
+ const entries = Object.entries(servers);
1671
+ if (entries.length === 0) {
1672
+ return;
1673
+ }
1674
+ log.info(`Connecting to ${entries.length} MCP server(s)...`);
1675
+ for (const [name, serverConfig] of entries) {
1676
+ const enabled = serverConfig.enabled !== false;
1677
+ if (!enabled) {
1678
+ summaries.push({
1679
+ name,
1680
+ transport: serverConfig.transport,
1681
+ enabled: false,
1682
+ connected: false,
1683
+ toolCount: 0,
1684
+ toolNames: []
1685
+ });
1686
+ log.debug(`MCP server "${name}" is disabled, skipping`);
1687
+ continue;
1688
+ }
1689
+ try {
1690
+ const connection = await connectToServer(name, serverConfig);
1691
+ connections.push(connection);
1692
+ const toolNames = Object.keys(connection.tools);
1693
+ summaries.push({
1694
+ name,
1695
+ transport: serverConfig.transport,
1696
+ enabled: true,
1697
+ connected: true,
1698
+ toolCount: connection.toolCount,
1699
+ toolNames
1700
+ });
1701
+ log.info(`MCP "${name}": connected, ${connection.toolCount} tool(s) [${toolNames.join(", ")}]`);
1702
+ } catch (err) {
1703
+ const message = err instanceof Error ? err.message : String(err);
1704
+ summaries.push({
1705
+ name,
1706
+ transport: serverConfig.transport,
1707
+ enabled: true,
1708
+ connected: false,
1709
+ toolCount: 0,
1710
+ toolNames: [],
1711
+ error: message
1712
+ });
1713
+ log.warn(`MCP "${name}": connection failed \u2014 ${message}`);
1714
+ }
1715
+ }
1716
+ const totalTools = connections.reduce((sum, c) => sum + c.toolCount, 0);
1717
+ if (totalTools > 0) {
1718
+ log.info(`MCP: ${totalTools} tool(s) loaded from ${connections.length} server(s)`);
1719
+ }
1720
+ },
1721
+ getTools() {
1722
+ const merged = {};
1723
+ for (const connection of connections) {
1724
+ Object.assign(merged, connection.tools);
1725
+ }
1726
+ return merged;
1727
+ },
1728
+ getSummaries() {
1729
+ const serverNames = Object.keys(servers);
1730
+ const checkedNames = new Set(summaries.map((s) => s.name));
1731
+ for (const name of serverNames) {
1732
+ if (!checkedNames.has(name)) {
1733
+ const serverConfig = servers[name];
1734
+ summaries.push({
1735
+ name,
1736
+ transport: serverConfig.transport,
1737
+ enabled: serverConfig.enabled !== false,
1738
+ connected: false,
1739
+ toolCount: 0,
1740
+ toolNames: []
1741
+ });
1742
+ }
1743
+ }
1744
+ return [...summaries];
1745
+ },
1746
+ async close() {
1747
+ const closePromises = connections.map(async (connection) => {
1748
+ try {
1749
+ await connection.client.close();
1750
+ log.debug(`MCP "${connection.name}": closed`);
1751
+ } catch (err) {
1752
+ const message = err instanceof Error ? err.message : String(err);
1753
+ log.warn(`MCP "${connection.name}": close failed \u2014 ${message}`);
1754
+ }
1755
+ });
1756
+ await Promise.all(closePromises);
1757
+ connections.length = 0;
1758
+ }
1759
+ };
1760
+ }
1761
+ async function loadMcpTools(config) {
1762
+ const manager = createMcpManager(config);
1763
+ await manager.connect();
1764
+ return {
1765
+ tools: manager.getTools(),
1766
+ summaries: manager.getSummaries(),
1767
+ close: () => manager.close()
1768
+ };
1769
+ }
1770
+ function validateMcpConfig(config) {
1771
+ const errors = [];
1772
+ const servers = config.mcp?.servers ?? {};
1773
+ for (const [name, serverConfig] of Object.entries(servers)) {
1774
+ if (serverConfig.transport === "stdio") {
1775
+ if (!serverConfig.command) {
1776
+ errors.push({ server: name, error: 'stdio transport requires "command" field' });
1777
+ }
1778
+ } else if (serverConfig.transport === "http" || serverConfig.transport === "sse") {
1779
+ if (!serverConfig.url) {
1780
+ errors.push({ server: name, error: `${serverConfig.transport} transport requires "url" field` });
1781
+ }
1782
+ } else {
1783
+ errors.push({ server: name, error: `unknown transport "${serverConfig.transport}"` });
1784
+ }
1785
+ }
1786
+ return errors;
1787
+ }
1788
+
1789
+ // src/core/harness.ts
1790
+ function createHarness(options) {
1791
+ const dir = resolve(options.dir);
1792
+ if (!existsSync11(dir)) {
1793
+ throw new Error(`Harness directory not found: ${dir}`);
1794
+ }
1795
+ const config = loadConfig(dir, options.config);
1796
+ if (options.model) {
1797
+ config.model = { ...config.model, id: options.model };
1798
+ }
1799
+ if (options.provider) {
1800
+ config.model = { ...config.model, provider: options.provider };
1801
+ }
1802
+ const model = getModel(config, options.apiKey);
1803
+ const hooks = options.hooks ?? {};
1804
+ let state;
1805
+ let systemPrompt;
1806
+ let booted = false;
1807
+ let toolSet = {};
1808
+ let mcpManager;
1809
+ const agent = {
1810
+ name: config.agent.name,
1811
+ config,
1812
+ async boot() {
1813
+ state = loadState(dir);
1814
+ const previousMode = state.mode;
1815
+ state.mode = "active";
1816
+ state.last_interaction = (/* @__PURE__ */ new Date()).toISOString();
1817
+ const ctx = buildSystemPrompt(dir, config);
1818
+ systemPrompt = ctx.systemPrompt;
1819
+ let mcpTools = {};
1820
+ mcpManager = createMcpManager(config);
1821
+ if (mcpManager.hasServers()) {
1822
+ try {
1823
+ await mcpManager.connect();
1824
+ mcpTools = mcpManager.getTools();
1825
+ } catch (err) {
1826
+ log.warn(`MCP connection failed during boot: ${err instanceof Error ? err.message : String(err)}. Continuing without MCP tools.`);
1827
+ }
1828
+ }
1829
+ toolSet = buildToolSet(dir, options.toolExecutor, mcpTools);
1830
+ const toolCount = Object.keys(toolSet).length;
1831
+ booted = true;
1832
+ log.info(
1833
+ `Booted "${config.agent.name}" | ${ctx.budget.loaded_files.length} files loaded | ~${ctx.budget.used_tokens} tokens used | ${ctx.budget.remaining} remaining` + (toolCount > 0 ? ` | ${toolCount} tools` : "")
1834
+ );
1835
+ for (const warning of ctx.warnings) {
1836
+ log.warn(warning);
1837
+ }
1838
+ if (previousMode !== "active" && hooks.onStateChange) {
1839
+ await hooks.onStateChange({ agent, previous: previousMode, current: "active" });
1840
+ }
1841
+ try {
1842
+ recordBoot(dir);
1843
+ } catch {
1844
+ }
1845
+ if (hooks.onBoot) {
1846
+ await hooks.onBoot({ agent, config, state });
1847
+ }
1848
+ },
1849
+ async run(prompt) {
1850
+ if (!booted) await agent.boot();
1851
+ const guard = checkGuardrails(dir, config);
1852
+ if (!guard.allowed) {
1853
+ const error = new Error(`Guardrail blocked: ${guard.reason}`);
1854
+ try {
1855
+ recordFailure(dir, error.message);
1856
+ } catch {
1857
+ }
1858
+ if (hooks.onError) {
1859
+ try {
1860
+ await hooks.onError({ agent, error, prompt });
1861
+ } catch {
1862
+ }
1863
+ }
1864
+ throw error;
1865
+ }
1866
+ const sessionId = createSessionId();
1867
+ const started = (/* @__PURE__ */ new Date()).toISOString();
1868
+ const hasTools = Object.keys(toolSet).length > 0;
1869
+ let result;
1870
+ try {
1871
+ result = await generate({
1872
+ model,
1873
+ system: systemPrompt,
1874
+ prompt,
1875
+ maxRetries: config.model.max_retries,
1876
+ timeoutMs: config.model.timeout_ms,
1877
+ ...hasTools ? { tools: toolSet, maxToolSteps: options.toolExecutor?.maxToolCalls ?? 5 } : {}
1878
+ });
1879
+ } catch (err) {
1880
+ const error = err instanceof Error ? err : new Error(String(err));
1881
+ try {
1882
+ recordFailure(dir, error.message);
1883
+ } catch {
1884
+ }
1885
+ if (hooks.onError) {
1886
+ try {
1887
+ await hooks.onError({ agent, error, prompt });
1888
+ } catch {
1889
+ }
1890
+ }
1891
+ throw error;
1892
+ }
1893
+ const ended = (/* @__PURE__ */ new Date()).toISOString();
1894
+ const session = {
1895
+ id: sessionId,
1896
+ started,
1897
+ ended,
1898
+ prompt,
1899
+ summary: result.text.slice(0, 200),
1900
+ tokens_used: result.usage.totalTokens,
1901
+ steps: result.steps,
1902
+ model_id: config.model.id,
1903
+ tool_calls: result.toolCalls.length > 0 ? result.toolCalls : void 0
1904
+ };
1905
+ try {
1906
+ writeSession(dir, session);
1907
+ } catch (err) {
1908
+ log.warn(`Failed to write session ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
1909
+ }
1910
+ try {
1911
+ recordCost(dir, {
1912
+ model_id: config.model.id,
1913
+ provider: config.model.provider ?? "openrouter",
1914
+ input_tokens: result.usage.inputTokens,
1915
+ output_tokens: result.usage.outputTokens,
1916
+ source: `run:${sessionId}`
1917
+ });
1918
+ } catch (err) {
1919
+ log.warn(`Failed to record cost: ${err instanceof Error ? err.message : String(err)}`);
1920
+ }
1921
+ try {
1922
+ recordSuccess(dir);
1923
+ } catch (err) {
1924
+ log.warn(`Failed to record health: ${err instanceof Error ? err.message : String(err)}`);
1925
+ }
1926
+ try {
1927
+ state.last_interaction = ended;
1928
+ saveState(dir, state);
1929
+ } catch (err) {
1930
+ log.warn(`Failed to save state: ${err instanceof Error ? err.message : String(err)}`);
1931
+ }
1932
+ const runResult = {
1933
+ text: result.text,
1934
+ usage: result.usage,
1935
+ session_id: sessionId,
1936
+ steps: result.steps,
1937
+ toolCalls: result.toolCalls
1938
+ };
1939
+ if (hooks.onSessionEnd) {
1940
+ try {
1941
+ await hooks.onSessionEnd({ agent, sessionId, prompt, result: runResult });
1942
+ } catch (err) {
1943
+ log.warn(`onSessionEnd hook error: ${err instanceof Error ? err.message : String(err)}`);
1944
+ }
1945
+ }
1946
+ return runResult;
1947
+ },
1948
+ stream(prompt) {
1949
+ const sessionId = createSessionId();
1950
+ let resolveResult;
1951
+ let rejectResult;
1952
+ const resultPromise = new Promise((res, rej) => {
1953
+ resolveResult = res;
1954
+ rejectResult = rej;
1955
+ });
1956
+ resultPromise.catch(() => {
1957
+ });
1958
+ async function* generateStream() {
1959
+ if (!booted) await agent.boot();
1960
+ const guard = checkGuardrails(dir, config);
1961
+ if (!guard.allowed) {
1962
+ const error = new Error(`Guardrail blocked: ${guard.reason}`);
1963
+ try {
1964
+ recordFailure(dir, error.message);
1965
+ } catch {
1966
+ }
1967
+ if (hooks.onError) {
1968
+ try {
1969
+ await hooks.onError({ agent, error, prompt });
1970
+ } catch {
1971
+ }
1972
+ }
1973
+ rejectResult(error);
1974
+ throw error;
1975
+ }
1976
+ const started = (/* @__PURE__ */ new Date()).toISOString();
1977
+ let fullText = "";
1978
+ const hasTools = Object.keys(toolSet).length > 0;
1979
+ let streamResult;
1980
+ try {
1981
+ streamResult = streamGenerateWithDetails({
1982
+ model,
1983
+ system: systemPrompt,
1984
+ prompt,
1985
+ maxRetries: config.model.max_retries,
1986
+ timeoutMs: config.model.timeout_ms,
1987
+ ...hasTools ? { tools: toolSet, maxToolSteps: options.toolExecutor?.maxToolCalls ?? 5 } : {}
1988
+ });
1989
+ } catch (err) {
1990
+ const error = err instanceof Error ? err : new Error(String(err));
1991
+ try {
1992
+ recordFailure(dir, error.message);
1993
+ } catch {
1994
+ }
1995
+ if (hooks.onError) {
1996
+ try {
1997
+ await hooks.onError({ agent, error, prompt });
1998
+ } catch {
1999
+ }
2000
+ }
2001
+ rejectResult(error);
2002
+ throw error;
2003
+ }
2004
+ try {
2005
+ for await (const chunk of streamResult.textStream) {
2006
+ fullText += chunk;
2007
+ yield chunk;
2008
+ }
2009
+ } catch (err) {
2010
+ const error = err instanceof Error ? err : new Error(String(err));
2011
+ try {
2012
+ recordFailure(dir, error.message);
2013
+ } catch {
2014
+ }
2015
+ if (hooks.onError) {
2016
+ try {
2017
+ await hooks.onError({ agent, error, prompt });
2018
+ } catch {
2019
+ }
2020
+ }
2021
+ rejectResult(error);
2022
+ throw error;
2023
+ }
2024
+ let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
2025
+ let steps = 1;
2026
+ let toolCalls = [];
2027
+ try {
2028
+ [usage, steps, toolCalls] = await Promise.all([
2029
+ streamResult.usage,
2030
+ streamResult.steps,
2031
+ streamResult.toolCalls
2032
+ ]);
2033
+ } catch (err) {
2034
+ log.warn(`Failed to resolve post-stream metadata: ${err instanceof Error ? err.message : String(err)}`);
2035
+ }
2036
+ const ended = (/* @__PURE__ */ new Date()).toISOString();
2037
+ const session = {
2038
+ id: sessionId,
2039
+ started,
2040
+ ended,
2041
+ prompt,
2042
+ summary: fullText.slice(0, 200),
2043
+ tokens_used: usage.totalTokens,
2044
+ steps,
2045
+ model_id: config.model.id,
2046
+ tool_calls: toolCalls.length > 0 ? toolCalls : void 0
2047
+ };
2048
+ try {
2049
+ writeSession(dir, session);
2050
+ } catch (err) {
2051
+ log.warn(`Failed to write session ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
2052
+ }
2053
+ try {
2054
+ recordCost(dir, {
2055
+ model_id: config.model.id,
2056
+ provider: config.model.provider ?? "openrouter",
2057
+ input_tokens: usage.inputTokens,
2058
+ output_tokens: usage.outputTokens,
2059
+ source: `stream:${sessionId}`
2060
+ });
2061
+ } catch (err) {
2062
+ log.warn(`Failed to record cost: ${err instanceof Error ? err.message : String(err)}`);
2063
+ }
2064
+ try {
2065
+ recordSuccess(dir);
2066
+ } catch (err) {
2067
+ log.warn(`Failed to record health: ${err instanceof Error ? err.message : String(err)}`);
2068
+ }
2069
+ try {
2070
+ state.last_interaction = ended;
2071
+ saveState(dir, state);
2072
+ } catch (err) {
2073
+ log.warn(`Failed to save state: ${err instanceof Error ? err.message : String(err)}`);
2074
+ }
2075
+ const runResult = {
2076
+ text: fullText,
2077
+ usage,
2078
+ session_id: sessionId,
2079
+ steps,
2080
+ toolCalls
2081
+ };
2082
+ if (hooks.onSessionEnd) {
2083
+ try {
2084
+ await hooks.onSessionEnd({ agent, sessionId, prompt, result: runResult });
2085
+ } catch (err) {
2086
+ log.warn(`onSessionEnd hook error: ${err instanceof Error ? err.message : String(err)}`);
2087
+ }
2088
+ }
2089
+ resolveResult(runResult);
2090
+ }
2091
+ return {
2092
+ textStream: generateStream(),
2093
+ result: resultPromise
2094
+ };
2095
+ },
2096
+ async shutdown() {
2097
+ if (!booted) return;
2098
+ if (hooks.onShutdown) {
2099
+ try {
2100
+ await hooks.onShutdown({ agent, state });
2101
+ } catch (err) {
2102
+ log.warn(`onShutdown hook error: ${err instanceof Error ? err.message : String(err)}`);
2103
+ }
2104
+ }
2105
+ if (mcpManager) {
2106
+ try {
2107
+ await mcpManager.close();
2108
+ } catch (err) {
2109
+ log.warn(`MCP shutdown error: ${err instanceof Error ? err.message : String(err)}`);
2110
+ }
2111
+ mcpManager = void 0;
2112
+ }
2113
+ const previousMode = state.mode;
2114
+ state.mode = "idle";
2115
+ try {
2116
+ saveState(dir, state);
2117
+ } catch (err) {
2118
+ log.warn(`Failed to save state during shutdown: ${err instanceof Error ? err.message : String(err)}`);
2119
+ }
2120
+ booted = false;
2121
+ if (previousMode !== "idle" && hooks.onStateChange) {
2122
+ try {
2123
+ await hooks.onStateChange({ agent, previous: previousMode, current: "idle" });
2124
+ } catch (err) {
2125
+ log.warn(`onStateChange hook error: ${err instanceof Error ? err.message : String(err)}`);
2126
+ }
2127
+ }
2128
+ log.info(`Shutdown "${config.agent.name}"`);
2129
+ },
2130
+ getSystemPrompt() {
2131
+ return systemPrompt || "";
2132
+ },
2133
+ getState() {
2134
+ return state || loadState(dir);
2135
+ }
2136
+ };
2137
+ return agent;
2138
+ }
2139
+
2140
+ export {
2141
+ loadConfig,
2142
+ writeDefaultConfig,
2143
+ createLogger,
2144
+ setGlobalLogLevel,
2145
+ getGlobalLogLevel,
2146
+ log,
2147
+ parseHarnessDocument,
2148
+ loadDirectory,
2149
+ loadDirectoryWithErrors,
2150
+ loadAllPrimitives,
2151
+ loadAllPrimitivesWithErrors,
2152
+ estimateTokens,
2153
+ getAtLevel,
2154
+ buildSystemPrompt,
2155
+ tryLock,
2156
+ releaseLock,
2157
+ acquireLock,
2158
+ withFileLock,
2159
+ withFileLockSync,
2160
+ isLocked,
2161
+ breakLock,
2162
+ loadState,
2163
+ saveState,
2164
+ createSessionId,
2165
+ writeSession,
2166
+ archiveOldFiles,
2167
+ cleanupOldFiles,
2168
+ listSessions,
2169
+ listExpiredFiles,
2170
+ loadCosts,
2171
+ saveCosts,
2172
+ findPricing,
2173
+ calculateCost,
2174
+ recordCost,
2175
+ getSpending,
2176
+ checkBudget,
2177
+ clearCosts,
2178
+ loadHealth,
2179
+ saveHealth,
2180
+ recordSuccess,
2181
+ recordFailure,
2182
+ recordBoot,
2183
+ getHealthStatus,
2184
+ resetHealth,
2185
+ loadRateLimits,
2186
+ saveRateLimits,
2187
+ checkRateLimit,
2188
+ recordEvent,
2189
+ tryAcquire,
2190
+ getUsage,
2191
+ clearRateLimits,
2192
+ buildRateLimits,
2193
+ checkGuardrails,
2194
+ parseToolDefinition,
2195
+ loadTools,
2196
+ getToolById,
2197
+ listToolSummaries,
2198
+ checkToolAuth,
2199
+ resolveEndpoint,
2200
+ buildOperationSchema,
2201
+ executeHttpOperation,
2202
+ buildAuthHeaders,
2203
+ convertToolDefinition,
2204
+ buildToolSet,
2205
+ createToolCallTracker,
2206
+ getToolSetSummary,
2207
+ createMcpManager,
2208
+ loadMcpTools,
2209
+ validateMcpConfig,
2210
+ createHarness
2211
+ };
2212
+ //# sourceMappingURL=chunk-GUJTBGVS.js.map