@gajae-code/coding-agent 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/dist/types/async/job-manager.d.ts +4 -1
  3. package/dist/types/autoresearch/dashboard.d.ts +29 -4
  4. package/dist/types/autoresearch/data-context.d.ts +25 -0
  5. package/dist/types/autoresearch/git.d.ts +65 -7
  6. package/dist/types/autoresearch/harness.d.ts +58 -0
  7. package/dist/types/autoresearch/prompts.d.ts +51 -0
  8. package/dist/types/autoresearch/runs.d.ts +173 -0
  9. package/dist/types/autoresearch/types.d.ts +9 -139
  10. package/dist/types/capability/types.d.ts +20 -0
  11. package/dist/types/cli/accounts-cli.d.ts +1 -0
  12. package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
  13. package/dist/types/config/autorouting-contract.d.ts +146 -0
  14. package/dist/types/config/autorouting-generator.d.ts +40 -0
  15. package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
  16. package/dist/types/config/autorouting.d.ts +35 -0
  17. package/dist/types/config/model-discovery-manager.d.ts +8 -1
  18. package/dist/types/config/model-registry.d.ts +30 -1
  19. package/dist/types/config/provider-selection-policy.d.ts +23 -0
  20. package/dist/types/config/settings-schema.d.ts +61 -3
  21. package/dist/types/config/settings.d.ts +3 -0
  22. package/dist/types/coordinator/contract.d.ts +1 -1
  23. package/dist/types/coordinator-mcp/durability.d.ts +29 -0
  24. package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
  26. package/dist/types/coordinator-mcp/server.d.ts +14 -0
  27. package/dist/types/crash/index-store.d.ts +3 -0
  28. package/dist/types/crash/record-loader.d.ts +2 -0
  29. package/dist/types/crash/upstream/relay.d.ts +5 -3
  30. package/dist/types/cursor.d.ts +12 -0
  31. package/dist/types/defaults/gjc-defaults.d.ts +35 -1
  32. package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
  33. package/dist/types/discovery/helpers.d.ts +1 -1
  34. package/dist/types/edit/index.d.ts +2 -1
  35. package/dist/types/eval/py/executor.d.ts +2 -0
  36. package/dist/types/extensibility/extensions/loader.d.ts +32 -0
  37. package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
  40. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  41. package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
  42. package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
  44. package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
  45. package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
  46. package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
  47. package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
  49. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
  50. package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
  51. package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
  52. package/dist/types/hooks/skill-keywords.d.ts +1 -1
  53. package/dist/types/main.d.ts +0 -10
  54. package/dist/types/modes/acp/acp-agent.d.ts +13 -6
  55. package/dist/types/modes/components/model-selector.d.ts +14 -0
  56. package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
  57. package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
  58. package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/types.d.ts +9 -0
  61. package/dist/types/rlm/artifacts.d.ts +4 -4
  62. package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
  63. package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
  64. package/dist/types/runtime-mcp/config.d.ts +6 -0
  65. package/dist/types/runtime-mcp/manager.d.ts +10 -0
  66. package/dist/types/sdk/acp/adapter.d.ts +0 -4
  67. package/dist/types/sdk/broker/broker.d.ts +1 -1
  68. package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
  69. package/dist/types/sdk/broker/session-index.d.ts +15 -0
  70. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
  71. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
  72. package/dist/types/sdk/bus/index.d.ts +8 -0
  73. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
  74. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
  75. package/dist/types/sdk/client/client.d.ts +39 -4
  76. package/dist/types/sdk/host/host.d.ts +1 -0
  77. package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
  78. package/dist/types/sdk/host/model-pin.d.ts +29 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +2 -1
  80. package/dist/types/sdk/lifecycle-session.d.ts +13 -0
  81. package/dist/types/sdk/router/session-router.d.ts +27 -1
  82. package/dist/types/sdk/session.d.ts +11 -1
  83. package/dist/types/session/agent-session.d.ts +36 -6
  84. package/dist/types/session/artifacts.d.ts +16 -1
  85. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  86. package/dist/types/session/session-manager.d.ts +84 -2
  87. package/dist/types/session/session-staging-paths.d.ts +3 -0
  88. package/dist/types/skill-state/canonical-skills.d.ts +1 -1
  89. package/dist/types/skill-state/workflow-hud.d.ts +12 -21
  90. package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
  91. package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
  92. package/dist/types/system-prompt.d.ts +23 -0
  93. package/dist/types/task/executor.d.ts +35 -4
  94. package/dist/types/task/index.d.ts +17 -3
  95. package/dist/types/task/receipt.d.ts +3 -1
  96. package/dist/types/task/types.d.ts +144 -0
  97. package/dist/types/tools/atomic-file-write.d.ts +72 -0
  98. package/dist/types/tools/descriptors.d.ts +1 -0
  99. package/dist/types/tools/index.d.ts +36 -1
  100. package/dist/types/tools/move-session.d.ts +40 -0
  101. package/dist/types/tools/python.d.ts +12 -0
  102. package/dist/types/tools/resource-gc.d.ts +0 -3
  103. package/dist/types/tools/search.d.ts +8 -1
  104. package/dist/types/tools/skill.d.ts +9 -4
  105. package/dist/types/tools/subagent.d.ts +33 -3
  106. package/dist/types/utils/xml-escape.d.ts +2 -0
  107. package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
  108. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
  109. package/package.json +11 -12
  110. package/scripts/build-sdk-package-smoke.ts +7 -5
  111. package/scripts/check-autorouting-tier-map.ts +183 -0
  112. package/scripts/generate-sdk-operation-inventory.ts +12 -0
  113. package/scripts/generate-tool-catalog.ts +40 -5
  114. package/src/async/job-manager.ts +5 -2
  115. package/src/autoresearch/dashboard.ts +134 -314
  116. package/src/autoresearch/data-context.ts +35 -0
  117. package/src/autoresearch/git.ts +186 -26
  118. package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
  119. package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
  120. package/src/autoresearch/prompts/prompt.md +103 -0
  121. package/src/autoresearch/prompts.ts +126 -0
  122. package/src/autoresearch/runs.ts +708 -0
  123. package/src/autoresearch/types.ts +9 -150
  124. package/src/capability/index.ts +4 -4
  125. package/src/capability/types.ts +20 -0
  126. package/src/cli/accounts-cli.ts +21 -5
  127. package/src/cli/config-cli.ts +25 -1
  128. package/src/cli/fast-help.ts +1 -2
  129. package/src/cli/list-models.ts +5 -8
  130. package/src/cli/setup-cli.ts +8 -0
  131. package/src/cli.ts +2 -21
  132. package/src/commands/autoresearch.ts +33 -0
  133. package/src/commands/coordinator.ts +15 -0
  134. package/src/commands/launch.ts +2 -4
  135. package/src/commands/quick-lane.ts +1 -1
  136. package/src/commands/sdk.ts +24 -0
  137. package/src/commands/state.ts +1 -1
  138. package/src/config/autorouting-contract.ts +474 -0
  139. package/src/config/autorouting-generator.ts +238 -0
  140. package/src/config/autorouting-tier-map.ts +6108 -0
  141. package/src/config/autorouting.ts +120 -0
  142. package/src/config/model-discovery-manager.ts +42 -11
  143. package/src/config/model-registry.ts +348 -68
  144. package/src/config/provider-selection-policy.ts +60 -9
  145. package/src/config/settings-schema.ts +195 -6
  146. package/src/config/settings.ts +62 -1
  147. package/src/config.ts +2 -3
  148. package/src/coordinator/contract.ts +0 -1
  149. package/src/coordinator-mcp/codex-handoff.ts +68 -42
  150. package/src/coordinator-mcp/durability.ts +200 -0
  151. package/src/coordinator-mcp/event-webhook.ts +278 -0
  152. package/src/coordinator-mcp/question-state.ts +109 -46
  153. package/src/coordinator-mcp/server.ts +932 -504
  154. package/src/crash/index-store.ts +126 -22
  155. package/src/crash/record-loader.ts +7 -0
  156. package/src/crash/upstream/relay.ts +82 -34
  157. package/src/cursor.ts +84 -1
  158. package/src/debug/index.ts +26 -0
  159. package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
  160. package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
  161. package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
  162. package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
  163. package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
  164. package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
  165. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
  166. package/src/defaults/gjc-defaults.ts +89 -2
  167. package/src/defaults/gjc-skills.generated.ts +24 -17
  168. package/src/discovery/builtin.ts +18 -5
  169. package/src/discovery/helpers.ts +6 -6
  170. package/src/edit/index.ts +5 -2
  171. package/src/eval/py/executor.ts +8 -0
  172. package/src/extensibility/extensions/loader.ts +135 -13
  173. package/src/extensibility/extensions/wrapper.ts +1 -2
  174. package/src/extensibility/gjc-plugins/schema.ts +1 -1
  175. package/src/extensibility/runtime-skill-discovery.ts +2 -2
  176. package/src/extensibility/skill-management.ts +2 -3
  177. package/src/extensibility/skills.ts +16 -8
  178. package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
  179. package/src/gjc-runtime/gc-render.ts +0 -1
  180. package/src/gjc-runtime/gc-runtime.ts +1 -11
  181. package/src/gjc-runtime/launch-tmux.ts +3 -3
  182. package/src/gjc-runtime/psmux-detect.ts +2 -2
  183. package/src/gjc-runtime/python-transcript.ts +58 -0
  184. package/src/gjc-runtime/ralplan-runtime.ts +6 -18
  185. package/src/gjc-runtime/session-layout.ts +18 -7
  186. package/src/gjc-runtime/session-state-sidecar.ts +58 -9
  187. package/src/gjc-runtime/state-renderer.ts +0 -38
  188. package/src/gjc-runtime/state-runtime.ts +43 -22
  189. package/src/gjc-runtime/state-schema.ts +1 -1
  190. package/src/gjc-runtime/tmux-common.ts +7 -7
  191. package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
  192. package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
  193. package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
  194. package/src/gjc-runtime/workflow-command-ref.ts +15 -11
  195. package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
  196. package/src/gjc-runtime/workflow-manifest.ts +43 -83
  197. package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
  198. package/src/hooks/skill-keywords.ts +3 -3
  199. package/src/hooks/skill-state.ts +2 -2
  200. package/src/internal-urls/docs-index.generated.ts +26 -24
  201. package/src/lsp/index.ts +25 -5
  202. package/src/main.ts +2 -27
  203. package/src/modes/acp/acp-agent.ts +200 -75
  204. package/src/modes/components/model-selector.ts +216 -13
  205. package/src/modes/components/oauth-selector.ts +5 -5
  206. package/src/modes/components/skill-hud/render.ts +173 -35
  207. package/src/modes/components/smart-routing-panel.ts +531 -0
  208. package/src/modes/components/tool-status-header.ts +6 -3
  209. package/src/modes/controllers/command-controller.ts +17 -7
  210. package/src/modes/controllers/input-controller.ts +44 -9
  211. package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
  212. package/src/modes/controllers/selector-controller.ts +298 -65
  213. package/src/modes/interactive-mode.ts +8 -1
  214. package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
  215. package/src/modes/types.ts +9 -1
  216. package/src/prompts/agents/planner.md +1 -1
  217. package/src/prompts/system/system-prompt.md +1 -1
  218. package/src/prompts/system/volatile-project-context.md +2 -1
  219. package/src/prompts/tools/move-session.md +15 -0
  220. package/src/prompts/tools/python.md +12 -0
  221. package/src/prompts/tools/skill-discovery.md +1 -1
  222. package/src/prompts/tools/skill.md +2 -2
  223. package/src/prompts/tools/task-summary.md +1 -0
  224. package/src/prompts/tools/task.md +6 -0
  225. package/src/rlm/artifacts.ts +12 -10
  226. package/src/runtime/optional-runtime-services.ts +3 -2
  227. package/src/runtime/workspace-tree-service.ts +11 -3
  228. package/src/runtime-mcp/config.ts +8 -1
  229. package/src/runtime-mcp/manager.ts +33 -0
  230. package/src/sdk/acp/adapter.ts +48 -1
  231. package/src/sdk/broker/broker.ts +30 -2
  232. package/src/sdk/broker/discovery.ts +217 -2
  233. package/src/sdk/broker/lifecycle.ts +385 -59
  234. package/src/sdk/broker/session-index.ts +243 -48
  235. package/src/sdk/broker/transport.ts +1 -0
  236. package/src/sdk/bus/chat-daemon-control.ts +8 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
  238. package/src/sdk/bus/index.ts +208 -11
  239. package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
  240. package/src/sdk/bus/reconciliation-store.ts +27 -5
  241. package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
  242. package/src/sdk/client/client.ts +204 -22
  243. package/src/sdk/host/host.ts +16 -0
  244. package/src/sdk/host/internal-autorouting-state.ts +13 -0
  245. package/src/sdk/host/model-pin.ts +101 -0
  246. package/src/sdk/host/reverse-leases.ts +2 -1
  247. package/src/sdk/host/session-runtime.test.ts +224 -1
  248. package/src/sdk/host/session-runtime.ts +125 -37
  249. package/src/sdk/lifecycle/service.ts +3 -1
  250. package/src/sdk/lifecycle-session.ts +42 -1
  251. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  252. package/src/sdk/router/session-router.ts +375 -36
  253. package/src/sdk/session.ts +547 -47
  254. package/src/session/account-inventory.ts +1 -1
  255. package/src/session/agent-session.ts +765 -189
  256. package/src/session/artifacts.ts +234 -4
  257. package/src/session/internal/managed-session-storage.ts +91 -4
  258. package/src/session/provider-safety-stop-hint.ts +3 -2
  259. package/src/session/session-manager.ts +775 -16
  260. package/src/session/session-staging-paths.ts +8 -0
  261. package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
  262. package/src/setup/host-plugin-setup.ts +2 -2
  263. package/src/skill-state/active-state.ts +2 -2
  264. package/src/skill-state/canonical-skills.ts +1 -1
  265. package/src/skill-state/initial-phase.ts +1 -1
  266. package/src/skill-state/workflow-hud.ts +41 -41
  267. package/src/skill-state/workflow-mutation-guard.ts +26 -7
  268. package/src/slash-commands/builtin-registry.ts +77 -0
  269. package/src/slash-commands/helpers/autorouting-status.ts +55 -0
  270. package/src/system-prompt.ts +102 -5
  271. package/src/task/discovery.ts +2 -3
  272. package/src/task/executor.ts +617 -43
  273. package/src/task/index.ts +262 -15
  274. package/src/task/receipt.ts +30 -0
  275. package/src/task/render.ts +11 -1
  276. package/src/task/types.ts +316 -0
  277. package/src/tools/atomic-file-write.ts +594 -0
  278. package/src/tools/bisect.ts +3 -2
  279. package/src/tools/descriptors.test.ts +3 -0
  280. package/src/tools/descriptors.ts +24 -0
  281. package/src/tools/image-gen.ts +5 -1
  282. package/src/tools/index.ts +62 -5
  283. package/src/tools/move-session.ts +107 -0
  284. package/src/tools/python.ts +205 -0
  285. package/src/tools/read.ts +88 -15
  286. package/src/tools/renderers.ts +2 -0
  287. package/src/tools/resource-gc.ts +8 -177
  288. package/src/tools/search.ts +30 -6
  289. package/src/tools/skill-discovery.ts +1 -0
  290. package/src/tools/skill.ts +37 -7
  291. package/src/tools/subagent-render.ts +119 -13
  292. package/src/tools/subagent.ts +91 -74
  293. package/src/tools/tool-catalog.generated.ts +64 -2
  294. package/src/tools/write.ts +16 -12
  295. package/src/utils/xml-escape.ts +14 -0
  296. package/src/web/search/providers/codex.ts +2 -2
  297. package/src/workflow/workflow-intent-diff.ts +13 -12
  298. package/src/workflow/workflow-intent-report-consumer.ts +6 -6
  299. package/dist/types/autoresearch/helpers.d.ts +0 -24
  300. package/dist/types/autoresearch/index.d.ts +0 -2
  301. package/dist/types/autoresearch/state.d.ts +0 -17
  302. package/dist/types/autoresearch/storage.d.ts +0 -142
  303. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  304. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  305. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  306. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  307. package/dist/types/commands/team.d.ts +0 -24
  308. package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
  309. package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
  310. package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
  311. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
  312. package/dist/types/gjc-runtime/team-store.d.ts +0 -168
  313. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
  314. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
  315. package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
  316. package/dist/types/rlm/index.d.ts +0 -47
  317. package/dist/types/rlm/preset.d.ts +0 -23
  318. package/dist/types/rlm/python-tool.d.ts +0 -16
  319. package/src/autoresearch/command-resume.md +0 -14
  320. package/src/autoresearch/index.ts +0 -536
  321. package/src/autoresearch/prompt.md +0 -103
  322. package/src/autoresearch/resume-message.md +0 -10
  323. package/src/autoresearch/state.ts +0 -273
  324. package/src/autoresearch/storage.ts +0 -699
  325. package/src/autoresearch/tools/init-experiment.ts +0 -272
  326. package/src/autoresearch/tools/log-experiment.ts +0 -524
  327. package/src/autoresearch/tools/run-experiment.ts +0 -475
  328. package/src/autoresearch/tools/update-notes.ts +0 -109
  329. package/src/commands/rlm.ts +0 -19
  330. package/src/commands/team.ts +0 -238
  331. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
  332. package/src/defaults/gjc/skills/team/SKILL.md +0 -467
  333. package/src/gjc-runtime/team-gc.ts +0 -49
  334. package/src/gjc-runtime/team-launch.ts +0 -404
  335. package/src/gjc-runtime/team-notify.ts +0 -326
  336. package/src/gjc-runtime/team-runtime.ts +0 -5923
  337. package/src/gjc-runtime/team-store.ts +0 -878
  338. package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
  339. package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
  340. package/src/gjc-runtime/team-workers.ts +0 -1123
  341. package/src/prompts/system/rlm-report-command.md +0 -1
  342. package/src/prompts/system/rlm-research.md +0 -23
  343. package/src/rlm/index.ts +0 -365
  344. package/src/rlm/preset.ts +0 -76
  345. package/src/rlm/python-tool.ts +0 -68
