@atolis-hq/wake 0.1.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 (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +140 -0
  3. package/dist/src/adapters/claude/claude-runner.js +388 -0
  4. package/dist/src/adapters/claude/prompt-templates.js +57 -0
  5. package/dist/src/adapters/codex/codex-runner.js +391 -0
  6. package/dist/src/adapters/cursor/cursor-runner.js +352 -0
  7. package/dist/src/adapters/docker/docker-cli.js +97 -0
  8. package/dist/src/adapters/fake/fake-artifact-verifier.js +14 -0
  9. package/dist/src/adapters/fake/fake-github-pull-request-activity-source.js +73 -0
  10. package/dist/src/adapters/fake/fake-resource-index.js +21 -0
  11. package/dist/src/adapters/fake/fake-runner.js +22 -0
  12. package/dist/src/adapters/fake/fake-ticketing-system.js +166 -0
  13. package/dist/src/adapters/fake/fake-workspace-manager.js +27 -0
  14. package/dist/src/adapters/fs/resource-index.js +84 -0
  15. package/dist/src/adapters/fs/self-update-ledger.js +17 -0
  16. package/dist/src/adapters/fs/state-store.js +364 -0
  17. package/dist/src/adapters/git/git-workspace-manager.js +168 -0
  18. package/dist/src/adapters/github/github-artifact-verifier.js +38 -0
  19. package/dist/src/adapters/github/github-auth.js +16 -0
  20. package/dist/src/adapters/github/github-client.js +100 -0
  21. package/dist/src/adapters/github/github-issues-work-source.js +410 -0
  22. package/dist/src/adapters/github/github-pull-request-activity-source.js +263 -0
  23. package/dist/src/adapters/http/ui-assets.js +302 -0
  24. package/dist/src/adapters/http/ui-data.js +389 -0
  25. package/dist/src/adapters/http/ui-server.js +151 -0
  26. package/dist/src/adapters/runner/cli-command.js +43 -0
  27. package/dist/src/adapters/runner/prompt-templates.js +76 -0
  28. package/dist/src/adapters/runner/runner-cli-adapter.js +112 -0
  29. package/dist/src/adapters/runner/runner-registry.js +141 -0
  30. package/dist/src/adapters/runner/stage-prompt.js +256 -0
  31. package/dist/src/adapters/runner/transcripts.js +25 -0
  32. package/dist/src/cli/correlate-command.js +62 -0
  33. package/dist/src/cli/init-command.js +11 -0
  34. package/dist/src/cli/locks-command.js +19 -0
  35. package/dist/src/cli/sandbox-command.js +191 -0
  36. package/dist/src/cli/sandbox-logging.js +30 -0
  37. package/dist/src/cli/sandbox-resume.js +77 -0
  38. package/dist/src/cli/scaffold-assets.js +173 -0
  39. package/dist/src/cli/self-update-command.js +158 -0
  40. package/dist/src/cli/startup-preflight.js +127 -0
  41. package/dist/src/cli/stop-command.js +42 -0
  42. package/dist/src/cli/ui-command.js +27 -0
  43. package/dist/src/config/defaults.js +11 -0
  44. package/dist/src/config/load-config.js +26 -0
  45. package/dist/src/core/contracts.js +1 -0
  46. package/dist/src/core/control-plane.js +127 -0
  47. package/dist/src/core/lifecycle-service.js +8 -0
  48. package/dist/src/core/policy-engine.js +200 -0
  49. package/dist/src/core/projection-updater.js +438 -0
  50. package/dist/src/core/quota-backoff.js +69 -0
  51. package/dist/src/core/sink-router.js +85 -0
  52. package/dist/src/core/tick-runner.js +1347 -0
  53. package/dist/src/domain/branch-naming.js +14 -0
  54. package/dist/src/domain/resource-uri.js +38 -0
  55. package/dist/src/domain/runner-routing.js +108 -0
  56. package/dist/src/domain/schema.js +685 -0
  57. package/dist/src/domain/sources.js +16 -0
  58. package/dist/src/domain/stages.js +39 -0
  59. package/dist/src/domain/types.js +1 -0
  60. package/dist/src/domain/workflows.js +83 -0
  61. package/dist/src/lib/clock.js +5 -0
  62. package/dist/src/lib/event-log.js +21 -0
  63. package/dist/src/lib/json-file.js +23 -0
  64. package/dist/src/lib/lock.js +118 -0
  65. package/dist/src/lib/paths.js +44 -0
  66. package/dist/src/lib/work-id.js +19 -0
  67. package/dist/src/main.js +523 -0
  68. package/dist/src/version.js +1 -0
  69. package/docker/Dockerfile +54 -0
  70. package/docker/entrypoint.sh +75 -0
  71. package/docker/log-command.sh +107 -0
  72. package/docker/setup.sh +72 -0
  73. package/package.json +52 -0
  74. package/prompts/implement.md +49 -0
  75. package/prompts/refine.md +44 -0
@@ -0,0 +1,391 @@
1
+ import { buildStagePrompt } from '../runner/stage-prompt.js';
2
+ import { runAgentCliCommand } from '../runner/cli-command.js';
3
+ import { writeRunnerTranscript } from '../runner/transcripts.js';
4
+ import { parseRunnerResult } from '../../domain/schema.js';
5
+ const CODEX_CLI_NAME = 'Codex';
6
+ export function buildCodexExecArgs(input) {
7
+ const prompt = buildCodexPromptText(input);
8
+ return [
9
+ '--ask-for-approval',
10
+ 'never',
11
+ ...(input.reasoningEffort === undefined
12
+ ? []
13
+ : ['-c', `model_reasoning_effort="${input.reasoningEffort}"`]),
14
+ 'exec',
15
+ '--json',
16
+ '--skip-git-repo-check',
17
+ '--sandbox',
18
+ input.sandboxMode,
19
+ '--cd',
20
+ input.cwd,
21
+ '--model',
22
+ input.model,
23
+ prompt,
24
+ ];
25
+ }
26
+ export function buildCodexPromptText(input) {
27
+ return input.harnessPrompt === undefined
28
+ ? input.prompt
29
+ : `${input.harnessPrompt}\n\n${input.prompt}`;
30
+ }
31
+ export function buildCodexResumeArgs(input) {
32
+ return ['resume', input.sessionId];
33
+ }
34
+ function compactLogValue(value) {
35
+ return value.replace(/\s+/g, ' ').trim();
36
+ }
37
+ export function formatCodexRunLogLine(input) {
38
+ const parts = [
39
+ '[codex-run]',
40
+ `phase=${input.phase}`,
41
+ `runId=${input.runId}`,
42
+ `cli=Codex`,
43
+ ...(input.model === undefined ? [] : [`model=${input.model}`]),
44
+ `repo=${input.repo}`,
45
+ `issueNumber=${input.issueNumber}`,
46
+ `action=${input.action}`,
47
+ `recentEventIds=${input.recentEventIds.length > 0 ? input.recentEventIds.join(',') : '(none)'}`,
48
+ ...(input.workspacePath === undefined
49
+ ? []
50
+ : [`workspacePath=${compactLogValue(input.workspacePath)}`]),
51
+ ...(input.sessionId === undefined ? [] : [`sessionId=${input.sessionId}`]),
52
+ ...(input.exitCode === undefined ? [] : [`exitCode=${input.exitCode}`]),
53
+ ];
54
+ return parts.join(' ');
55
+ }
56
+ export function extractCodexExecResult(stdout) {
57
+ let result;
58
+ let sessionId;
59
+ let turns = 0;
60
+ let inputTokens = 0;
61
+ let outputTokens = 0;
62
+ let cachedInputTokens = 0;
63
+ let sawUsage = false;
64
+ for (const line of stdout.split(/\r?\n/).filter((entry) => entry.trim().length > 0)) {
65
+ const event = JSON.parse(line);
66
+ if (event.type === 'thread.started' && typeof event.thread_id === 'string') {
67
+ sessionId = event.thread_id;
68
+ continue;
69
+ }
70
+ if (event.type === 'item.completed') {
71
+ const item = event.item;
72
+ if (item?.type === 'agent_message' && typeof item.text === 'string') {
73
+ result = item.text;
74
+ }
75
+ continue;
76
+ }
77
+ if (event.type === 'turn.completed') {
78
+ const usage = event.usage;
79
+ if (usage !== undefined) {
80
+ sawUsage = true;
81
+ turns += 1;
82
+ // Each `turn.completed` reports that turn's own usage (an exec run can
83
+ // make several model turns via tool calls), so accumulate across turns
84
+ // rather than keeping only the last one - otherwise a multi-turn run's
85
+ // reported usage undercounts the actual spend (#135).
86
+ inputTokens += typeof usage.input_tokens === 'number' ? usage.input_tokens : 0;
87
+ outputTokens += typeof usage.output_tokens === 'number' ? usage.output_tokens : 0;
88
+ // OpenAI's Responses API reports cached prompt tokens nested under
89
+ // input_tokens_details.cached_tokens; unverified against a live Codex
90
+ // success response (this environment's Codex quota was exhausted while
91
+ // this adapter was written), so it is read defensively and simply
92
+ // omitted if absent rather than assumed to exist.
93
+ const inputTokensDetails = usage.input_tokens_details;
94
+ if (typeof inputTokensDetails?.cached_tokens === 'number') {
95
+ cachedInputTokens += inputTokensDetails.cached_tokens;
96
+ }
97
+ }
98
+ }
99
+ }
100
+ if (result === undefined) {
101
+ throw new Error('Codex exec JSONL stream did not include a final agent message.');
102
+ }
103
+ const tokenUsage = sawUsage
104
+ ? {
105
+ inputTokens,
106
+ outputTokens,
107
+ turns,
108
+ ...(cachedInputTokens > 0 ? { cacheReadInputTokens: cachedInputTokens } : {}),
109
+ }
110
+ : undefined;
111
+ return {
112
+ result,
113
+ ...(sessionId === undefined ? {} : { sessionId }),
114
+ ...(tokenUsage === undefined ? {} : { tokenUsage }),
115
+ };
116
+ }
117
+ // Codex exec emits a JSONL stream even on failure (unlike a bare CLI crash,
118
+ // which produces no JSON at all). Reading the structured `message` field off
119
+ // `error`/`turn.failed` events is more reliable than grepping the raw stdout
120
+ // blob, and it is also the string that carries the actual reset-time hint
121
+ // (e.g. "...or try again at 2:29 PM") that quota-backoff needs.
122
+ export function extractCodexErrorMessage(stdout) {
123
+ for (const line of stdout.split(/\r?\n/)) {
124
+ const trimmed = line.trim();
125
+ if (trimmed.length === 0) {
126
+ continue;
127
+ }
128
+ let event;
129
+ try {
130
+ event = JSON.parse(trimmed);
131
+ }
132
+ catch {
133
+ continue;
134
+ }
135
+ if (event.type === 'error' && typeof event.message === 'string') {
136
+ return event.message;
137
+ }
138
+ if (event.type === 'turn.failed') {
139
+ const error = event.error;
140
+ if (typeof error?.message === 'string') {
141
+ return error.message;
142
+ }
143
+ }
144
+ }
145
+ return undefined;
146
+ }
147
+ export function classifyCodexCliFailure(input) {
148
+ if (input.timedOut) {
149
+ return 'infra';
150
+ }
151
+ const structuredMessage = extractCodexErrorMessage(input.stdout);
152
+ const text = (structuredMessage ?? `${input.stderr}\n${input.stdout}`).toLowerCase();
153
+ if (text.includes('usage limit') ||
154
+ text.includes('rate limit') ||
155
+ text.includes('quota') ||
156
+ text.includes('billing') ||
157
+ text.includes('credit') ||
158
+ text.includes('too many requests') ||
159
+ text.includes('unauthorized') ||
160
+ text.includes('authentication')) {
161
+ return 'quota';
162
+ }
163
+ return 'infra';
164
+ }
165
+ function resolveModel(input) {
166
+ const { models, model } = input.settings;
167
+ const actionSpecificModel = models[input.action];
168
+ if (actionSpecificModel !== undefined) {
169
+ return actionSpecificModel;
170
+ }
171
+ if (models.default !== undefined) {
172
+ return models.default;
173
+ }
174
+ return model;
175
+ }
176
+ function readSandboxLogBreadcrumb() {
177
+ const containerName = process.env.WAKE_SANDBOX_CONTAINER_NAME;
178
+ if (containerName === undefined || containerName.length === 0) {
179
+ return null;
180
+ }
181
+ return {
182
+ text: `Sandbox logs: docker logs --tail 200 ${containerName}`,
183
+ metadata: {
184
+ sandboxContainerName: containerName,
185
+ },
186
+ };
187
+ }
188
+ function resolveSandboxMode(input) {
189
+ return input.workspaceMode === 'branch' ? 'danger-full-access' : 'workspace-write';
190
+ }
191
+ // Codex uses shell execution rather than Claude Code's named tools (Read/Glob/Grep).
192
+ // For read-only stages, override the tool capability note so the agent is told what it
193
+ // can actually use instead of Claude-specific tool names it does not have.
194
+ export function buildCodexToolCapabilityNote(input) {
195
+ if (input.workspaceMode !== 'read-only') {
196
+ return undefined;
197
+ }
198
+ const note = 'You may read the repository using standard shell commands (cat, ls, find, grep, head, tail) and git status. ' +
199
+ 'The workspace-write sandbox prevents write and edit operations — do not attempt to modify files.';
200
+ return input.mode === 'resume' ? `Reminder: this is still a planning-only stage. ${note}` : note;
201
+ }
202
+ export function createCodexRunner(options) {
203
+ return {
204
+ async run(input) {
205
+ const runMode = 'start';
206
+ const toolCapabilityNote = buildCodexToolCapabilityNote({
207
+ ...(input.workspaceMode === undefined ? {} : { workspaceMode: input.workspaceMode }),
208
+ mode: runMode,
209
+ });
210
+ const stagePrompt = await buildStagePrompt({
211
+ action: input.action,
212
+ projection: input.projection,
213
+ mode: runMode,
214
+ config: input.config,
215
+ ...(input.workspaceMode === undefined ? {} : { workspaceMode: input.workspaceMode }),
216
+ ...(input.mergeConflictDetected === true ? { mergeConflictDetected: true } : {}),
217
+ ...(input.upstreamChanges === undefined ? {} : { upstreamChanges: input.upstreamChanges }),
218
+ ...(toolCapabilityNote !== undefined ? { contextOverrides: { toolCapabilityNote } } : {}),
219
+ });
220
+ const model = resolveModel({
221
+ action: input.action,
222
+ settings: options.settings,
223
+ });
224
+ const cwd = input.workspacePath ?? options.cwd;
225
+ const sandboxMode = resolveSandboxMode({
226
+ ...(input.workspaceMode === undefined ? {} : { workspaceMode: input.workspaceMode }),
227
+ });
228
+ const promptText = buildCodexPromptText({
229
+ prompt: stagePrompt.prompt,
230
+ harnessPrompt: stagePrompt.harnessPrompt,
231
+ });
232
+ const promptTranscriptPath = await writeRunnerTranscript({
233
+ config: input.config,
234
+ projection: input.projection,
235
+ runId: input.runId,
236
+ action: input.action,
237
+ cli: CODEX_CLI_NAME,
238
+ kind: 'prompt',
239
+ text: promptText,
240
+ });
241
+ console.log(formatCodexRunLogLine({
242
+ phase: 'start',
243
+ runId: input.runId,
244
+ action: input.action,
245
+ issueNumber: input.projection.issue.number,
246
+ repo: input.projection.issue.repo,
247
+ recentEventIds: input.recentEvents.map((event) => event.eventId),
248
+ model,
249
+ ...(input.workspacePath === undefined
250
+ ? {}
251
+ : { workspacePath: input.workspacePath }),
252
+ }));
253
+ const result = await runAgentCliCommand({
254
+ command: options.command,
255
+ args: buildCodexExecArgs({
256
+ model,
257
+ prompt: promptText,
258
+ cwd,
259
+ sandboxMode,
260
+ ...(options.settings.reasoningEffort === undefined
261
+ ? {}
262
+ : { reasoningEffort: options.settings.reasoningEffort }),
263
+ }),
264
+ cwd,
265
+ timeoutMs: options.settings.timeoutMs,
266
+ });
267
+ const responseTranscriptPath = await writeRunnerTranscript({
268
+ config: input.config,
269
+ projection: input.projection,
270
+ runId: input.runId,
271
+ action: input.action,
272
+ cli: CODEX_CLI_NAME,
273
+ kind: 'response',
274
+ text: result.stdout,
275
+ });
276
+ if (result.exitCode !== 0 || result.timedOut || result.stdout.trim().length === 0) {
277
+ const sandboxLog = readSandboxLogBreadcrumb();
278
+ const failureClass = classifyCodexCliFailure({
279
+ stdout: result.stdout,
280
+ stderr: result.stderr,
281
+ timedOut: result.timedOut,
282
+ });
283
+ const structuredMessage = result.timedOut
284
+ ? undefined
285
+ : extractCodexErrorMessage(result.stdout);
286
+ console.error(formatCodexRunLogLine({
287
+ phase: 'failure',
288
+ runId: input.runId,
289
+ action: input.action,
290
+ issueNumber: input.projection.issue.number,
291
+ repo: input.projection.issue.repo,
292
+ recentEventIds: input.recentEvents.map((event) => event.eventId),
293
+ model,
294
+ ...(input.workspacePath === undefined
295
+ ? {}
296
+ : { workspacePath: input.workspacePath }),
297
+ exitCode: result.exitCode,
298
+ }));
299
+ return {
300
+ result: [
301
+ result.timedOut
302
+ ? `Codex runner timed out after ${options.settings.timeoutMs}ms and was killed`
303
+ : structuredMessage !== undefined
304
+ ? `Codex runner failed: ${structuredMessage}`
305
+ : result.stdout.trim().length === 0 ? 'Codex runner produced no output'
306
+ : 'Codex runner failed',
307
+ result.stderr,
308
+ sandboxLog?.text,
309
+ 'FAILED',
310
+ ]
311
+ .filter((part) => part !== undefined && part.length > 0)
312
+ .join('\n'),
313
+ model,
314
+ cli: CODEX_CLI_NAME,
315
+ failureClass,
316
+ metadata: {
317
+ stdout: result.stdout,
318
+ stderr: result.stderr,
319
+ exitCode: result.exitCode,
320
+ failureClass,
321
+ ...(promptTranscriptPath === undefined ? {} : { promptTranscriptPath }),
322
+ ...(responseTranscriptPath === undefined ? {} : { responseTranscriptPath }),
323
+ ...(sandboxLog?.metadata ?? {}),
324
+ },
325
+ };
326
+ }
327
+ const parsed = extractCodexExecResult(result.stdout);
328
+ const sandboxLog = readSandboxLogBreadcrumb();
329
+ console.log(formatCodexRunLogLine({
330
+ phase: 'success',
331
+ runId: input.runId,
332
+ action: input.action,
333
+ issueNumber: input.projection.issue.number,
334
+ repo: input.projection.issue.repo,
335
+ recentEventIds: input.recentEvents.map((event) => event.eventId),
336
+ model,
337
+ ...(input.workspacePath === undefined
338
+ ? {}
339
+ : { workspacePath: input.workspacePath }),
340
+ ...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
341
+ }));
342
+ return {
343
+ result: parsed.result,
344
+ model,
345
+ cli: CODEX_CLI_NAME,
346
+ ...(parseRunnerResult(parsed.result).status === 'FAILED'
347
+ ? { failureClass: 'task' }
348
+ : {}),
349
+ ...(parsed.sessionId === undefined ? {} : { session_id: parsed.sessionId }),
350
+ ...(parsed.tokenUsage === undefined ? {} : { tokenUsage: parsed.tokenUsage }),
351
+ metadata: {
352
+ stdout: result.stdout,
353
+ stderr: result.stderr,
354
+ raw: result.stdout
355
+ .split(/\r?\n/)
356
+ .filter((line) => line.trim().length > 0)
357
+ .map((line) => JSON.parse(line)),
358
+ skipApproval: stagePrompt.skipApproval,
359
+ ...(promptTranscriptPath === undefined ? {} : { promptTranscriptPath }),
360
+ ...(responseTranscriptPath === undefined ? {} : { responseTranscriptPath }),
361
+ ...(sandboxLog?.metadata ?? {}),
362
+ },
363
+ };
364
+ },
365
+ async smoke() {
366
+ const result = await runAgentCliCommand({
367
+ command: options.command,
368
+ args: buildCodexExecArgs({
369
+ model: options.settings.smokeModel,
370
+ prompt: options.settings.smokePrompt,
371
+ cwd: options.cwd,
372
+ sandboxMode: 'danger-full-access',
373
+ ...(options.settings.reasoningEffort === undefined
374
+ ? {}
375
+ : { reasoningEffort: options.settings.reasoningEffort }),
376
+ }),
377
+ cwd: options.cwd,
378
+ });
379
+ const parsed = result.exitCode === 0 && result.stdout.trim().length > 0
380
+ ? extractCodexExecResult(result.stdout)
381
+ : undefined;
382
+ return {
383
+ text: parsed?.result ?? '',
384
+ ...(parsed?.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
385
+ stdout: result.stdout,
386
+ stderr: result.stderr,
387
+ exitCode: result.exitCode,
388
+ };
389
+ },
390
+ };
391
+ }