@the-open-engine/zeroshot 5.4.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 (295) hide show
  1. package/CHANGELOG.md +539 -0
  2. package/LICENSE +21 -0
  3. package/README.md +508 -0
  4. package/cli/commands/inspect-render.js +135 -0
  5. package/cli/commands/inspect.js +294 -0
  6. package/cli/commands/providers.js +149 -0
  7. package/cli/index.js +5431 -0
  8. package/cli/lib/first-run.js +211 -0
  9. package/cli/lib/update-checker.js +281 -0
  10. package/cli/message-formatter-utils.js +75 -0
  11. package/cli/message-formatters-normal.js +275 -0
  12. package/cli/message-formatters-watch.js +185 -0
  13. package/cluster-templates/base-templates/debug-workflow.json +422 -0
  14. package/cluster-templates/base-templates/full-workflow.json +727 -0
  15. package/cluster-templates/base-templates/heavy-validation.json +272 -0
  16. package/cluster-templates/base-templates/quick-validation.json +285 -0
  17. package/cluster-templates/base-templates/single-worker.json +71 -0
  18. package/cluster-templates/base-templates/worker-validator.json +230 -0
  19. package/cluster-templates/conductor-bootstrap.json +130 -0
  20. package/docker/zeroshot-cluster/Dockerfile +153 -0
  21. package/docker/zeroshot-cluster/pre-baked-deps.json +28 -0
  22. package/lib/agent-cli-provider/adapters/claude-parser.d.ts +3 -0
  23. package/lib/agent-cli-provider/adapters/claude-parser.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/adapters/claude-parser.js +122 -0
  25. package/lib/agent-cli-provider/adapters/claude-parser.js.map +1 -0
  26. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts +15 -0
  27. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/adapters/claude-recovery.js +165 -0
  29. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -0
  30. package/lib/agent-cli-provider/adapters/claude.d.ts +3 -0
  31. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/adapters/claude.js +181 -0
  33. package/lib/agent-cli-provider/adapters/claude.js.map +1 -0
  34. package/lib/agent-cli-provider/adapters/codex-parser.d.ts +3 -0
  35. package/lib/agent-cli-provider/adapters/codex-parser.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/adapters/codex-parser.js +192 -0
  37. package/lib/agent-cli-provider/adapters/codex-parser.js.map +1 -0
  38. package/lib/agent-cli-provider/adapters/codex.d.ts +3 -0
  39. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -0
  40. package/lib/agent-cli-provider/adapters/codex.js +151 -0
  41. package/lib/agent-cli-provider/adapters/codex.js.map +1 -0
  42. package/lib/agent-cli-provider/adapters/common.d.ts +28 -0
  43. package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -0
  44. package/lib/agent-cli-provider/adapters/common.js +71 -0
  45. package/lib/agent-cli-provider/adapters/common.js.map +1 -0
  46. package/lib/agent-cli-provider/adapters/gemini.d.ts +3 -0
  47. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -0
  48. package/lib/agent-cli-provider/adapters/gemini.js +176 -0
  49. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -0
  50. package/lib/agent-cli-provider/adapters/index.d.ts +9 -0
  51. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -0
  52. package/lib/agent-cli-provider/adapters/index.js +95 -0
  53. package/lib/agent-cli-provider/adapters/index.js.map +1 -0
  54. package/lib/agent-cli-provider/adapters/opencode.d.ts +3 -0
  55. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -0
  56. package/lib/agent-cli-provider/adapters/opencode.js +228 -0
  57. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -0
  58. package/lib/agent-cli-provider/contract-actions.d.ts +5 -0
  59. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -0
  60. package/lib/agent-cli-provider/contract-actions.js +140 -0
  61. package/lib/agent-cli-provider/contract-actions.js.map +1 -0
  62. package/lib/agent-cli-provider/contract-env.d.ts +11 -0
  63. package/lib/agent-cli-provider/contract-env.d.ts.map +1 -0
  64. package/lib/agent-cli-provider/contract-env.js +113 -0
  65. package/lib/agent-cli-provider/contract-env.js.map +1 -0
  66. package/lib/agent-cli-provider/contract-envelope.d.ts +49 -0
  67. package/lib/agent-cli-provider/contract-envelope.d.ts.map +1 -0
  68. package/lib/agent-cli-provider/contract-envelope.js +52 -0
  69. package/lib/agent-cli-provider/contract-envelope.js.map +1 -0
  70. package/lib/agent-cli-provider/contract-errors.d.ts +22 -0
  71. package/lib/agent-cli-provider/contract-errors.d.ts.map +1 -0
  72. package/lib/agent-cli-provider/contract-errors.js +71 -0
  73. package/lib/agent-cli-provider/contract-errors.js.map +1 -0
  74. package/lib/agent-cli-provider/contract-fallback.d.ts +7 -0
  75. package/lib/agent-cli-provider/contract-fallback.d.ts.map +1 -0
  76. package/lib/agent-cli-provider/contract-fallback.js +102 -0
  77. package/lib/agent-cli-provider/contract-fallback.js.map +1 -0
  78. package/lib/agent-cli-provider/contract-invoke.d.ts +5 -0
  79. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -0
  80. package/lib/agent-cli-provider/contract-invoke.js +89 -0
  81. package/lib/agent-cli-provider/contract-invoke.js.map +1 -0
  82. package/lib/agent-cli-provider/contract-options.d.ts +3 -0
  83. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -0
  84. package/lib/agent-cli-provider/contract-options.js +140 -0
  85. package/lib/agent-cli-provider/contract-options.js.map +1 -0
  86. package/lib/agent-cli-provider/contract-parse.d.ts +15 -0
  87. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -0
  88. package/lib/agent-cli-provider/contract-parse.js +81 -0
  89. package/lib/agent-cli-provider/contract-parse.js.map +1 -0
  90. package/lib/agent-cli-provider/contract-support.d.ts +18 -0
  91. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -0
  92. package/lib/agent-cli-provider/contract-support.js +154 -0
  93. package/lib/agent-cli-provider/contract-support.js.map +1 -0
  94. package/lib/agent-cli-provider/contract.d.ts +13 -0
  95. package/lib/agent-cli-provider/contract.d.ts.map +1 -0
  96. package/lib/agent-cli-provider/contract.js +30 -0
  97. package/lib/agent-cli-provider/contract.js.map +1 -0
  98. package/lib/agent-cli-provider/env-safety.d.ts +11 -0
  99. package/lib/agent-cli-provider/env-safety.d.ts.map +1 -0
  100. package/lib/agent-cli-provider/env-safety.js +83 -0
  101. package/lib/agent-cli-provider/env-safety.js.map +1 -0
  102. package/lib/agent-cli-provider/errors.d.ts +5 -0
  103. package/lib/agent-cli-provider/errors.d.ts.map +1 -0
  104. package/lib/agent-cli-provider/errors.js +115 -0
  105. package/lib/agent-cli-provider/errors.js.map +1 -0
  106. package/lib/agent-cli-provider/executable.d.ts +3 -0
  107. package/lib/agent-cli-provider/executable.d.ts.map +1 -0
  108. package/lib/agent-cli-provider/executable.js +24 -0
  109. package/lib/agent-cli-provider/executable.js.map +1 -0
  110. package/lib/agent-cli-provider/index.d.ts +8 -0
  111. package/lib/agent-cli-provider/index.d.ts.map +1 -0
  112. package/lib/agent-cli-provider/index.js +31 -0
  113. package/lib/agent-cli-provider/index.js.map +1 -0
  114. package/lib/agent-cli-provider/invoke-evidence.d.ts +4 -0
  115. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -0
  116. package/lib/agent-cli-provider/invoke-evidence.js +17 -0
  117. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -0
  118. package/lib/agent-cli-provider/json.d.ts +16 -0
  119. package/lib/agent-cli-provider/json.d.ts.map +1 -0
  120. package/lib/agent-cli-provider/json.js +110 -0
  121. package/lib/agent-cli-provider/json.js.map +1 -0
  122. package/lib/agent-cli-provider/log-prefix.d.ts +2 -0
  123. package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -0
  124. package/lib/agent-cli-provider/log-prefix.js +22 -0
  125. package/lib/agent-cli-provider/log-prefix.js.map +1 -0
  126. package/lib/agent-cli-provider/process-runner.d.ts +17 -0
  127. package/lib/agent-cli-provider/process-runner.d.ts.map +1 -0
  128. package/lib/agent-cli-provider/process-runner.js +89 -0
  129. package/lib/agent-cli-provider/process-runner.js.map +1 -0
  130. package/lib/agent-cli-provider/redaction.d.ts +9 -0
  131. package/lib/agent-cli-provider/redaction.d.ts.map +1 -0
  132. package/lib/agent-cli-provider/redaction.js +227 -0
  133. package/lib/agent-cli-provider/redaction.js.map +1 -0
  134. package/lib/agent-cli-provider/schema.d.ts +5 -0
  135. package/lib/agent-cli-provider/schema.d.ts.map +1 -0
  136. package/lib/agent-cli-provider/schema.js +136 -0
  137. package/lib/agent-cli-provider/schema.js.map +1 -0
  138. package/lib/agent-cli-provider/single-agent-runtime.d.ts +15 -0
  139. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -0
  140. package/lib/agent-cli-provider/single-agent-runtime.js +228 -0
  141. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -0
  142. package/lib/agent-cli-provider/types.d.ts +194 -0
  143. package/lib/agent-cli-provider/types.d.ts.map +1 -0
  144. package/lib/agent-cli-provider/types.js +12 -0
  145. package/lib/agent-cli-provider/types.js.map +1 -0
  146. package/lib/completion.js +174 -0
  147. package/lib/detached-startup.js +220 -0
  148. package/lib/docker-config.js +220 -0
  149. package/lib/git-remote-utils.js +165 -0
  150. package/lib/id-detector.js +55 -0
  151. package/lib/provider-defaults.js +62 -0
  152. package/lib/provider-detection.js +59 -0
  153. package/lib/provider-names.js +57 -0
  154. package/lib/repo-settings.js +69 -0
  155. package/lib/settings/claude-auth.js +78 -0
  156. package/lib/settings.js +542 -0
  157. package/lib/start-cluster.js +321 -0
  158. package/lib/stream-json-parser.js +67 -0
  159. package/package.json +162 -0
  160. package/scripts/fix-node-pty-permissions.js +75 -0
  161. package/scripts/record-demo.sh +279 -0
  162. package/scripts/setup-merge-queue.sh +170 -0
  163. package/scripts/test-install.sh +40 -0
  164. package/scripts/validate-templates.js +107 -0
  165. package/src/agent/agent-config.js +266 -0
  166. package/src/agent/agent-context-builder.js +189 -0
  167. package/src/agent/agent-context-sections.js +338 -0
  168. package/src/agent/agent-context-sources.js +147 -0
  169. package/src/agent/agent-hook-executor.js +721 -0
  170. package/src/agent/agent-input-injector.js +141 -0
  171. package/src/agent/agent-lifecycle.js +982 -0
  172. package/src/agent/agent-quality-gate-schema.js +93 -0
  173. package/src/agent/agent-quality-gates-context.js +51 -0
  174. package/src/agent/agent-stuck-detector.js +256 -0
  175. package/src/agent/agent-task-executor.js +2028 -0
  176. package/src/agent/agent-trigger-evaluator.js +67 -0
  177. package/src/agent/context-metrics.js +160 -0
  178. package/src/agent/context-pack-builder.js +367 -0
  179. package/src/agent/context-replay-policy.js +51 -0
  180. package/src/agent/guidance-queue.js +77 -0
  181. package/src/agent/output-extraction.js +367 -0
  182. package/src/agent/output-reformatter.js +175 -0
  183. package/src/agent/pr-verification.js +653 -0
  184. package/src/agent/rate-limit-backoff.js +82 -0
  185. package/src/agent/schema-utils.js +146 -0
  186. package/src/agent/validation-platform.js +35 -0
  187. package/src/agent-cli-provider/adapters/claude-parser.ts +133 -0
  188. package/src/agent-cli-provider/adapters/claude-recovery.ts +203 -0
  189. package/src/agent-cli-provider/adapters/claude.ts +247 -0
  190. package/src/agent-cli-provider/adapters/codex-parser.ts +211 -0
  191. package/src/agent-cli-provider/adapters/codex.ts +217 -0
  192. package/src/agent-cli-provider/adapters/common.ts +124 -0
  193. package/src/agent-cli-provider/adapters/gemini.ts +243 -0
  194. package/src/agent-cli-provider/adapters/index.ts +126 -0
  195. package/src/agent-cli-provider/adapters/opencode.ts +286 -0
  196. package/src/agent-cli-provider/contract-actions.ts +150 -0
  197. package/src/agent-cli-provider/contract-env.ts +111 -0
  198. package/src/agent-cli-provider/contract-envelope.ts +110 -0
  199. package/src/agent-cli-provider/contract-errors.ts +66 -0
  200. package/src/agent-cli-provider/contract-fallback.ts +121 -0
  201. package/src/agent-cli-provider/contract-invoke.ts +104 -0
  202. package/src/agent-cli-provider/contract-options.ts +173 -0
  203. package/src/agent-cli-provider/contract-parse.ts +94 -0
  204. package/src/agent-cli-provider/contract-support.ts +167 -0
  205. package/src/agent-cli-provider/contract.ts +56 -0
  206. package/src/agent-cli-provider/env-safety.ts +82 -0
  207. package/src/agent-cli-provider/errors.ts +122 -0
  208. package/src/agent-cli-provider/executable.ts +24 -0
  209. package/src/agent-cli-provider/index.ts +83 -0
  210. package/src/agent-cli-provider/invoke-evidence.ts +18 -0
  211. package/src/agent-cli-provider/json.ts +114 -0
  212. package/src/agent-cli-provider/log-prefix.ts +20 -0
  213. package/src/agent-cli-provider/process-runner.ts +145 -0
  214. package/src/agent-cli-provider/redaction.ts +282 -0
  215. package/src/agent-cli-provider/schema.ts +115 -0
  216. package/src/agent-cli-provider/single-agent-runtime.ts +311 -0
  217. package/src/agent-cli-provider/types.ts +237 -0
  218. package/src/agent-wrapper.js +615 -0
  219. package/src/agents/git-pusher-template.js +705 -0
  220. package/src/attach/attach-client.js +438 -0
  221. package/src/attach/attach-server.js +543 -0
  222. package/src/attach/index.js +37 -0
  223. package/src/attach/protocol.js +220 -0
  224. package/src/attach/ring-buffer.js +121 -0
  225. package/src/attach/send-input.js +88 -0
  226. package/src/attach/socket-discovery.js +267 -0
  227. package/src/claude-task-runner.js +661 -0
  228. package/src/config-router.js +89 -0
  229. package/src/config-validator.js +2202 -0
  230. package/src/copy-worker.js +43 -0
  231. package/src/guidance-topics.js +10 -0
  232. package/src/input-helpers.js +65 -0
  233. package/src/isolation-manager.js +1734 -0
  234. package/src/issue-providers/README.md +305 -0
  235. package/src/issue-providers/azure-devops-provider.js +307 -0
  236. package/src/issue-providers/base-provider.js +232 -0
  237. package/src/issue-providers/github-provider.js +210 -0
  238. package/src/issue-providers/gitlab-provider.js +262 -0
  239. package/src/issue-providers/index.js +196 -0
  240. package/src/issue-providers/jira-provider.js +260 -0
  241. package/src/ledger.js +692 -0
  242. package/src/lib/gc.js +232 -0
  243. package/src/lib/safe-exec.js +88 -0
  244. package/src/logic-engine.js +201 -0
  245. package/src/message-buffer.js +81 -0
  246. package/src/message-bus-bridge.js +144 -0
  247. package/src/message-bus.js +256 -0
  248. package/src/name-generator.js +232 -0
  249. package/src/orchestrator.js +3924 -0
  250. package/src/preflight.js +712 -0
  251. package/src/process-metrics.js +608 -0
  252. package/src/providers/anthropic/index.js +3 -0
  253. package/src/providers/base-provider.js +355 -0
  254. package/src/providers/capabilities.js +60 -0
  255. package/src/providers/google/index.js +3 -0
  256. package/src/providers/index.js +293 -0
  257. package/src/providers/openai/index.js +3 -0
  258. package/src/providers/opencode/index.js +3 -0
  259. package/src/quality-gates.js +143 -0
  260. package/src/schemas/sub-cluster.js +208 -0
  261. package/src/state-snapshot.js +398 -0
  262. package/src/state-snapshotter.js +142 -0
  263. package/src/status-footer.js +1026 -0
  264. package/src/sub-cluster-wrapper.js +693 -0
  265. package/src/task-runner.js +30 -0
  266. package/src/template-resolver.js +425 -0
  267. package/src/template-validation/index.js +338 -0
  268. package/src/template-validation/simulate-consensus-gates.js +324 -0
  269. package/src/template-validation/simulate-random-topology.js +541 -0
  270. package/src/template-validation/simulate-two-stage-validation.js +270 -0
  271. package/src/worktree-claude-config.js +135 -0
  272. package/src/worktree-tooling-env.js +150 -0
  273. package/task-lib/attachable-watcher.js +381 -0
  274. package/task-lib/commands/clean.js +50 -0
  275. package/task-lib/commands/episodes.js +105 -0
  276. package/task-lib/commands/get-log-path.js +23 -0
  277. package/task-lib/commands/kill.js +32 -0
  278. package/task-lib/commands/list.js +105 -0
  279. package/task-lib/commands/logs.js +439 -0
  280. package/task-lib/commands/resume.js +42 -0
  281. package/task-lib/commands/run.js +57 -0
  282. package/task-lib/commands/schedule.js +105 -0
  283. package/task-lib/commands/scheduler-cmd.js +96 -0
  284. package/task-lib/commands/schedules.js +148 -0
  285. package/task-lib/commands/status.js +44 -0
  286. package/task-lib/commands/unschedule.js +16 -0
  287. package/task-lib/completion.js +9 -0
  288. package/task-lib/config.js +11 -0
  289. package/task-lib/name-generator.js +230 -0
  290. package/task-lib/package.json +3 -0
  291. package/task-lib/provider-helper-runtime.js +29 -0
  292. package/task-lib/runner.js +190 -0
  293. package/task-lib/scheduler.js +252 -0
  294. package/task-lib/store.js +529 -0
  295. package/task-lib/watcher.js +305 -0
