headlesscode 1.0.2

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 (232) hide show
  1. package/ATTRIBUTION.md +53 -0
  2. package/CODE_OF_CONDUCT.md +130 -0
  3. package/CONTRIBUTING.md +107 -0
  4. package/LICENSE +202 -0
  5. package/README.md +486 -0
  6. package/SECURITY.md +211 -0
  7. package/bin/headlesscode.mjs +83 -0
  8. package/package.json +63 -0
  9. package/shared/prompts/review-mode-prompt-short.md +93 -0
  10. package/shared/prompts/review-mode-prompt.md +281 -0
  11. package/shared/rules-code/rules.md +22 -0
  12. package/shared/stacks/cpp/rules.md +30 -0
  13. package/shared/stacks/fastapi/rules.md +30 -0
  14. package/shared/stacks/javascript/rules.md +37 -0
  15. package/shared/stacks/postgresql/rules.md +31 -0
  16. package/shared/stacks/python/rules.md +35 -0
  17. package/shared/stacks/react/rules.md +11 -0
  18. package/shared/stacks/typescript/rules.md +10 -0
  19. package/src/budget/budget.ts +221 -0
  20. package/src/budget/concurrency.ts +126 -0
  21. package/src/budget/cost.ts +309 -0
  22. package/src/budget/index.ts +8 -0
  23. package/src/checkpoints/cli.ts +256 -0
  24. package/src/checkpoints/service.ts +227 -0
  25. package/src/cli.ts +1535 -0
  26. package/src/cloud/docker-provider.ts +334 -0
  27. package/src/cloud/provider.ts +300 -0
  28. package/src/codeintel/call-graph.ts +78 -0
  29. package/src/codeintel/find-references.ts +123 -0
  30. package/src/codeintel/go-to-definition.ts +193 -0
  31. package/src/codeintel/handlers.ts +190 -0
  32. package/src/codeintel/import-graph.ts +173 -0
  33. package/src/codeintel/outline.ts +180 -0
  34. package/src/codeintel/position.ts +77 -0
  35. package/src/codeintel/program.ts +350 -0
  36. package/src/codeintel/rename-symbol.ts +213 -0
  37. package/src/codeintel/tools.ts +280 -0
  38. package/src/codemap/build.ts +135 -0
  39. package/src/codemap/cli.ts +190 -0
  40. package/src/codemap/extract.ts +339 -0
  41. package/src/codemap/files.ts +236 -0
  42. package/src/codemap/fingerprint.ts +65 -0
  43. package/src/codemap/flows.ts +62 -0
  44. package/src/codemap/html.ts +451 -0
  45. package/src/codemap/lock.ts +80 -0
  46. package/src/codemap/types.ts +101 -0
  47. package/src/codesearch/airunner-embedder.ts +185 -0
  48. package/src/codesearch/chunk.ts +339 -0
  49. package/src/codesearch/cli.ts +223 -0
  50. package/src/codesearch/embedder.ts +332 -0
  51. package/src/codesearch/files.ts +280 -0
  52. package/src/codesearch/index.ts +469 -0
  53. package/src/codesearch/ollama-embedder.ts +205 -0
  54. package/src/codesearch/search.ts +141 -0
  55. package/src/codesearch/types.ts +100 -0
  56. package/src/config/mode-models.ts +218 -0
  57. package/src/dashboard/aggregate.ts +364 -0
  58. package/src/dashboard/chat-thread.ts +141 -0
  59. package/src/dashboard/checkpoints.ts +124 -0
  60. package/src/dashboard/cli.ts +193 -0
  61. package/src/dashboard/codemap.ts +44 -0
  62. package/src/dashboard/files.ts +121 -0
  63. package/src/dashboard/page.ts +2803 -0
  64. package/src/dashboard/self-improvement-metrics.ts +282 -0
  65. package/src/dashboard/server.ts +1103 -0
  66. package/src/dashboard/session-launch.ts +310 -0
  67. package/src/dashboard/timeline.ts +273 -0
  68. package/src/dashboard/tool-exec.ts +107 -0
  69. package/src/dashboard/trend-cli.ts +141 -0
  70. package/src/dashboard/trend.ts +413 -0
  71. package/src/decision-proxy/cli.ts +261 -0
  72. package/src/decision-proxy/proxy.ts +569 -0
  73. package/src/deploy/gate-cli.ts +147 -0
  74. package/src/deploy/gate.ts +254 -0
  75. package/src/engine/condense.ts +512 -0
  76. package/src/engine/events.ts +428 -0
  77. package/src/engine/handoff.ts +71 -0
  78. package/src/engine/lazy-tools.ts +160 -0
  79. package/src/engine/local-explore.ts +653 -0
  80. package/src/engine/logger.ts +96 -0
  81. package/src/engine/loop.ts +5517 -0
  82. package/src/engine/parser.ts +347 -0
  83. package/src/engine/prompt.ts +860 -0
  84. package/src/engine/reports.ts +47 -0
  85. package/src/engine/stacks.ts +448 -0
  86. package/src/engine/types.ts +291 -0
  87. package/src/engine/usage.ts +186 -0
  88. package/src/github/app-auth.ts +161 -0
  89. package/src/github/cli.ts +448 -0
  90. package/src/github/installations.ts +133 -0
  91. package/src/github/pr.ts +321 -0
  92. package/src/github/provision.ts +118 -0
  93. package/src/github/push.ts +122 -0
  94. package/src/index-util.ts +50 -0
  95. package/src/index.ts +81 -0
  96. package/src/init/cli.ts +248 -0
  97. package/src/init/gitignore.ts +74 -0
  98. package/src/llm/ollama.ts +308 -0
  99. package/src/llm/openrouter.ts +868 -0
  100. package/src/llm/preflight.ts +367 -0
  101. package/src/llm/transcript-capture.ts +84 -0
  102. package/src/memory/embed.ts +110 -0
  103. package/src/memory/index.ts +22 -0
  104. package/src/memory/local.ts +259 -0
  105. package/src/memory/summarizer.ts +283 -0
  106. package/src/memory/types.ts +153 -0
  107. package/src/memory/uwuchat.ts +157 -0
  108. package/src/migrate/cli.ts +115 -0
  109. package/src/orchestrator/analyze-cli.ts +104 -0
  110. package/src/orchestrator/auto-split.ts +206 -0
  111. package/src/orchestrator/cleanup.ts +1003 -0
  112. package/src/orchestrator/cli.ts +3571 -0
  113. package/src/orchestrator/cost-estimate.ts +564 -0
  114. package/src/orchestrator/cost-history-cli.ts +242 -0
  115. package/src/orchestrator/cost-history.ts +397 -0
  116. package/src/orchestrator/git-sync.ts +250 -0
  117. package/src/orchestrator/index.ts +153 -0
  118. package/src/orchestrator/log-analysis.ts +0 -0
  119. package/src/orchestrator/merge-check.ts +108 -0
  120. package/src/orchestrator/pipeline.ts +411 -0
  121. package/src/orchestrator/resume.ts +1940 -0
  122. package/src/orchestrator/reviewer.ts +503 -0
  123. package/src/orchestrator/split.ts +296 -0
  124. package/src/orchestrator/state.ts +542 -0
  125. package/src/orchestrator/status.ts +697 -0
  126. package/src/orchestrator/verification-gate.ts +134 -0
  127. package/src/orchestrator/watch.ts +898 -0
  128. package/src/permissions/commands.ts +1083 -0
  129. package/src/permissions/config.ts +241 -0
  130. package/src/permissions/index.ts +12 -0
  131. package/src/permissions/protected-files.ts +96 -0
  132. package/src/permissions/store-protection.ts +272 -0
  133. package/src/project-store.ts +648 -0
  134. package/src/projects/cli.ts +382 -0
  135. package/src/qa/qa.ts +487 -0
  136. package/src/tools/browser/handler.ts +346 -0
  137. package/src/tools/browser/service.ts +406 -0
  138. package/src/tools/browser/smoke.ts +78 -0
  139. package/src/tools/browser/tool.ts +99 -0
  140. package/src/tools/executor.ts +2575 -0
  141. package/src/tools/language-detect.ts +183 -0
  142. package/src/tools/output-summarizer.ts +369 -0
  143. package/src/tools/run-tests.ts +302 -0
  144. package/src/tools/set-indentation-tool.ts +49 -0
  145. package/src/tools/test-selection.ts +160 -0
  146. package/src/vendor/tests/smoke.ts +103 -0
  147. package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
  148. package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
  149. package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
  150. package/src/vendor/zoo-code/shim/os-name.ts +18 -0
  151. package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
  152. package/src/vendor/zoo-code/shim/vscode.ts +76 -0
  153. package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
  154. package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
  155. package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
  156. package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
  157. package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
  158. package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
  159. package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
  160. package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
  161. package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
  162. package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
  163. package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
  164. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
  165. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
  166. package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
  167. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
  168. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
  169. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
  170. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
  171. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
  172. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
  173. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
  174. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
  175. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
  176. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
  177. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
  178. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
  179. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
  180. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
  181. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
  182. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
  183. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
  184. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
  185. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
  186. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
  187. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
  188. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
  189. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
  190. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
  191. package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
  192. package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
  193. package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
  194. package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
  195. package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
  196. package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
  197. package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
  198. package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
  199. package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
  200. package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
  201. package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
  202. package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
  203. package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
  204. package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
  205. package/src/vendor/zoo-code/src/shared/language.ts +43 -0
  206. package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
  207. package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
  208. package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
  209. package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
  210. package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
  211. package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
  212. package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
  213. package/src/vendor/zoo-code/src/utils/object.ts +18 -0
  214. package/src/vendor/zoo-code/src/utils/path.ts +94 -0
  215. package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
  216. package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
  217. package/src/vendor/zoo-code/types/global-settings.ts +19 -0
  218. package/src/vendor/zoo-code/types/index.ts +22 -0
  219. package/src/vendor/zoo-code/types/message.ts +375 -0
  220. package/src/vendor/zoo-code/types/mode.ts +241 -0
  221. package/src/vendor/zoo-code/types/todo.ts +19 -0
  222. package/src/vendor/zoo-code/types/tool-params.ts +116 -0
  223. package/src/vendor/zoo-code/types/tool.ts +67 -0
  224. package/src/vendor/zoo-code/types/vscode.ts +84 -0
  225. package/src/vision/describe.ts +242 -0
  226. package/src/vision/tool.ts +91 -0
  227. package/src/watcher/cli.ts +369 -0
  228. package/src/watcher/github.ts +304 -0
  229. package/src/watcher/index.ts +59 -0
  230. package/src/watcher/state.ts +254 -0
  231. package/src/watcher/watch.ts +562 -0
  232. package/tsconfig.json +18 -0