@@ -1,524 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
-
4
- import { Text } from "@gajae-code/tui";
5
- import * as z from "zod/v4";
6
- import type { ToolDefinition } from "../../extensibility/extensions";
7
- import type { Theme } from "../../modes/theme/theme";
8
- import { replaceTabs, truncateToWidth } from "../../tools/render-utils";
9
- import * as git from "../../utils/git";
10
- import { computeRunModifiedPaths, getCurrentAutoresearchBranch, parseWorkDirDirtyPaths } from "../git";
11
- import {
12
- ensureNumericMetricMap,
13
- formatNum,
14
- mergeAsi,
15
- pathMatchesSpec,
16
- sanitizeAsi,
17
- tryGitPrefix,
18
- tryGitStatus,
19
- } from "../helpers";
20
- import {
21
- buildExperimentState,
22
- computeConfidence,
23
- currentResults,
24
- findBaselineSecondary,
25
- findBestKeptMetric,
26
- } from "../state";
27
- import { openAutoresearchStorageIfExists, type SessionRow } from "../storage";
28
- import type {
29
- ASIData,
30
- AutoresearchToolFactoryOptions,
31
- ExperimentResult,
32
- ExperimentState,
33
- LogDetails,
34
- NumericMetricMap,
35
- } from "../types";
36
-
37
- const EXPERIMENT_TOOL_NAMES = ["init_experiment", "run_experiment", "log_experiment", "update_notes"];
38
-
39
- const logExperimentSchema = z.object({
40
- metric: z.number().describe("primary metric value"),
41
- status: z.enum(["keep", "discard", "crash", "checks_failed"] as const).describe("run outcome"),
42
- description: z.string().describe("short run description"),
43
- metrics: z.record(z.string(), z.number()).describe("secondary metrics").optional(),
44
- asi: z.object({}).passthrough().describe("free-form structured metadata").optional(),
45
- commit: z.string().describe("override recorded commit hash").optional(),
46
- justification: z.string().describe("required when keeping a scope-deviating run").optional(),
47
- flag_runs: z
48
- .array(
49
- z.object({
50
- run_id: z.number().describe("run id to flag"),
51
- reason: z.string().describe("why this run is suspect"),
52
- }),
53
- )
54
- .describe("flag earlier runs as suspect")
55
- .optional(),
56
- });
57
-
58
- export function createLogExperimentTool(
59
- options: AutoresearchToolFactoryOptions,
60
- ): ToolDefinition<typeof logExperimentSchema, LogDetails> {
61
- return {
62
- name: "log_experiment",
63
- label: "Log Experiment",
64
- description:
65
- "Log the result of the latest run_experiment. Records the metric, optional ASI metadata, modified paths, and scope deviations. On `keep`, modified files are committed only on a dedicated `autoresearch/*` branch; otherwise the tool warns and leaves them in the worktree. On `discard`/`crash`/`checks_failed`, the worktree is reverted. Pass `flag_runs` to mark earlier runs as suspect; flagged runs are excluded from baseline and best-metric math.",
66
- parameters: logExperimentSchema,
67
- defaultInactive: true,
68
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
69
- const storage = await openAutoresearchStorageIfExists(ctx.cwd);
70
- const currentBranch = (await git.branch.current(ctx.cwd)) ?? null;
71
- const session = storage?.getActiveSessionForBranch(currentBranch) ?? null;
72
- if (!storage || !session) {
73
- return {
74
- content: [
75
- {
76
- type: "text",
77
- text: "Error: no active autoresearch session for the current branch. Call init_experiment first.",
78
- },
79
- ],
80
- };
81
- }
82
- const pendingRun = storage.getPendingRun(session.id);
83
- if (!pendingRun) {
84
- return {
85
- content: [{ type: "text", text: "Error: no pending run available. Run run_experiment first." }],
86
- };
87
- }
88
-
89
- const runtime = options.getRuntime(ctx);
90
-
91
- const flaggedRuns: LogDetails["flaggedRuns"] = [];
92
- for (const flag of params.flag_runs ?? []) {
93
- const target = storage.getRunById(flag.run_id);
94
- if (!target || target.sessionId !== session.id) continue;
95
- storage.flagRun(flag.run_id, flag.reason);
96
- flaggedRuns.push({ runId: flag.run_id, reason: flag.reason });
97
- }
98
-
99
- const branchName = await getCurrentAutoresearchBranch(options.pi, ctx.cwd);
100
- const onAutoresearchBranch = branchName !== null;
101
-
102
- let allModified: string[];
103
- if (onAutoresearchBranch) {
104
- // On a dedicated autoresearch branch every iteration starts from a clean
105
- // worktree (init_experiment baseline + previous keep commit / discard reset),
106
- // so any currently-dirty path is the agent's iteration change. Off-branch we
107
- // can't tell user dirt apart from agent edits, so we keep the (lossy)
108
- // preRunDirtyPaths filter.
109
- const statusText = await tryGitStatus(ctx.cwd);
110
- const workDirPrefix = await tryGitPrefix(ctx.cwd);
111
- allModified = parseWorkDirDirtyPaths(statusText, workDirPrefix);
112
- } else {
113
- const { modifiedTracked, modifiedUntracked } = await detectModifiedPaths(
114
- ctx.cwd,
115
- pendingRun.preRunDirtyPaths,
116
- );
117
- allModified = [...modifiedTracked, ...modifiedUntracked];
118
- }
119
- const scopeDeviations = computeScopeDeviations(allModified, session);
120
-
121
- const justification = params.justification?.trim() || null;
122
- const warnings: string[] = [];
123
-
124
- const headSha = await tryReadHeadSha(ctx.cwd);
125
- const explicitCommit = params.commit?.trim();
126
- let commitHash = explicitCommit && explicitCommit.length > 0 ? explicitCommit : headSha;
127
-
128
- let gitNote: string | null = null;
129
- if (params.status === "keep") {
130
- if (onAutoresearchBranch && allModified.length > 0) {
131
- const commitResult = await commitKeptExperiment(
132
- ctx.cwd,
133
- params.description,
134
- params.status,
135
- params.metric,
136
- params.metrics ?? {},
137
- allModified,
138
- session.primaryMetric,
139
- );
140
- if (commitResult.error) {
141
- return {
142
- content: [{ type: "text", text: `Error: ${commitResult.error}` }],
143
- };
144
- }
145
- gitNote = commitResult.note ?? null;
146
- const newSha = await tryReadHeadSha(ctx.cwd);
147
- if (newSha) commitHash = newSha;
148
- } else if (!onAutoresearchBranch) {
149
- warnings.push(
150
- "Auto-commit skipped: not on a dedicated autoresearch branch. Modified files remain in the worktree.",
151
- );
152
- } else if (allModified.length === 0) {
153
- gitNote = "nothing to commit";
154
- }
155
- if (scopeDeviations.length > 0) {
156
- if (justification === null) {
157
- warnings.push(
158
- `Kept with unjustified scope deviations: ${scopeDeviations.join(", ")}. Pass \`justification\` next time or \`flag_runs\` this entry on a future log_experiment if it was a mistake.`,
159
- );
160
- } else {
161
- warnings.push(`Kept with scope deviations (justified): ${scopeDeviations.join(", ")}`);
162
- }
163
- }
164
- } else {
165
- const revertResult = await revertFailedExperiment(
166
- ctx.cwd,
167
- pendingRun.preRunDirtyPaths,
168
- onAutoresearchBranch,
169
- );
170
- if (revertResult.error) {
171
- return {
172
- content: [{ type: "text", text: `Error: ${revertResult.error}` }],
173
- };
174
- }
175
- gitNote = revertResult.note ?? null;
176
- }
177
-
178
- const metric = params.metric;
179
- const secondaryMetrics: NumericMetricMap = mergeMetrics(
180
- pendingRun.parsedMetrics,
181
- params.metrics,
182
- session.primaryMetric,
183
- );
184
- const asi: ASIData | undefined = mergeAsi(pendingRun.parsedAsi, sanitizeAsi(params.asi));
185
-
186
- if (pendingRun.parsedPrimary !== null && metric !== pendingRun.parsedPrimary) {
187
- warnings.push(
188
- `Logged metric ${metric} differs from parsed primary ${pendingRun.parsedPrimary}. Both values stored.`,
189
- );
190
- }
191
-
192
- const loggedAt = Date.now();
193
- const tentativeRun = storage.markRunLogged({
194
- runId: pendingRun.id,
195
- status: params.status,
196
- description: params.description,
197
- metric,
198
- metrics: secondaryMetrics,
199
- asi: asi ?? null,
200
- commitHash,
201
- confidence: null,
202
- modifiedPaths: allModified,
203
- scopeDeviations,
204
- justification,
205
- loggedAt,
206
- });
207
-
208
- // Recompute confidence with this run included
209
- const refreshedSession = storage.getSessionById(session.id) ?? session;
210
- const loggedRuns = storage.listLoggedRuns(session.id);
211
- const stateForConfidence = buildExperimentState(refreshedSession, loggedRuns);
212
- const confidence = computeConfidence(
213
- stateForConfidence.results,
214
- stateForConfidence.currentSegment,
215
- stateForConfidence.bestDirection,
216
- );
217
- storage.updateRunConfidence(tentativeRun.id, confidence);
218
-
219
- const finalState = buildExperimentState(refreshedSession, storage.listLoggedRuns(session.id));
220
- runtime.state = finalState;
221
- runtime.runningExperiment = null;
222
- runtime.lastRunSummary = null;
223
- runtime.lastRunDuration = null;
224
- runtime.lastRunAsi = null;
225
- runtime.lastRunArtifactDir = null;
226
- runtime.lastRunNumber = null;
227
- runtime.autoResumeArmed = true;
228
- runtime.lastAutoResumePendingRunNumber = null;
229
-
230
- const experiment: ExperimentResult = {
231
- runNumber: tentativeRun.id,
232
- commit: (commitHash ?? "").slice(0, 12),
233
- metric,
234
- metrics: secondaryMetrics,
235
- status: params.status,
236
- description: params.description,
237
- timestamp: loggedAt,
238
- segment: pendingRun.segment,
239
- confidence,
240
- asi,
241
- modifiedPaths: allModified,
242
- scopeDeviations,
243
- justification,
244
- flagged: false,
245
- flaggedReason: null,
246
- };
247
-
248
- const segmentRunCount = currentResults(finalState.results, finalState.currentSegment).length;
249
- if (finalState.maxExperiments !== null && segmentRunCount >= finalState.maxExperiments) {
250
- runtime.autoresearchMode = false;
251
- options.pi.appendEntry(
252
- "autoresearch-control",
253
- runtime.goal ? { mode: "off", goal: runtime.goal } : { mode: "off" },
254
- );
255
- await options.pi.setActiveTools(
256
- options.pi.getActiveTools().filter(name => !EXPERIMENT_TOOL_NAMES.includes(name)),
257
- );
258
- }
259
-
260
- options.dashboard.updateWidget(ctx, runtime);
261
- options.dashboard.requestRender();
262
-
263
- const wallClockSeconds = pendingRun.durationMs !== null ? pendingRun.durationMs / 1000 : null;
264
- const text = buildLogText(
265
- finalState,
266
- experiment,
267
- segmentRunCount,
268
- wallClockSeconds,
269
- gitNote,
270
- warnings,
271
- flaggedRuns,
272
- );
273
-
274
- return {
275
- content: [{ type: "text", text }],
276
- details: {
277
- experiment,
278
- state: finalState,
279
- wallClockSeconds,
280
- scopeDeviations,
281
- justification,
282
- flaggedRuns,
283
- },
284
- };
285
- },
286
- renderCall(args, _options, theme): Text {
287
- const color = args.status === "keep" ? "success" : args.status === "discard" ? "warning" : "error";
288
- const description = truncateToWidth(replaceTabs(args.description), 100);
289
- return new Text(
290
- `${theme.fg("toolTitle", theme.bold("log_experiment"))} ${theme.fg(color, args.status)} ${theme.fg("muted", description)}`,
291
- 0,
292
- 0,
293
- );
294
- },
295
- renderResult(result, _options, theme): Text {
296
- const details = result.details;
297
- if (!details) {
298
- return new Text(replaceTabs(result.content.find(part => part.type === "text")?.text ?? ""), 0, 0);
299
- }
300
- return new Text(renderSummary(details, theme), 0, 0);
301
- },
302
- };
303
- }
304
-
305
- interface KeepCommitResult {
306
- error?: string;
307
- note?: string;
308
- }
309
-
310
- async function commitKeptExperiment(
311
- cwd: string,
312
- description: string,
313
- status: ExperimentResult["status"],
314
- metric: number,
315
- metrics: NumericMetricMap,
316
- files: string[],
317
- primaryMetric: string,
318
- ): Promise<KeepCommitResult> {
319
- if (files.length === 0) return { note: "nothing to commit" };
320
- try {
321
- await git.stage.files(cwd, files);
322
- } catch (err) {
323
- return { error: `git add failed: ${err instanceof Error ? err.message : String(err)}` };
324
- }
325
- if (!(await git.diff.has(cwd, { cached: true, files }))) {
326
- return { note: "nothing to commit" };
327
- }
328
- const payload: { [key: string]: string | number } = {
329
- status,
330
- [primaryMetric]: metric,
331
- };
332
- for (const [name, value] of Object.entries(metrics)) {
333
- payload[name] = value;
334
- }
335
- const commitMessage = `${description}\n\nResult: ${JSON.stringify(payload)}`;
336
- try {
337
- const commitResult = await git.commit(cwd, commitMessage, { files });
338
- const summary = `${commitResult.stdout}${commitResult.stderr}`.split("\n").find(line => line.trim().length > 0);
339
- return { note: summary?.trim() ?? "committed" };
340
- } catch (err) {
341
- return { error: `git commit failed: ${err instanceof Error ? err.message : String(err)}` };
342
- }
343
- }
344
-
345
- async function revertFailedExperiment(
346
- cwd: string,
347
- preRunDirtyPaths: string[],
348
- onAutoresearchBranch: boolean,
349
- ): Promise<KeepCommitResult> {
350
- if (onAutoresearchBranch) {
351
- // Discard reverts only the current iteration's uncommitted changes — never
352
- // rewinds prior `keep` commits. Reset to HEAD so any kept improvements
353
- // already on the branch survive.
354
- try {
355
- await git.reset(cwd, { hard: true, target: "HEAD" });
356
- await git.clean(cwd);
357
- return { note: "worktree reset to HEAD" };
358
- } catch (err) {
359
- return { error: `git reset/clean failed: ${err instanceof Error ? err.message : String(err)}` };
360
- }
361
- }
362
-
363
- const statusText = await tryGitStatus(cwd);
364
- const workDirPrefix = await tryGitPrefix(cwd);
365
- const { tracked, untracked } = computeRunModifiedPaths(preRunDirtyPaths, statusText, workDirPrefix);
366
- const total = tracked.length + untracked.length;
367
- if (total === 0) return { note: "nothing to revert" };
368
- if (tracked.length > 0) {
369
- try {
370
- await git.restore(cwd, { files: tracked, source: "HEAD", staged: true, worktree: true });
371
- } catch (err) {
372
- return { error: `git restore failed: ${err instanceof Error ? err.message : String(err)}` };
373
- }
374
- }
375
- for (const filePath of untracked) {
376
- try {
377
- fs.rmSync(path.join(cwd, filePath), { force: true, recursive: true });
378
- } catch {
379
- // best effort
380
- }
381
- }
382
- return { note: `reverted ${total} file${total === 1 ? "" : "s"}` };
383
- }
384
-
385
- async function detectModifiedPaths(
386
- cwd: string,
387
- preRunDirtyPaths: string[],
388
- ): Promise<{ modifiedTracked: string[]; modifiedUntracked: string[] }> {
389
- const statusText = await tryGitStatus(cwd);
390
- const workDirPrefix = await tryGitPrefix(cwd);
391
- const { tracked, untracked } = computeRunModifiedPaths(preRunDirtyPaths, statusText, workDirPrefix);
392
- return { modifiedTracked: tracked, modifiedUntracked: untracked };
393
- }
394
-
395
- function computeScopeDeviations(modifiedPaths: string[], session: SessionRow): string[] {
396
- const deviations: string[] = [];
397
- for (const filePath of modifiedPaths) {
398
- if (session.offLimits.some(spec => pathMatchesSpec(filePath, spec))) {
399
- deviations.push(filePath);
400
- continue;
401
- }
402
- if (session.scopePaths.length > 0 && !session.scopePaths.some(spec => pathMatchesSpec(filePath, spec))) {
403
- deviations.push(filePath);
404
- }
405
- }
406
- return deviations;
407
- }
408
-
409
- function mergeMetrics(
410
- parsed: NumericMetricMap | null,
411
- overrides: NumericMetricMap | undefined,
412
- primaryMetricName: string,
413
- ): NumericMetricMap {
414
- const merged: NumericMetricMap = {};
415
- for (const [name, value] of Object.entries(parsed ?? {})) {
416
- if (name === primaryMetricName) continue;
417
- merged[name] = value;
418
- }
419
- for (const [name, value] of Object.entries(ensureNumericMetricMap(overrides))) {
420
- merged[name] = value;
421
- }
422
- return merged;
423
- }
424
-
425
- async function tryReadHeadSha(cwd: string): Promise<string | null> {
426
- try {
427
- return (await git.head.sha(cwd)) ?? null;
428
- } catch {
429
- return null;
430
- }
431
- }
432
-
433
- function buildLogText(
434
- state: ExperimentState,
435
- experiment: ExperimentResult,
436
- segmentRunCount: number,
437
- wallClockSeconds: number | null,
438
- gitNote: string | null,
439
- warnings: string[],
440
- flaggedRuns: LogDetails["flaggedRuns"],
441
- ): string {
442
- const displayRunNumber = experiment.runNumber ?? state.results.length;
443
- const lines = [`Logged run #${displayRunNumber}: ${experiment.status} - ${experiment.description}`];
444
- if (wallClockSeconds !== null) {
445
- lines.push(`Wall clock: ${wallClockSeconds.toFixed(1)}s`);
446
- }
447
- if (state.bestMetric !== null) {
448
- lines.push(`Baseline ${state.metricName}: ${formatNum(state.bestMetric, state.metricUnit)}`);
449
- }
450
- if (segmentRunCount > 1 && state.bestMetric !== null && experiment.metric !== state.bestMetric) {
451
- const delta = ((experiment.metric - state.bestMetric) / state.bestMetric) * 100;
452
- const sign = delta > 0 ? "+" : "";
453
- lines.push(`This run: ${formatNum(experiment.metric, state.metricUnit)} (${sign}${delta.toFixed(1)}%)`);
454
- } else {
455
- lines.push(`This run: ${formatNum(experiment.metric, state.metricUnit)}`);
456
- }
457
- if (Object.keys(experiment.metrics).length > 0) {
458
- const baselineSecondary = findBaselineSecondary(state.results, state.currentSegment, state.secondaryMetrics);
459
- const parts = Object.entries(experiment.metrics).map(([name, value]) => {
460
- const unit = state.secondaryMetrics.find(metric => metric.name === name)?.unit ?? "";
461
- const baseline = baselineSecondary[name];
462
- if (baseline === undefined || baseline === 0 || segmentRunCount === 1) {
463
- return `${name}: ${formatNum(value, unit)}`;
464
- }
465
- const delta = ((value - baseline) / baseline) * 100;
466
- const sign = delta > 0 ? "+" : "";
467
- return `${name}: ${formatNum(value, unit)} (${sign}${delta.toFixed(1)}%)`;
468
- });
469
- lines.push(`Secondary metrics: ${parts.join(" ")}`);
470
- }
471
- const bestKept = findBestKeptMetric(state.results, state.currentSegment, state.bestDirection);
472
- if (bestKept !== null && state.bestMetric !== null && bestKept !== state.bestMetric) {
473
- lines.push(`Best kept ${state.metricName}: ${formatNum(bestKept, state.metricUnit)}`);
474
- }
475
- if (experiment.asi) {
476
- const asiSummary = Object.entries(experiment.asi)
477
- .map(([key, value]) => `${key}: ${truncateAsiValue(value)}`)
478
- .join(" | ");
479
- lines.push(`ASI: ${asiSummary}`);
480
- }
481
- if (state.confidence !== null) {
482
- const status = state.confidence >= 2 ? "likely real" : state.confidence >= 1 ? "marginal" : "within noise";
483
- lines.push(`Confidence: ${state.confidence.toFixed(1)}x noise floor (${status})`);
484
- }
485
- if (gitNote) {
486
- lines.push(`Git: ${gitNote}`);
487
- }
488
- if (state.maxExperiments !== null) {
489
- lines.push(`Progress: ${segmentRunCount}/${state.maxExperiments} runs in current segment`);
490
- if (segmentRunCount >= state.maxExperiments) {
491
- lines.push(`Maximum experiments reached (${state.maxExperiments}). Autoresearch mode is now off.`);
492
- }
493
- }
494
- if (flaggedRuns.length > 0) {
495
- const formatted = flaggedRuns.map(({ runId, reason }) => `#${runId} (${reason})`).join(", ");
496
- lines.push(`Flagged: ${formatted}`);
497
- }
498
- for (const warning of warnings) {
499
- lines.push(`Warning: ${warning}`);
500
- }
501
- return lines.join("\n");
502
- }
503
-
504
- function truncateAsiValue(value: ASIData[string]): string {
505
- const text = typeof value === "string" ? value : JSON.stringify(value);
506
- return text.length > 120 ? `${text.slice(0, 117)}...` : text;
507
- }
508
-
509
- function renderSummary(details: LogDetails, theme: Theme): string {
510
- const { experiment, state } = details;
511
- const color = experiment.status === "keep" ? "success" : experiment.status === "discard" ? "warning" : "error";
512
- let summary = `${theme.fg(color, experiment.status.toUpperCase())} ${theme.fg("muted", truncateToWidth(replaceTabs(experiment.description), 100))}`;
513
- summary += ` ${theme.fg("accent", `${state.metricName}=${formatNum(experiment.metric, state.metricUnit)}`)}`;
514
- if (state.bestMetric !== null) {
515
- summary += ` ${theme.fg("dim", `baseline ${formatNum(state.bestMetric, state.metricUnit)}`)}`;
516
- }
517
- if (state.confidence !== null) {
518
- summary += ` ${theme.fg("dim", `conf ${state.confidence.toFixed(1)}x`)}`;
519
- }
520
- if (details.scopeDeviations.length > 0) {
521
- summary += ` ${theme.fg("warning", `deviations:${details.scopeDeviations.length}`)}`;
522
- }
523
- return summary;
524
- }