@@ -0,0 +1,145 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { omitProcessControlEnv, omitUnsafeProviderEnv } from './env-safety';
3
+ import type { CommandSpec } from './types';
4
+
5
+ const DEFAULT_TIMEOUT_KILL_GRACE_MS = 100;
6
+ const PROVIDER_STDIO: ['ignore', 'pipe', 'pipe'] = ['ignore', 'pipe', 'pipe'];
7
+
8
+ export interface ProcessRunnerOptions {
9
+ readonly timeoutMs?: number;
10
+ readonly timeoutKillGraceMs?: number;
11
+ }
12
+
13
+ export interface ProcessResult {
14
+ readonly stdout: string;
15
+ readonly stderr: string;
16
+ readonly exitCode: number | null;
17
+ readonly signal: string | null;
18
+ readonly durationMs: number;
19
+ readonly timedOut?: boolean;
20
+ readonly timeoutMs?: number;
21
+ }
22
+
23
+ export type ProcessRunner = (
24
+ commandSpec: CommandSpec,
25
+ options?: ProcessRunnerOptions
26
+ ) => Promise<ProcessResult>;
27
+
28
+ interface TimeoutState {
29
+ timedOut: boolean;
30
+ timeout?: NodeJS.Timeout;
31
+ timeoutKill?: NodeJS.Timeout;
32
+ }
33
+
34
+ function clearTimeoutState(state: TimeoutState): void {
35
+ if (state.timeout !== undefined) clearTimeout(state.timeout);
36
+ if (state.timeoutKill !== undefined) clearTimeout(state.timeoutKill);
37
+ }
38
+
39
+ function armTimeout(
40
+ child: ReturnType<typeof spawn>,
41
+ options: ProcessRunnerOptions,
42
+ state: TimeoutState,
43
+ isSettled: () => boolean
44
+ ): void {
45
+ if (options.timeoutMs === undefined) return;
46
+ state.timeout = setTimeout(() => {
47
+ state.timedOut = true;
48
+ child.kill('SIGTERM');
49
+ state.timeoutKill = setTimeout(() => {
50
+ if (!isSettled()) child.kill('SIGKILL');
51
+ }, options.timeoutKillGraceMs ?? DEFAULT_TIMEOUT_KILL_GRACE_MS);
52
+ }, options.timeoutMs);
53
+ }
54
+
55
+ function processResult(input: {
56
+ readonly stdout: readonly Buffer[];
57
+ readonly stderr: readonly Buffer[];
58
+ readonly exitCode: number | null;
59
+ readonly signal: string | null;
60
+ readonly startedAt: number;
61
+ readonly state: TimeoutState;
62
+ readonly timeoutMs?: number;
63
+ }): ProcessResult {
64
+ return {
65
+ stdout: Buffer.concat(input.stdout).toString('utf8'),
66
+ stderr: Buffer.concat(input.stderr).toString('utf8'),
67
+ exitCode: input.exitCode,
68
+ signal: input.signal,
69
+ durationMs: Date.now() - input.startedAt,
70
+ timedOut: input.state.timedOut,
71
+ ...(input.state.timedOut && input.timeoutMs !== undefined
72
+ ? { timeoutMs: input.timeoutMs }
73
+ : {}),
74
+ };
75
+ }
76
+
77
+ function spawnOptions(commandSpec: CommandSpec): {
78
+ readonly shell: false;
79
+ readonly env: NodeJS.ProcessEnv;
80
+ readonly stdio: ['ignore', 'pipe', 'pipe'];
81
+ cwd?: string;
82
+ } {
83
+ const options: {
84
+ readonly shell: false;
85
+ readonly env: NodeJS.ProcessEnv;
86
+ readonly stdio: ['ignore', 'pipe', 'pipe'];
87
+ cwd?: string;
88
+ } = {
89
+ shell: false,
90
+ env: { ...omitProcessControlEnv(process.env), ...omitUnsafeProviderEnv(commandSpec.env) },
91
+ stdio: PROVIDER_STDIO,
92
+ };
93
+ if (commandSpec.cwd !== undefined) options.cwd = commandSpec.cwd;
94
+ return options;
95
+ }
96
+
97
+ function attachOutputCollectors(
98
+ child: ReturnType<typeof spawn>,
99
+ stdout: Buffer[],
100
+ stderr: Buffer[]
101
+ ): void {
102
+ child.stdout?.on('data', (chunk: Buffer) => stdout.push(chunk));
103
+ child.stderr?.on('data', (chunk: Buffer) => stderr.push(chunk));
104
+ }
105
+
106
+ export function spawnProcessRunner(): ProcessRunner {
107
+ return (commandSpec, options = {}) =>
108
+ new Promise<ProcessResult>((resolve, reject) => {
109
+ const startedAt = Date.now();
110
+ const stdout: Buffer[] = [];
111
+ const stderr: Buffer[] = [];
112
+ const child = spawn(commandSpec.binary, [...commandSpec.args], spawnOptions(commandSpec));
113
+ const timeoutState: TimeoutState = { timedOut: false };
114
+ let settled = false;
115
+
116
+ function settle(): boolean {
117
+ if (settled) return false;
118
+ settled = true;
119
+ clearTimeoutState(timeoutState);
120
+ return true;
121
+ }
122
+
123
+ attachOutputCollectors(child, stdout, stderr);
124
+ child.once('error', (error) => {
125
+ if (settle()) reject(error);
126
+ });
127
+ child.once('close', (exitCode, signal) => {
128
+ if (settle()) {
129
+ resolve(
130
+ processResult({
131
+ stdout,
132
+ stderr,
133
+ exitCode,
134
+ signal,
135
+ startedAt,
136
+ state: timeoutState,
137
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
138
+ })
139
+ );
140
+ }
141
+ });
142
+
143
+ armTimeout(child, options, timeoutState, () => settled);
144
+ });
145
+ }
@@ -0,0 +1,282 @@
1
+ import { isRecord } from './json';
2
+ import type { RedactionMetadata } from './types';
3
+
4
+ const SECRET_KEY_PATTERN = /TOKEN|KEY|SECRET|PASSWORD/i;
5
+ const REDACTED_PREFIX = '[REDACTED:';
6
+ const SECRET_VALUE_PATTERNS: readonly RegExp[] = [
7
+ /\b(?:sk|rk|pk)-[A-Za-z0-9][A-Za-z0-9._-]{5,}\b/g,
8
+ /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
9
+ /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g,
10
+ /\bxox[baprs]?-[A-Za-z0-9-]{10,}\b/g,
11
+ /\bAIza[A-Za-z0-9_-]{20,}\b/g,
12
+ ];
13
+ const UNTRUSTED_OUTPUT_PATHS = new Set([
14
+ 'error.message',
15
+ 'evidence.message',
16
+ 'evidence.code',
17
+ 'result.evidence.message',
18
+ 'result.evidence.code',
19
+ 'result.evidence.stdout',
20
+ 'result.evidence.stderr',
21
+ ]);
22
+
23
+ interface RedactionTarget {
24
+ readonly kind: RedactionMetadata['kind'];
25
+ readonly key: string;
26
+ readonly source?: string;
27
+ readonly scope: 'env-or-untrusted' | 'global' | 'untrusted';
28
+ readonly value: string;
29
+ }
30
+
31
+ export interface RedactionResult {
32
+ readonly value: unknown;
33
+ readonly redactions: readonly RedactionMetadata[];
34
+ }
35
+
36
+ function metadataFor(target: RedactionTarget): RedactionMetadata {
37
+ if (target.source === undefined) return { kind: target.kind, key: target.key };
38
+ return { kind: target.kind, key: target.key, source: target.source };
39
+ }
40
+
41
+ function metadataKey(metadata: RedactionMetadata): string {
42
+ return `${metadata.kind}:${metadata.key}:${metadata.source ?? ''}`;
43
+ }
44
+
45
+ function dedupeMetadata(metadata: readonly RedactionMetadata[]): readonly RedactionMetadata[] {
46
+ const seen = new Set<string>();
47
+ const result: RedactionMetadata[] = [];
48
+ for (const item of metadata) {
49
+ const key = metadataKey(item);
50
+ if (seen.has(key)) continue;
51
+ seen.add(key);
52
+ result.push(item);
53
+ }
54
+ return result;
55
+ }
56
+
57
+ function targetsFromEnv(env: Readonly<Record<string, string>>): readonly RedactionTarget[] {
58
+ return Object.entries(env)
59
+ .filter(([, value]) => value.length > 0)
60
+ .map(([key, value]) => ({ kind: 'env', key, scope: 'env-or-untrusted', value }));
61
+ }
62
+
63
+ function shouldRedactSecretKey(key: string): boolean {
64
+ return SECRET_KEY_PATTERN.test(key);
65
+ }
66
+
67
+ function isRedactionMetadataRecord(value: unknown): boolean {
68
+ if (!isRecord(value)) return false;
69
+ const keys = Object.keys(value);
70
+ if (!keys.every((key) => key === 'kind' || key === 'key' || key === 'source')) return false;
71
+ if (value.kind !== 'env' && value.kind !== 'secret-key' && value.kind !== 'secret-value') {
72
+ return false;
73
+ }
74
+ if (typeof value.key !== 'string') return false;
75
+ return value.source === undefined || typeof value.source === 'string';
76
+ }
77
+
78
+ function isRedactionMetadataPath(path: string): boolean {
79
+ return /(^|\.)redactions\[\d+\]$/.test(path);
80
+ }
81
+
82
+ function shouldPreserveRedactionMetadataRecord(value: unknown, path: string): boolean {
83
+ return isRedactionMetadataRecord(value) && isRedactionMetadataPath(path);
84
+ }
85
+
86
+ function isCredentialPresenceRecord(value: unknown): boolean {
87
+ if (!isRecord(value)) return false;
88
+ const keys = Object.keys(value);
89
+ return (
90
+ keys.length === 2 &&
91
+ typeof value.key === 'string' &&
92
+ typeof value.present === 'boolean' &&
93
+ keys.every((key) => key === 'key' || key === 'present')
94
+ );
95
+ }
96
+
97
+ function isProbeCredentialPresencePath(path: string): boolean {
98
+ return /^result\.credentials\[\d+\]$/.test(path);
99
+ }
100
+
101
+ function shouldPreserveCredentialPresenceRecord(value: unknown, path: string): boolean {
102
+ return isCredentialPresenceRecord(value) && isProbeCredentialPresencePath(path);
103
+ }
104
+
105
+ function collectSecretKeyTargets(value: unknown, path = ''): readonly RedactionTarget[] {
106
+ if (Array.isArray(value)) {
107
+ return value.flatMap((item, index) => collectSecretKeyTargets(item, `${path}[${index}]`));
108
+ }
109
+ if (!isRecord(value)) return [];
110
+ if (shouldPreserveRedactionMetadataRecord(value, path)) return [];
111
+ if (shouldPreserveCredentialPresenceRecord(value, path)) return [];
112
+
113
+ const targets: RedactionTarget[] = [];
114
+ for (const [key, item] of Object.entries(value)) {
115
+ const keyPath = path ? `${path}.${key}` : key;
116
+ if (typeof item === 'string' && item.length > 0 && shouldRedactSecretKey(key)) {
117
+ targets.push({ kind: 'secret-key', key: keyPath, scope: 'untrusted', value: item });
118
+ }
119
+ targets.push(...collectSecretKeyTargets(item, keyPath));
120
+ }
121
+ return targets;
122
+ }
123
+
124
+ function collectSecretValueTargets(value: unknown, path = ''): readonly RedactionTarget[] {
125
+ if (typeof value === 'string') {
126
+ return SECRET_VALUE_PATTERNS.flatMap((pattern) =>
127
+ collectSecretValuePatternTargets(value, path, pattern)
128
+ );
129
+ }
130
+ if (Array.isArray(value)) {
131
+ return value.flatMap((item, index) => collectSecretValueTargets(item, `${path}[${index}]`));
132
+ }
133
+ if (!isRecord(value)) return [];
134
+ if (shouldPreserveRedactionMetadataRecord(value, path)) return [];
135
+ if (shouldPreserveCredentialPresenceRecord(value, path)) return [];
136
+
137
+ return Object.entries(value).flatMap(([key, item]) => {
138
+ const keyPath = path ? `${path}.${key}` : key;
139
+ return collectSecretValueTargets(item, keyPath);
140
+ });
141
+ }
142
+
143
+ function collectSecretValuePatternTargets(
144
+ value: string,
145
+ path: string,
146
+ pattern: RegExp
147
+ ): readonly RedactionTarget[] {
148
+ const targets: RedactionTarget[] = [];
149
+ pattern.lastIndex = 0;
150
+ let match = pattern.exec(value);
151
+ while (match !== null) {
152
+ const secret = match[0];
153
+ if (secret) {
154
+ targets.push({
155
+ kind: 'secret-value',
156
+ key: path || 'value',
157
+ scope: 'global',
158
+ value: secret,
159
+ });
160
+ }
161
+ match = pattern.exec(value);
162
+ }
163
+ return targets;
164
+ }
165
+
166
+ function applyTargets(input: string, targets: readonly RedactionTarget[]): string {
167
+ let output = input;
168
+ for (const target of targets) {
169
+ if (!target.value) continue;
170
+ output = output.split(target.value).join(`${REDACTED_PREFIX}${target.key}]`);
171
+ }
172
+ return output;
173
+ }
174
+
175
+ function envTargetPathKey(target: RedactionTarget): string {
176
+ return target.key.split(':', 1)[0] ?? target.key;
177
+ }
178
+
179
+ function isEnvValuePath(path: string, target: RedactionTarget): boolean {
180
+ const key = envTargetPathKey(target);
181
+ return path === `env.${key}` || path.endsWith(`.env.${key}`);
182
+ }
183
+
184
+ function isUntrustedOutputPath(path: string): boolean {
185
+ if (path.length === 0 || path === 'value') return true;
186
+ return (
187
+ UNTRUSTED_OUTPUT_PATHS.has(path) ||
188
+ path.startsWith('result.events') ||
189
+ path.startsWith('result.diagnostics')
190
+ );
191
+ }
192
+
193
+ function shouldApplyTargetAtPath(target: RedactionTarget, path: string): boolean {
194
+ if (target.scope === 'global') return true;
195
+ if (target.scope === 'untrusted') return isUntrustedOutputPath(path);
196
+ return isEnvValuePath(path, target) || isUntrustedOutputPath(path);
197
+ }
198
+
199
+ function applyTargetsAtPath(
200
+ input: string,
201
+ targets: readonly RedactionTarget[],
202
+ path: string
203
+ ): string {
204
+ return applyTargets(
205
+ input,
206
+ targets.filter((target) => shouldApplyTargetAtPath(target, path))
207
+ );
208
+ }
209
+
210
+ function redactValue(value: unknown, targets: readonly RedactionTarget[], path = ''): unknown {
211
+ if (typeof value === 'string') return applyTargetsAtPath(value, targets, path);
212
+ if (Array.isArray(value)) {
213
+ return value.map((item, index) => redactValue(item, targets, `${path}[${index}]`));
214
+ }
215
+ if (!isRecord(value)) return value;
216
+ if (shouldPreserveRecord(value, path)) return value;
217
+ return redactRecord(value, targets, path);
218
+ }
219
+
220
+ function shouldPreserveRecord(value: Record<string, unknown>, path: string): boolean {
221
+ return (
222
+ shouldPreserveRedactionMetadataRecord(value, path) ||
223
+ shouldPreserveCredentialPresenceRecord(value, path)
224
+ );
225
+ }
226
+
227
+ function redactRecord(
228
+ value: Record<string, unknown>,
229
+ targets: readonly RedactionTarget[],
230
+ path: string
231
+ ): Record<string, unknown> {
232
+ const redacted: Record<string, unknown> = {};
233
+ for (const [key, item] of Object.entries(value)) {
234
+ const keyPath = path ? `${path}.${key}` : key;
235
+ redacted[key] = redactRecordItem(key, item, targets, keyPath);
236
+ }
237
+ return redacted;
238
+ }
239
+
240
+ function redactRecordItem(
241
+ key: string,
242
+ item: unknown,
243
+ targets: readonly RedactionTarget[],
244
+ path: string
245
+ ): unknown {
246
+ if (typeof item === 'string' && item.length > 0 && shouldRedactSecretKey(key)) {
247
+ return `${REDACTED_PREFIX}${key}]`;
248
+ }
249
+ return redactValue(item, targets, path);
250
+ }
251
+
252
+ export function redactString(
253
+ value: string,
254
+ env: Readonly<Record<string, string>> = {}
255
+ ): RedactionResult {
256
+ const targets = targetsFromEnv(env);
257
+ return {
258
+ value: applyTargetsAtPath(value, targets, 'value'),
259
+ redactions: dedupeMetadata(targets.map(metadataFor)),
260
+ };
261
+ }
262
+
263
+ export function redactObject(
264
+ value: unknown,
265
+ env: Readonly<Record<string, string>> = {}
266
+ ): RedactionResult {
267
+ const targets = [
268
+ ...targetsFromEnv(env),
269
+ ...collectSecretKeyTargets(value),
270
+ ...collectSecretValueTargets(value),
271
+ ];
272
+ return {
273
+ value: redactValue(value, targets),
274
+ redactions: dedupeMetadata(targets.map(metadataFor)),
275
+ };
276
+ }
277
+
278
+ export function mergeRedactions(
279
+ ...metadataGroups: readonly (readonly RedactionMetadata[] | undefined)[]
280
+ ): readonly RedactionMetadata[] {
281
+ return dedupeMetadata(metadataGroups.flatMap((metadata) => metadata ?? []));
282
+ }
@@ -0,0 +1,115 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+
6
+ import { parseJson, stringifyJson } from './json';
7
+
8
+ type SchemaObject = Record<string, unknown> | readonly unknown[];
9
+
10
+ function isSchemaObject(schema: unknown): schema is SchemaObject {
11
+ return typeof schema === 'object' && schema !== null;
12
+ }
13
+
14
+ function shallowCopySchemaObject(schema: SchemaObject): Record<string, unknown> {
15
+ const result: Record<string, unknown> = {};
16
+ for (const [key, value] of Object.entries(schema)) {
17
+ result[key] = value;
18
+ }
19
+ return result;
20
+ }
21
+
22
+ function objectKeys(schema: unknown): string[] {
23
+ return isSchemaObject(schema) ? Object.keys(schema) : [];
24
+ }
25
+
26
+ function enforceSchemaNode(schema: unknown): unknown {
27
+ if (!isSchemaObject(schema)) {
28
+ return schema;
29
+ }
30
+
31
+ const result = shallowCopySchemaObject(schema);
32
+ enforceObjectSchema(result);
33
+ enforceProperties(result);
34
+ enforceItems(result);
35
+ enforceCompositionKeywords(result);
36
+ enforceAdditionalProperties(result);
37
+ return result;
38
+ }
39
+
40
+ function enforceObjectSchema(result: Record<string, unknown>): void {
41
+ if (result.type === 'object') {
42
+ result.additionalProperties = false;
43
+ if (result.properties) {
44
+ result.required = objectKeys(result.properties);
45
+ }
46
+ }
47
+ }
48
+
49
+ function enforceProperties(result: Record<string, unknown>): void {
50
+ const properties = result.properties;
51
+ if (isSchemaObject(properties)) {
52
+ const strictProperties: Record<string, unknown> = {};
53
+ for (const [key, value] of Object.entries(properties)) {
54
+ strictProperties[key] = enforceSchemaNode(value);
55
+ }
56
+ result.properties = strictProperties;
57
+ }
58
+ }
59
+
60
+ function enforceItems(result: Record<string, unknown>): void {
61
+ if (result.items) {
62
+ result.items = enforceSchemaNode(result.items);
63
+ }
64
+ }
65
+
66
+ function enforceCompositionKeywords(result: Record<string, unknown>): void {
67
+ for (const key of ['anyOf', 'oneOf', 'allOf']) {
68
+ const value = result[key];
69
+ if (Array.isArray(value)) {
70
+ result[key] = value.map((item) => enforceSchemaNode(item));
71
+ }
72
+ }
73
+ }
74
+
75
+ function enforceAdditionalProperties(result: Record<string, unknown>): void {
76
+ const additionalProperties = result.additionalProperties;
77
+ if (additionalProperties && isSchemaObject(additionalProperties)) {
78
+ result.additionalProperties = enforceSchemaNode(additionalProperties);
79
+ }
80
+ }
81
+
82
+ export function enforceOpenAIStrictSchema(schema: unknown): unknown {
83
+ return enforceSchemaNode(schema);
84
+ }
85
+
86
+ export function schemaToPromptString(schema: unknown): string {
87
+ return typeof schema === 'string' ? schema : stringifyJson(schema, 2);
88
+ }
89
+
90
+ export function appendJsonSchemaPrompt(context: string, schema: unknown): string {
91
+ const schemaString = schemaToPromptString(schema);
92
+ return (
93
+ context +
94
+ `\n\n## OUTPUT FORMAT (CRITICAL - REQUIRED)
95
+
96
+ You MUST respond with a JSON object that exactly matches this schema. NO markdown, NO explanation, NO code blocks. ONLY the raw JSON object.
97
+
98
+ Schema:
99
+ \`\`\`json
100
+ ${schemaString}
101
+ \`\`\`
102
+
103
+ Your response must be ONLY valid JSON. Start with { and end with }. Nothing else.`
104
+ );
105
+ }
106
+
107
+ export function writeStrictOutputSchemaFile(schema: unknown): string {
108
+ const parsedSchema = typeof schema === 'string' ? parseJson(schema) : schema;
109
+ const strictSchema = enforceOpenAIStrictSchema(parsedSchema);
110
+ const schemaText = stringifyJson(strictSchema, 2);
111
+ const schemaDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zeroshot-schema-'));
112
+ const schemaFile = path.join(schemaDir, `${randomUUID()}.json`);
113
+ fs.writeFileSync(schemaFile, schemaText, { flag: 'wx', mode: 0o600 });
114
+ return schemaFile;
115
+ }