@@ -0,0 +1,261 @@
1
+ /**
2
+ * `headlesscode decision-proxy` subcommand — the decision-proxy agent
3
+ * (plans/decision-proxy-agent.md): an LLM stand-in for the human on
4
+ * `ask_followup_question`. Watches a workspace for `.harness.needs-decision`
5
+ * and writes `.harness.decision-answer` grounded in the session's original
6
+ * task text, so a headless session never blocks the full decision timeout
7
+ * on a question its own task can answer. See src/decision-proxy/proxy.ts for
8
+ * the three-outcome logic and the fail-open contract.
9
+ *
10
+ * npx tsx src/cli.ts decision-proxy --workspace <path> [--task <text>] [options]
11
+ *
12
+ * OPT-IN experimental subsystem: refuses to run unless HEADLESSCODE_DECISION_PROXY=1
13
+ * is set (same convention as HEADLESSCODE_LOCAL_EXPLORE).
14
+ */
15
+
16
+ import * as fs from "node:fs"
17
+ import * as fsp from "node:fs/promises"
18
+ import * as path from "node:path"
19
+
20
+ import { OpenRouterClient } from "../llm/openrouter.js"
21
+ import { Logger } from "../engine/logger.js"
22
+ import {
23
+ DECISION_PROXY_LOG_FILE,
24
+ DECISION_PROXY_MODEL_KEY,
25
+ isDecisionProxyEnabled,
26
+ resolveDecisionProxyLlmTimeout,
27
+ resolveDecisionProxyPollInterval,
28
+ resolveDecisionProxyModel,
29
+ runDecisionProxy,
30
+ } from "./proxy.js"
31
+ import type { DecisionProxyOptions } from "./proxy.js"
32
+
33
+ const DECISION_PROXY_USAGE = `headlesscode decision-proxy — LLM stand-in for the human on ask_followup_question
34
+
35
+ Usage:
36
+ headlesscode decision-proxy --workspace <path> [--task <text>] [options]
37
+
38
+ Watches <path> for .harness.needs-decision markers and answers them by writing
39
+ .harness.decision-answer — the same file a human writes via
40
+ scripts/headlesscode-answer.sh — grounded in the session's ORIGINAL task text.
41
+ When the task text cannot ground a specific answer the proxy writes NOTHING
42
+ and the session's existing decision-timeout fallback fires exactly as today.
43
+
44
+ OPT-IN: requires HEADLESSCODE_DECISION_PROXY=1 (experimental subsystem).
45
+
46
+ Options:
47
+ --workspace <path> Workspace/worktree to watch (required)
48
+ --task <text> The session's original task text, VERBATIM. Used to
49
+ ground answers; when omitted the proxy falls back to
50
+ --task-file, then to the orchestrator's group
51
+ task_file (read from .worktrees/.orchestrator-state.json)
52
+ --task-file <path> Read the original task from this file (relative to
53
+ --workspace), verbatim
54
+ --model <id> OpenRouter model id for answers (default: the
55
+ _decision-proxy key in
56
+ <workspace>/.headlesscode/mode-models.json, else
57
+ mode/_default/OPENROUTER_MODEL/client default)
58
+ --poll-interval-ms <n> Marker poll interval (default:
59
+ $HEADLESSCODE_DECISION_PROXY_POLL_INTERVAL_MS or 5000)
60
+ --llm-timeout-ms <n> Per-answer LLM abort timeout, ms (default:
61
+ $HEADLESSCODE_DECISION_PROXY_LLM_TIMEOUT_MS or 60000)
62
+ --log-file <path> Proxy audit log (default:
63
+ <workspace>/.headlesscode/decision-proxy.log)
64
+ --help Show this help and exit
65
+
66
+ Environment:
67
+ HEADLESSCODE_DECISION_PROXY Required: "1"/"true" enables the proxy
68
+ HEADLESSCODE_OPENROUTER_API_KEY Required (real LLM answers)
69
+ OPENROUTER_MODEL Default model id
70
+ HEADLESSCODE_DECISION_PROXY_POLL_INTERVAL_MS Poll interval, ms (default 5000)
71
+ HEADLESSCODE_DECISION_PROXY_LLM_TIMEOUT_MS Per-answer timeout, ms (default 60000)
72
+
73
+ The proxy runs until interrupted (SIGINT/SIGTERM → clean exit 0). Every
74
+ question seen is logged with its outcome (answered / uncertain / errored) to
75
+ the audit log, so a full session's decision history is reconstructable even
76
+ though nothing blocked on it live.
77
+ `
78
+
79
+ interface DecisionProxyCliOptions {
80
+ workspace?: string
81
+ task?: string
82
+ taskFile?: string
83
+ model?: string
84
+ pollIntervalMs?: number
85
+ llmTimeoutMs?: number
86
+ logFile?: string
87
+ help: boolean
88
+ }
89
+
90
+ export function parseDecisionProxyArgs(argv: string[]): { options: DecisionProxyCliOptions; error?: string } {
91
+ const options: DecisionProxyCliOptions = { help: false }
92
+
93
+ for (let i = 0; i < argv.length; i++) {
94
+ const arg = argv[i]
95
+ const eq = arg.indexOf("=")
96
+ const flag = eq === -1 ? arg : arg.slice(0, eq)
97
+ const inlineValue = eq === -1 ? undefined : arg.slice(eq + 1)
98
+ const next = (): string | undefined => {
99
+ if (inlineValue !== undefined) {
100
+ return inlineValue
101
+ }
102
+ const v = argv[i + 1]
103
+ if (v === undefined || v.startsWith("--")) {
104
+ return undefined
105
+ }
106
+ i++
107
+ return v
108
+ }
109
+
110
+ switch (flag) {
111
+ case "--workspace": {
112
+ const v = next()
113
+ if (v === undefined) {
114
+ return { options, error: "Missing value for --workspace" }
115
+ }
116
+ options.workspace = v
117
+ break
118
+ }
119
+ case "--task": {
120
+ const v = next()
121
+ if (v === undefined) {
122
+ return { options, error: "Missing value for --task" }
123
+ }
124
+ options.task = v
125
+ break
126
+ }
127
+ case "--task-file": {
128
+ const v = next()
129
+ if (v === undefined) {
130
+ return { options, error: "Missing value for --task-file" }
131
+ }
132
+ options.taskFile = v
133
+ break
134
+ }
135
+ case "--model": {
136
+ const v = next()
137
+ if (v === undefined) {
138
+ return { options, error: "Missing value for --model" }
139
+ }
140
+ options.model = v
141
+ break
142
+ }
143
+ case "--poll-interval-ms": {
144
+ const v = next()
145
+ const n = Number(v)
146
+ if (v === undefined || !Number.isInteger(n) || n <= 0) {
147
+ return { options, error: "--poll-interval-ms must be a positive integer" }
148
+ }
149
+ options.pollIntervalMs = n
150
+ break
151
+ }
152
+ case "--llm-timeout-ms": {
153
+ const v = next()
154
+ const n = Number(v)
155
+ if (v === undefined || !Number.isInteger(n) || n <= 0) {
156
+ return { options, error: "--llm-timeout-ms must be a positive integer" }
157
+ }
158
+ options.llmTimeoutMs = n
159
+ break
160
+ }
161
+ case "--log-file": {
162
+ const v = next()
163
+ if (v === undefined) {
164
+ return { options, error: "Missing value for --log-file" }
165
+ }
166
+ options.logFile = v
167
+ break
168
+ }
169
+ case "--help":
170
+ case "-h":
171
+ options.help = true
172
+ break
173
+ default:
174
+ return { options, error: `Unknown decision-proxy argument: ${arg}` }
175
+ }
176
+ }
177
+
178
+ return { options }
179
+ }
180
+
181
+ export async function decisionProxyMain(argv: string[]): Promise<number> {
182
+ const { options, error } = parseDecisionProxyArgs(argv)
183
+ if (error) {
184
+ process.stderr.write(`decision-proxy: ${error}\n\n${DECISION_PROXY_USAGE}`)
185
+ return 2
186
+ }
187
+ if (options.help) {
188
+ process.stdout.write(DECISION_PROXY_USAGE)
189
+ return 0
190
+ }
191
+
192
+ if (!isDecisionProxyEnabled(process.env)) {
193
+ process.stderr.write(
194
+ "decision-proxy: HEADLESSCODE_DECISION_PROXY is not set. The decision proxy is an opt-in\n" +
195
+ " experimental subsystem (like HEADLESSCODE_LOCAL_EXPLORE) — set HEADLESSCODE_DECISION_PROXY=1\n" +
196
+ " to enable it.\n",
197
+ )
198
+ return 2
199
+ }
200
+
201
+ if (!options.workspace) {
202
+ process.stderr.write(`decision-proxy: --workspace <path> is required\n\n${DECISION_PROXY_USAGE}`)
203
+ return 2
204
+ }
205
+
206
+ const workspaceRoot = path.resolve(options.workspace)
207
+ if (!fs.existsSync(workspaceRoot) || !fs.statSync(workspaceRoot).isDirectory()) {
208
+ process.stderr.write(`decision-proxy: workspace does not exist or is not a directory: ${workspaceRoot}\n`)
209
+ return 2
210
+ }
211
+
212
+ const apiKey = process.env.HEADLESSCODE_OPENROUTER_API_KEY
213
+ if (!apiKey) {
214
+ process.stderr.write(
215
+ "decision-proxy: HEADLESSCODE_OPENROUTER_API_KEY is not set — the proxy needs a real LLM to answer questions.\n",
216
+ )
217
+ return 2
218
+ }
219
+
220
+ // Model resolution: an explicit --model always wins; otherwise the
221
+ // `_decision-proxy` key in mode-models.json (consulted first, like
222
+ // `_condensation`), then mode/_default/OPENROUTER_MODEL/client default.
223
+ const model =
224
+ options.model ?? resolveDecisionProxyModel(workspaceRoot, process.env) ?? undefined
225
+
226
+ const logFile = options.logFile ? path.resolve(options.logFile) : path.join(workspaceRoot, DECISION_PROXY_LOG_FILE)
227
+ try {
228
+ await fsp.mkdir(path.dirname(logFile), { recursive: true })
229
+ } catch {
230
+ // Non-fatal: the Logger falls back to stdout/stderr if the file can't be written.
231
+ }
232
+ const logger = new Logger({ level: "info", filePath: logFile })
233
+
234
+ const client = new OpenRouterClient({ apiKey, defaultModel: model })
235
+
236
+ const proxyOptions: DecisionProxyOptions = {
237
+ workspaceRoot,
238
+ task: options.task,
239
+ taskFile: options.taskFile,
240
+ model,
241
+ llmClient: client,
242
+ pollIntervalMs: options.pollIntervalMs ?? resolveDecisionProxyPollInterval(process.env),
243
+ llmTimeoutMs: options.llmTimeoutMs ?? resolveDecisionProxyLlmTimeout(process.env),
244
+ logger,
245
+ }
246
+
247
+ // SIGINT/SIGTERM → stop the loop cleanly and exit 0 (the audit log's
248
+ // "stopped" line is the terminal record for the session's decision history).
249
+ const controller = new AbortController()
250
+ const onSignal = (): void => controller.abort()
251
+ process.on("SIGINT", onSignal)
252
+ process.on("SIGTERM", onSignal)
253
+
254
+ try {
255
+ await runDecisionProxy(proxyOptions, controller.signal)
256
+ return 0
257
+ } finally {
258
+ process.off("SIGINT", onSignal)
259
+ process.off("SIGTERM", onSignal)
260
+ }
261
+ }