@atolis-hq/wake 0.2.43 → 0.2.45
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.
- package/dist/src/adapters/claude/claude-runner.js +54 -1
- package/dist/src/adapters/codex/codex-runner.js +87 -5
- package/dist/src/adapters/cursor/cursor-runner.js +54 -1
- package/dist/src/adapters/fake/fake-runner.js +32 -1
- package/dist/src/adapters/github/github-client.js +31 -0
- package/dist/src/adapters/github/github-pull-request-merge-actor.js +29 -0
- package/dist/src/adapters/runner/runtime-events.js +32 -0
- package/dist/src/core/policy-engine.js +7 -1
- package/dist/src/core/tick-runner.js +248 -1
- package/dist/src/domain/runtime-events.js +36 -0
- package/dist/src/domain/schema.js +37 -0
- package/dist/src/main.js +5 -0
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseClaudePrintResult, parseRunnerResult } from '../../domain/schema.js';
|
|
2
2
|
import { runAgentCliCommand } from '../runner/cli-command.js';
|
|
3
3
|
import { buildStagePrompt } from '../runner/stage-prompt.js';
|
|
4
|
+
import { emitRuntimeEvent, runnerRuntimeEvent } from '../runner/runtime-events.js';
|
|
4
5
|
import { writeRunnerTranscript } from '../runner/transcripts.js';
|
|
5
6
|
export { buildStagePrompt } from '../runner/stage-prompt.js';
|
|
6
7
|
function slugify(value, maxLength = 40) {
|
|
@@ -221,7 +222,18 @@ export function createClaudeRunner(options) {
|
|
|
221
222
|
args,
|
|
222
223
|
cwd: input.workspacePath ?? options.cwd,
|
|
223
224
|
timeoutMs: options.settings.timeoutMs,
|
|
224
|
-
|
|
225
|
+
onProcessStart: async (identity) => {
|
|
226
|
+
await input.onProcessStart?.(identity);
|
|
227
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
228
|
+
type: 'agent.process.started',
|
|
229
|
+
runId: input.runId,
|
|
230
|
+
projection: input.projection,
|
|
231
|
+
routing: input.routing,
|
|
232
|
+
cli: CLAUDE_CLI_NAME,
|
|
233
|
+
model,
|
|
234
|
+
payload: { pid: identity.pid, processStartedAt: identity.processStartedAt },
|
|
235
|
+
}));
|
|
236
|
+
},
|
|
225
237
|
});
|
|
226
238
|
const responseTranscriptPath = await writeRunnerTranscript({
|
|
227
239
|
config: input.config,
|
|
@@ -250,6 +262,15 @@ export function createClaudeRunner(options) {
|
|
|
250
262
|
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
251
263
|
exitCode: result.exitCode,
|
|
252
264
|
}));
|
|
265
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
266
|
+
type: 'agent.process.exited',
|
|
267
|
+
runId: input.runId,
|
|
268
|
+
projection: input.projection,
|
|
269
|
+
routing: input.routing,
|
|
270
|
+
cli: CLAUDE_CLI_NAME,
|
|
271
|
+
model,
|
|
272
|
+
payload: { exitCode: result.exitCode, timedOut: result.timedOut },
|
|
273
|
+
}));
|
|
253
274
|
let parsedReason;
|
|
254
275
|
let stdoutFailureDetail;
|
|
255
276
|
const trimmedStdout = result.stdout.trim();
|
|
@@ -295,6 +316,16 @@ export function createClaudeRunner(options) {
|
|
|
295
316
|
}
|
|
296
317
|
const parsed = parseClaudePrintOutput(result.stdout);
|
|
297
318
|
const sandboxLog = readSandboxLogBreadcrumb();
|
|
319
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
320
|
+
type: 'agent.progress',
|
|
321
|
+
runId: input.runId,
|
|
322
|
+
projection: input.projection,
|
|
323
|
+
routing: input.routing,
|
|
324
|
+
cli: CLAUDE_CLI_NAME,
|
|
325
|
+
model,
|
|
326
|
+
...(parsed.session_id === undefined ? {} : { sessionId: parsed.session_id }),
|
|
327
|
+
payload: { message: 'Claude print result received', raw: parsed },
|
|
328
|
+
}));
|
|
298
329
|
console.log(formatClaudeRunLogLine({
|
|
299
330
|
phase: 'success',
|
|
300
331
|
runId: input.runId,
|
|
@@ -307,6 +338,28 @@ export function createClaudeRunner(options) {
|
|
|
307
338
|
...(parsed.session_id === undefined ? {} : { sessionId: parsed.session_id }),
|
|
308
339
|
}));
|
|
309
340
|
const tokenUsage = extractTokenUsage(parsed);
|
|
341
|
+
if (tokenUsage !== undefined) {
|
|
342
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
343
|
+
type: 'agent.usage.updated',
|
|
344
|
+
runId: input.runId,
|
|
345
|
+
projection: input.projection,
|
|
346
|
+
routing: input.routing,
|
|
347
|
+
cli: CLAUDE_CLI_NAME,
|
|
348
|
+
model,
|
|
349
|
+
...(parsed.session_id === undefined ? {} : { sessionId: parsed.session_id }),
|
|
350
|
+
payload: { tokenUsage },
|
|
351
|
+
}));
|
|
352
|
+
}
|
|
353
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
354
|
+
type: 'agent.process.exited',
|
|
355
|
+
runId: input.runId,
|
|
356
|
+
projection: input.projection,
|
|
357
|
+
routing: input.routing,
|
|
358
|
+
cli: CLAUDE_CLI_NAME,
|
|
359
|
+
model,
|
|
360
|
+
...(parsed.session_id === undefined ? {} : { sessionId: parsed.session_id }),
|
|
361
|
+
payload: { exitCode: result.exitCode, timedOut: result.timedOut },
|
|
362
|
+
}));
|
|
310
363
|
return {
|
|
311
364
|
result: parsed.result,
|
|
312
365
|
model,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildStagePrompt } from '../runner/stage-prompt.js';
|
|
2
2
|
import { runAgentCliCommand } from '../runner/cli-command.js';
|
|
3
|
+
import { emitRuntimeEvent, runnerRuntimeEvent } from '../runner/runtime-events.js';
|
|
3
4
|
import { writeRunnerTranscript } from '../runner/transcripts.js';
|
|
4
5
|
import { parseRunnerResult } from '../../domain/schema.js';
|
|
5
6
|
const CODEX_CLI_NAME = 'Codex';
|
|
@@ -268,7 +269,18 @@ export function createCodexRunner(options) {
|
|
|
268
269
|
}),
|
|
269
270
|
cwd,
|
|
270
271
|
timeoutMs: options.settings.timeoutMs,
|
|
271
|
-
|
|
272
|
+
onProcessStart: async (identity) => {
|
|
273
|
+
await input.onProcessStart?.(identity);
|
|
274
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
275
|
+
type: 'agent.process.started',
|
|
276
|
+
runId: input.runId,
|
|
277
|
+
projection: input.projection,
|
|
278
|
+
routing: input.routing,
|
|
279
|
+
cli: CODEX_CLI_NAME,
|
|
280
|
+
model,
|
|
281
|
+
payload: { pid: identity.pid, processStartedAt: identity.processStartedAt },
|
|
282
|
+
}));
|
|
283
|
+
},
|
|
272
284
|
});
|
|
273
285
|
const responseTranscriptPath = await writeRunnerTranscript({
|
|
274
286
|
config: input.config,
|
|
@@ -300,6 +312,15 @@ export function createCodexRunner(options) {
|
|
|
300
312
|
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
301
313
|
exitCode: result.exitCode,
|
|
302
314
|
}));
|
|
315
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
316
|
+
type: 'agent.process.exited',
|
|
317
|
+
runId: input.runId,
|
|
318
|
+
projection: input.projection,
|
|
319
|
+
routing: input.routing,
|
|
320
|
+
cli: CODEX_CLI_NAME,
|
|
321
|
+
model,
|
|
322
|
+
payload: { exitCode: result.exitCode, timedOut: result.timedOut },
|
|
323
|
+
}));
|
|
303
324
|
return {
|
|
304
325
|
result: [
|
|
305
326
|
result.timedOut
|
|
@@ -331,6 +352,60 @@ export function createCodexRunner(options) {
|
|
|
331
352
|
}
|
|
332
353
|
const parsed = extractCodexExecResult(result.stdout);
|
|
333
354
|
const sandboxLog = readSandboxLogBreadcrumb();
|
|
355
|
+
const rawEvents = result.stdout
|
|
356
|
+
.split(/\r?\n/)
|
|
357
|
+
.filter((line) => line.trim().length > 0)
|
|
358
|
+
.map((line) => JSON.parse(line));
|
|
359
|
+
for (const raw of rawEvents) {
|
|
360
|
+
if (raw.type === 'thread.started' && typeof raw.thread_id === 'string') {
|
|
361
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
362
|
+
type: 'agent.session.started',
|
|
363
|
+
runId: input.runId,
|
|
364
|
+
projection: input.projection,
|
|
365
|
+
routing: input.routing,
|
|
366
|
+
cli: CODEX_CLI_NAME,
|
|
367
|
+
model,
|
|
368
|
+
sessionId: raw.thread_id,
|
|
369
|
+
payload: { raw },
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
else if (raw.type === 'turn.completed') {
|
|
373
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
374
|
+
type: 'agent.turn.completed',
|
|
375
|
+
runId: input.runId,
|
|
376
|
+
projection: input.projection,
|
|
377
|
+
routing: input.routing,
|
|
378
|
+
cli: CODEX_CLI_NAME,
|
|
379
|
+
model,
|
|
380
|
+
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
381
|
+
payload: { raw },
|
|
382
|
+
}));
|
|
383
|
+
}
|
|
384
|
+
else if (raw.type === 'item.completed') {
|
|
385
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
386
|
+
type: 'agent.progress',
|
|
387
|
+
runId: input.runId,
|
|
388
|
+
projection: input.projection,
|
|
389
|
+
routing: input.routing,
|
|
390
|
+
cli: CODEX_CLI_NAME,
|
|
391
|
+
model,
|
|
392
|
+
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
393
|
+
payload: { raw },
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (parsed.tokenUsage !== undefined) {
|
|
398
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
399
|
+
type: 'agent.usage.updated',
|
|
400
|
+
runId: input.runId,
|
|
401
|
+
projection: input.projection,
|
|
402
|
+
routing: input.routing,
|
|
403
|
+
cli: CODEX_CLI_NAME,
|
|
404
|
+
model,
|
|
405
|
+
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
406
|
+
payload: { tokenUsage: parsed.tokenUsage },
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
334
409
|
console.log(formatCodexRunLogLine({
|
|
335
410
|
phase: 'success',
|
|
336
411
|
runId: input.runId,
|
|
@@ -342,6 +417,16 @@ export function createCodexRunner(options) {
|
|
|
342
417
|
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
343
418
|
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
344
419
|
}));
|
|
420
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
421
|
+
type: 'agent.process.exited',
|
|
422
|
+
runId: input.runId,
|
|
423
|
+
projection: input.projection,
|
|
424
|
+
routing: input.routing,
|
|
425
|
+
cli: CODEX_CLI_NAME,
|
|
426
|
+
model,
|
|
427
|
+
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
428
|
+
payload: { exitCode: result.exitCode, timedOut: result.timedOut },
|
|
429
|
+
}));
|
|
345
430
|
return {
|
|
346
431
|
result: parsed.result,
|
|
347
432
|
model,
|
|
@@ -354,10 +439,7 @@ export function createCodexRunner(options) {
|
|
|
354
439
|
metadata: {
|
|
355
440
|
stdout: result.stdout,
|
|
356
441
|
stderr: result.stderr,
|
|
357
|
-
raw:
|
|
358
|
-
.split(/\r?\n/)
|
|
359
|
-
.filter((line) => line.trim().length > 0)
|
|
360
|
-
.map((line) => JSON.parse(line)),
|
|
442
|
+
raw: rawEvents,
|
|
361
443
|
skipApproval: stagePrompt.skipApproval,
|
|
362
444
|
allowAutoApproval: stagePrompt.allowAutoApproval,
|
|
363
445
|
...(promptTranscriptPath === undefined ? {} : { promptTranscriptPath }),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseRunnerResult } from '../../domain/schema.js';
|
|
2
2
|
import { buildStagePrompt } from '../runner/stage-prompt.js';
|
|
3
3
|
import { runAgentCliCommand } from '../runner/cli-command.js';
|
|
4
|
+
import { emitRuntimeEvent, runnerRuntimeEvent } from '../runner/runtime-events.js';
|
|
4
5
|
import { writeRunnerTranscript } from '../runner/transcripts.js';
|
|
5
6
|
const CURSOR_CLI_NAME = 'Cursor';
|
|
6
7
|
export function buildCursorAgentArgs(input) {
|
|
@@ -219,7 +220,18 @@ export function createCursorRunner(options) {
|
|
|
219
220
|
}),
|
|
220
221
|
cwd,
|
|
221
222
|
timeoutMs: options.settings.timeoutMs,
|
|
222
|
-
|
|
223
|
+
onProcessStart: async (identity) => {
|
|
224
|
+
await input.onProcessStart?.(identity);
|
|
225
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
226
|
+
type: 'agent.process.started',
|
|
227
|
+
runId: input.runId,
|
|
228
|
+
projection: input.projection,
|
|
229
|
+
routing: input.routing,
|
|
230
|
+
cli: CURSOR_CLI_NAME,
|
|
231
|
+
model,
|
|
232
|
+
payload: { pid: identity.pid, processStartedAt: identity.processStartedAt },
|
|
233
|
+
}));
|
|
234
|
+
},
|
|
223
235
|
});
|
|
224
236
|
const responseTranscriptPath = await writeRunnerTranscript({
|
|
225
237
|
config: input.config,
|
|
@@ -248,6 +260,15 @@ export function createCursorRunner(options) {
|
|
|
248
260
|
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
249
261
|
exitCode: result.exitCode,
|
|
250
262
|
}));
|
|
263
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
264
|
+
type: 'agent.process.exited',
|
|
265
|
+
runId: input.runId,
|
|
266
|
+
projection: input.projection,
|
|
267
|
+
routing: input.routing,
|
|
268
|
+
cli: CURSOR_CLI_NAME,
|
|
269
|
+
model,
|
|
270
|
+
payload: { exitCode: result.exitCode, timedOut: result.timedOut },
|
|
271
|
+
}));
|
|
251
272
|
return {
|
|
252
273
|
result: [
|
|
253
274
|
result.timedOut
|
|
@@ -299,6 +320,28 @@ export function createCursorRunner(options) {
|
|
|
299
320
|
};
|
|
300
321
|
}
|
|
301
322
|
const sandboxLog = readSandboxLogBreadcrumb();
|
|
323
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
324
|
+
type: 'agent.progress',
|
|
325
|
+
runId: input.runId,
|
|
326
|
+
projection: input.projection,
|
|
327
|
+
routing: input.routing,
|
|
328
|
+
cli: CURSOR_CLI_NAME,
|
|
329
|
+
model,
|
|
330
|
+
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
331
|
+
payload: { message: 'Cursor agent result received', raw: parsed },
|
|
332
|
+
}));
|
|
333
|
+
if (parsed.tokenUsage !== undefined) {
|
|
334
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
335
|
+
type: 'agent.usage.updated',
|
|
336
|
+
runId: input.runId,
|
|
337
|
+
projection: input.projection,
|
|
338
|
+
routing: input.routing,
|
|
339
|
+
cli: CURSOR_CLI_NAME,
|
|
340
|
+
model,
|
|
341
|
+
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
342
|
+
payload: { tokenUsage: parsed.tokenUsage },
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
302
345
|
console.log(formatCursorRunLogLine({
|
|
303
346
|
phase: 'success',
|
|
304
347
|
runId: input.runId,
|
|
@@ -310,6 +353,16 @@ export function createCursorRunner(options) {
|
|
|
310
353
|
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
311
354
|
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
312
355
|
}));
|
|
356
|
+
await emitRuntimeEvent(input.onRuntimeEvent, runnerRuntimeEvent({
|
|
357
|
+
type: 'agent.process.exited',
|
|
358
|
+
runId: input.runId,
|
|
359
|
+
projection: input.projection,
|
|
360
|
+
routing: input.routing,
|
|
361
|
+
cli: CURSOR_CLI_NAME,
|
|
362
|
+
model,
|
|
363
|
+
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
364
|
+
payload: { exitCode: result.exitCode, timedOut: result.timedOut },
|
|
365
|
+
}));
|
|
313
366
|
return {
|
|
314
367
|
result: parsed.result,
|
|
315
368
|
model,
|
|
@@ -1,6 +1,37 @@
|
|
|
1
|
+
import { emitRuntimeEvent, runnerRuntimeEvent } from '../runner/runtime-events.js';
|
|
1
2
|
export function createFakeRunner(result, options) {
|
|
2
3
|
return {
|
|
3
4
|
async run(_) {
|
|
5
|
+
const cli = options?.cli ?? 'Fake';
|
|
6
|
+
await emitRuntimeEvent(_.onRuntimeEvent, runnerRuntimeEvent({
|
|
7
|
+
type: 'agent.process.started',
|
|
8
|
+
runId: _.runId,
|
|
9
|
+
projection: _.projection,
|
|
10
|
+
routing: _.routing,
|
|
11
|
+
cli,
|
|
12
|
+
model: 'fake',
|
|
13
|
+
payload: { synthetic: true },
|
|
14
|
+
}));
|
|
15
|
+
await emitRuntimeEvent(_.onRuntimeEvent, runnerRuntimeEvent({
|
|
16
|
+
type: 'agent.progress',
|
|
17
|
+
runId: _.runId,
|
|
18
|
+
projection: _.projection,
|
|
19
|
+
routing: _.routing,
|
|
20
|
+
cli,
|
|
21
|
+
model: 'fake',
|
|
22
|
+
sessionId: 'fake-session-1',
|
|
23
|
+
payload: { message: 'Fake runner completed', synthetic: true },
|
|
24
|
+
}));
|
|
25
|
+
await emitRuntimeEvent(_.onRuntimeEvent, runnerRuntimeEvent({
|
|
26
|
+
type: 'agent.process.exited',
|
|
27
|
+
runId: _.runId,
|
|
28
|
+
projection: _.projection,
|
|
29
|
+
routing: _.routing,
|
|
30
|
+
cli,
|
|
31
|
+
model: 'fake',
|
|
32
|
+
sessionId: 'fake-session-1',
|
|
33
|
+
payload: { exitCode: 0, synthetic: true },
|
|
34
|
+
}));
|
|
4
35
|
return (result ?? {
|
|
5
36
|
result: [
|
|
6
37
|
'Fake runner completed',
|
|
@@ -11,7 +42,7 @@ export function createFakeRunner(result, options) {
|
|
|
11
42
|
'DONE',
|
|
12
43
|
].join('\n'),
|
|
13
44
|
model: 'fake',
|
|
14
|
-
cli
|
|
45
|
+
cli,
|
|
15
46
|
session_id: 'fake-session-1',
|
|
16
47
|
metadata: {
|
|
17
48
|
source: 'fake-runner',
|
|
@@ -194,6 +194,37 @@ export function createGitHubClient(token) {
|
|
|
194
194
|
}),
|
|
195
195
|
});
|
|
196
196
|
},
|
|
197
|
+
async listPullRequestFiles(owner, repo, pullNumber) {
|
|
198
|
+
return fetchPaginatedWithEtag({
|
|
199
|
+
cache: etagCache,
|
|
200
|
+
cacheKey: `pr-files:${owner}/${repo}#${pullNumber}`,
|
|
201
|
+
pages: (headers) => octokit.paginate.iterator(octokit.rest.pulls.listFiles, {
|
|
202
|
+
owner,
|
|
203
|
+
repo,
|
|
204
|
+
pull_number: pullNumber,
|
|
205
|
+
per_page: 100,
|
|
206
|
+
...(headers === undefined ? {} : { headers }),
|
|
207
|
+
}),
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
async createPullRequestApproval(owner, repo, pullNumber, body) {
|
|
211
|
+
await octokit.rest.pulls.createReview({
|
|
212
|
+
owner,
|
|
213
|
+
repo,
|
|
214
|
+
pull_number: pullNumber,
|
|
215
|
+
event: 'APPROVE',
|
|
216
|
+
body,
|
|
217
|
+
});
|
|
218
|
+
},
|
|
219
|
+
async enablePullRequestAutoMerge(pullRequestNodeId) {
|
|
220
|
+
await octokit.graphql(`mutation EnableWakeAutoMerge($pullRequestId: ID!) {
|
|
221
|
+
enablePullRequestAutoMerge(input: { pullRequestId: $pullRequestId }) {
|
|
222
|
+
pullRequest {
|
|
223
|
+
id
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}`, { pullRequestId: pullRequestNodeId });
|
|
227
|
+
},
|
|
197
228
|
async replyToReviewComment(owner, repo, pullNumber, commentId, body) {
|
|
198
229
|
return octokit.rest.pulls.createReplyForReviewComment({
|
|
199
230
|
owner,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function parseGithubPullRequestResourceUri(resourceUri) {
|
|
2
|
+
const match = /^github:pr:([^/]+)\/([^#]+)#(\d+)$/.exec(resourceUri);
|
|
3
|
+
if (match === null) {
|
|
4
|
+
throw new Error(`Unsupported GitHub pull request resource URI: ${resourceUri}`);
|
|
5
|
+
}
|
|
6
|
+
return {
|
|
7
|
+
owner: match[1],
|
|
8
|
+
repo: match[2],
|
|
9
|
+
pullNumber: Number(match[3]),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function createGitHubPullRequestMergeActor(input) {
|
|
13
|
+
return {
|
|
14
|
+
async listChangedFiles(resourceUri) {
|
|
15
|
+
const ref = parseGithubPullRequestResourceUri(resourceUri);
|
|
16
|
+
const files = await input.client.listPullRequestFiles(ref.owner, ref.repo, ref.pullNumber);
|
|
17
|
+
return files.map((file) => file.filename);
|
|
18
|
+
},
|
|
19
|
+
async approve(resourceUri, body) {
|
|
20
|
+
const ref = parseGithubPullRequestResourceUri(resourceUri);
|
|
21
|
+
await input.client.createPullRequestApproval(ref.owner, ref.repo, ref.pullNumber, body);
|
|
22
|
+
},
|
|
23
|
+
async enableAutoMerge(resourceUri) {
|
|
24
|
+
const ref = parseGithubPullRequestResourceUri(resourceUri);
|
|
25
|
+
const pr = await input.client.getPullRequest(ref.owner, ref.repo, ref.pullNumber);
|
|
26
|
+
await input.client.enablePullRequestAutoMerge(pr.node_id);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
function runnerKindForCli(cli) {
|
|
2
|
+
if (cli === 'Claude') {
|
|
3
|
+
return 'claude';
|
|
4
|
+
}
|
|
5
|
+
if (cli === 'Codex') {
|
|
6
|
+
return 'codex';
|
|
7
|
+
}
|
|
8
|
+
if (cli === 'Cursor') {
|
|
9
|
+
return 'cursor';
|
|
10
|
+
}
|
|
11
|
+
return 'fake';
|
|
12
|
+
}
|
|
13
|
+
export function runnerRuntimeEvent(input) {
|
|
14
|
+
return {
|
|
15
|
+
type: input.type,
|
|
16
|
+
runId: input.runId,
|
|
17
|
+
workItemId: input.projection.workItemKey,
|
|
18
|
+
runner: {
|
|
19
|
+
name: input.routing?.runnerName ?? input.cli.toLowerCase(),
|
|
20
|
+
kind: input.routing?.runnerKind ?? runnerKindForCli(input.cli),
|
|
21
|
+
cli: input.cli,
|
|
22
|
+
...(input.model === undefined ? {} : { model: input.model }),
|
|
23
|
+
},
|
|
24
|
+
...(input.sessionId === undefined ? {} : { sessionId: input.sessionId }),
|
|
25
|
+
payload: input.payload ?? {},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export async function emitRuntimeEvent(emit, event) {
|
|
29
|
+
if (emit !== undefined) {
|
|
30
|
+
await emit(event);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -188,7 +188,13 @@ export function createPolicyEngine() {
|
|
|
188
188
|
if (latestComment?.isBotAuthored === true &&
|
|
189
189
|
latestComment.resourceUri !== undefined &&
|
|
190
190
|
latestComment.body.includes(prReviewApprovalMarker)) {
|
|
191
|
-
return {
|
|
191
|
+
return {
|
|
192
|
+
approved: true,
|
|
193
|
+
pendingAction,
|
|
194
|
+
targetResourceUri: latestComment.resourceUri,
|
|
195
|
+
triggeringCommentId: latestComment.id,
|
|
196
|
+
triggeringCommentBody: latestComment.body,
|
|
197
|
+
};
|
|
192
198
|
}
|
|
193
199
|
if (latestHumanComment === undefined) {
|
|
194
200
|
return null;
|
|
@@ -6,6 +6,7 @@ import { acquireFileLock } from '../lib/lock.js';
|
|
|
6
6
|
import { CORRELATION_REGISTERED_EVENT, CORRELATION_PRIMARY_CONFLICT_EVENT, parseRunnerArtifacts, parseRunnerResult, } from '../domain/schema.js';
|
|
7
7
|
import { maxConfiguredRunnerTimeoutMs, resolveRunnerRouting } from '../domain/runner-routing.js';
|
|
8
8
|
import { awaitingApprovalRunnerSentinel, stageLabelForStage } from '../domain/stages.js';
|
|
9
|
+
import { isMeaningfulRuntimeEvent } from '../domain/runtime-events.js';
|
|
9
10
|
import { chooseAction as chooseWorkflowAction, entryStage as workflowEntryStage, isKnownWorkflowStage, workflowChangedBlockReason, workflowForProjection, workflowLabelForWorkflowName, workflowNameForProjection, } from '../domain/workflows.js';
|
|
10
11
|
import { createEventEnvelope } from '../lib/event-log.js';
|
|
11
12
|
import { branchNameForIssue } from '../domain/branch-naming.js';
|
|
@@ -110,6 +111,202 @@ export function createTickRunner(deps) {
|
|
|
110
111
|
function hasLabel(projection, label) {
|
|
111
112
|
return projection.issue.labels.includes(label);
|
|
112
113
|
}
|
|
114
|
+
function approvedMergePolicyForStage(stage) {
|
|
115
|
+
return (stage?.watch?.find((watch) => watch.onApproved?.merge !== undefined)?.onApproved?.merge ??
|
|
116
|
+
null);
|
|
117
|
+
}
|
|
118
|
+
function escapeRegex(input) {
|
|
119
|
+
return input.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
120
|
+
}
|
|
121
|
+
function globPatternMatches(pattern, path) {
|
|
122
|
+
const regex = new RegExp(`^${pattern.split('*').map(escapeRegex).join('.*')}$`);
|
|
123
|
+
return regex.test(path);
|
|
124
|
+
}
|
|
125
|
+
function reviewerMessageFromApprovalComment(body) {
|
|
126
|
+
return body
|
|
127
|
+
.replaceAll(prReviewApprovalMarker, '')
|
|
128
|
+
.replaceAll('<!-- wake:pr-review-changes-requested -->', '')
|
|
129
|
+
.trim();
|
|
130
|
+
}
|
|
131
|
+
async function evaluatePrMergeRisk(input) {
|
|
132
|
+
const incumbent = await deps.resourceIndex.resolve(input.targetResourceUri);
|
|
133
|
+
if (incumbent !== undefined && incumbent !== input.projection.workItemKey) {
|
|
134
|
+
return {
|
|
135
|
+
passed: false,
|
|
136
|
+
reason: `Merge policy blocked this PR because ${input.targetResourceUri} is primary-correlated to ${incumbent}, not ${input.projection.workItemKey}.`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const blockedLabel = input.policy.blockedLabels.find((label) => input.projection.issue.labels.includes(label));
|
|
140
|
+
if (blockedLabel !== undefined) {
|
|
141
|
+
return {
|
|
142
|
+
passed: false,
|
|
143
|
+
reason: `Merge policy blocked this PR because the work item has blocked label "${blockedLabel}".`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (deps.prMergeActor === undefined) {
|
|
147
|
+
return {
|
|
148
|
+
passed: false,
|
|
149
|
+
reason: 'Merge policy blocked this PR because no pull request merge actor is configured.',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const filesChanged = await deps.prMergeActor.listChangedFiles(input.targetResourceUri);
|
|
153
|
+
if (input.policy.maxFilesChanged !== undefined &&
|
|
154
|
+
filesChanged.length > input.policy.maxFilesChanged) {
|
|
155
|
+
return {
|
|
156
|
+
passed: false,
|
|
157
|
+
reason: `Merge policy blocked this PR because it changes ${filesChanged.length} files, exceeding maxFilesChanged ${input.policy.maxFilesChanged}.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
for (const pattern of input.policy.blockedPaths) {
|
|
161
|
+
const blockedPath = filesChanged.find((path) => globPatternMatches(pattern, path));
|
|
162
|
+
if (blockedPath !== undefined) {
|
|
163
|
+
return {
|
|
164
|
+
passed: false,
|
|
165
|
+
reason: `Merge policy blocked this PR because changed path "${blockedPath}" matches blockedPaths pattern "${pattern}".`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { passed: true, filesChanged };
|
|
170
|
+
}
|
|
171
|
+
async function publishPrMergePolicyBlock(input) {
|
|
172
|
+
const commentId = input.approvalResolution.triggeringCommentId;
|
|
173
|
+
const targetResourceUri = input.approvalResolution.targetResourceUri;
|
|
174
|
+
if (commentId === undefined || targetResourceUri === undefined) {
|
|
175
|
+
return { status: 'idle' };
|
|
176
|
+
}
|
|
177
|
+
const runId = `merge-gate-blocked-${commentId.replace(/[^a-z0-9]+/gi, '-')}`;
|
|
178
|
+
const occurredAt = eventStampNow();
|
|
179
|
+
const blockedEvent = createEventEnvelope({
|
|
180
|
+
eventId: `${runId}-completed`,
|
|
181
|
+
workItemKey: input.projection.workItemKey,
|
|
182
|
+
streamScope: 'work-item',
|
|
183
|
+
direction: 'internal',
|
|
184
|
+
sourceSystem: 'wake',
|
|
185
|
+
sourceEventType: 'wake.run.completed',
|
|
186
|
+
sourceRefs: {
|
|
187
|
+
repo: input.projection.issue.repo,
|
|
188
|
+
issueNumber: input.projection.issue.number,
|
|
189
|
+
runId,
|
|
190
|
+
resourceUri: targetResourceUri,
|
|
191
|
+
},
|
|
192
|
+
occurredAt,
|
|
193
|
+
ingestedAt: occurredAt,
|
|
194
|
+
trigger: 'immediate',
|
|
195
|
+
payload: {
|
|
196
|
+
action: input.approvalResolution.pendingAction,
|
|
197
|
+
sentinel: 'BLOCKED',
|
|
198
|
+
runId,
|
|
199
|
+
reason: 'pr-review-merge-policy-blocked',
|
|
200
|
+
blockReason: 'pr-review-merge-policy-blocked',
|
|
201
|
+
handledCommentId: commentId,
|
|
202
|
+
body: input.reason,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
const appended = await deps.stateStore.appendEventEnvelope(blockedEvent);
|
|
206
|
+
await projectionUpdater.rebuildFromEvents([appended]);
|
|
207
|
+
const reviewerMessage = reviewerMessageFromApprovalComment(input.approvalResolution.triggeringCommentBody ?? '');
|
|
208
|
+
const body = [reviewerMessage, input.reason].filter((part) => part.length > 0).join('\n\n');
|
|
209
|
+
await deliverOutboundEvent(createEventEnvelope({
|
|
210
|
+
eventId: `${runId}-publish-intent`,
|
|
211
|
+
workItemKey: input.projection.workItemKey,
|
|
212
|
+
streamScope: 'work-item',
|
|
213
|
+
direction: 'outbound',
|
|
214
|
+
sourceSystem: 'wake',
|
|
215
|
+
sourceEventType: 'wake.publish.intent.requested',
|
|
216
|
+
sourceRefs: {
|
|
217
|
+
repo: input.projection.issue.repo,
|
|
218
|
+
issueNumber: input.projection.issue.number,
|
|
219
|
+
runId,
|
|
220
|
+
resourceUri: targetResourceUri,
|
|
221
|
+
},
|
|
222
|
+
occurredAt,
|
|
223
|
+
ingestedAt: occurredAt,
|
|
224
|
+
trigger: 'context-only',
|
|
225
|
+
payload: {
|
|
226
|
+
kind: 'question',
|
|
227
|
+
origin: input.projection.origin ?? 'github',
|
|
228
|
+
body,
|
|
229
|
+
action: input.approvalResolution.pendingAction,
|
|
230
|
+
sentinel: 'BLOCKED',
|
|
231
|
+
runId,
|
|
232
|
+
idempotencyKey: `${commentId}:pr-review-merge-policy-blocked`,
|
|
233
|
+
deliveryState: 'PENDING',
|
|
234
|
+
},
|
|
235
|
+
derivedHints: { stage: input.projection.wake.stage },
|
|
236
|
+
}));
|
|
237
|
+
await deliverOutboundEvent(createLabelsEvent({
|
|
238
|
+
projection: input.projection,
|
|
239
|
+
runId,
|
|
240
|
+
statusLabel: 'wake:status.blocked',
|
|
241
|
+
stageLabel: stageLabelForStage(input.projection.wake.stage),
|
|
242
|
+
workflowLabel: workflowLabelForWorkflowName(input.workflowName),
|
|
243
|
+
occurredAt,
|
|
244
|
+
}));
|
|
245
|
+
return {
|
|
246
|
+
status: 'processed',
|
|
247
|
+
runId,
|
|
248
|
+
sentinel: 'BLOCKED',
|
|
249
|
+
nextStage: null,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async function performApprovedPrMergeActions(input) {
|
|
253
|
+
if (deps.prMergeActor === undefined ||
|
|
254
|
+
input.approvalResolution.targetResourceUri === undefined ||
|
|
255
|
+
input.approvalResolution.triggeringCommentId === undefined) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const targetResourceUri = input.approvalResolution.targetResourceUri;
|
|
259
|
+
const commentId = input.approvalResolution.triggeringCommentId.replace(/[^a-z0-9]+/gi, '-');
|
|
260
|
+
const reviewBody = reviewerMessageFromApprovalComment(input.approvalResolution.triggeringCommentBody ?? '');
|
|
261
|
+
const approvedEventId = `pr-merge-approved-${commentId}`;
|
|
262
|
+
if (input.mergePolicy.approve &&
|
|
263
|
+
(await deps.stateStore.readEventEnvelope(approvedEventId)) === null) {
|
|
264
|
+
await deps.prMergeActor.approve(targetResourceUri, reviewBody);
|
|
265
|
+
const occurredAt = eventStampNow();
|
|
266
|
+
await deps.stateStore.appendEventEnvelope(createEventEnvelope({
|
|
267
|
+
eventId: approvedEventId,
|
|
268
|
+
workItemKey: input.projection.workItemKey,
|
|
269
|
+
streamScope: 'work-item',
|
|
270
|
+
direction: 'internal',
|
|
271
|
+
sourceSystem: 'wake',
|
|
272
|
+
sourceEventType: 'wake.pr-review.approved',
|
|
273
|
+
sourceRefs: {
|
|
274
|
+
resourceUri: targetResourceUri,
|
|
275
|
+
commentId: input.approvalResolution.triggeringCommentId,
|
|
276
|
+
},
|
|
277
|
+
occurredAt,
|
|
278
|
+
ingestedAt: occurredAt,
|
|
279
|
+
trigger: 'context-only',
|
|
280
|
+
payload: {
|
|
281
|
+
idempotencyKey: `${input.approvalResolution.triggeringCommentId}:pr-review-approval`,
|
|
282
|
+
},
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
const autoMergeEventId = `pr-auto-merge-enabled-${commentId}`;
|
|
286
|
+
if (input.mergePolicy.autoMerge &&
|
|
287
|
+
(await deps.stateStore.readEventEnvelope(autoMergeEventId)) === null) {
|
|
288
|
+
await deps.prMergeActor.enableAutoMerge(targetResourceUri);
|
|
289
|
+
const occurredAt = eventStampNow();
|
|
290
|
+
await deps.stateStore.appendEventEnvelope(createEventEnvelope({
|
|
291
|
+
eventId: autoMergeEventId,
|
|
292
|
+
workItemKey: input.projection.workItemKey,
|
|
293
|
+
streamScope: 'work-item',
|
|
294
|
+
direction: 'internal',
|
|
295
|
+
sourceSystem: 'wake',
|
|
296
|
+
sourceEventType: 'wake.pr-auto-merge.enabled',
|
|
297
|
+
sourceRefs: {
|
|
298
|
+
resourceUri: targetResourceUri,
|
|
299
|
+
commentId: input.approvalResolution.triggeringCommentId,
|
|
300
|
+
},
|
|
301
|
+
occurredAt,
|
|
302
|
+
ingestedAt: occurredAt,
|
|
303
|
+
trigger: 'context-only',
|
|
304
|
+
payload: {
|
|
305
|
+
idempotencyKey: `${input.approvalResolution.triggeringCommentId}:pr-auto-merge`,
|
|
306
|
+
},
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
113
310
|
// Closes the loop on #82's review feedback: rather than scrape a PR link
|
|
114
311
|
// out of the agent's free text, the agent emits a `wake-artifacts` fence
|
|
115
312
|
// (domain/schema.ts's parseRunnerArtifacts) and Wake verifies each claim
|
|
@@ -656,6 +853,27 @@ export function createTickRunner(deps) {
|
|
|
656
853
|
promptContextOverrides = workflowAction?.promptContext;
|
|
657
854
|
}
|
|
658
855
|
else if (approvalResolution.approved) {
|
|
856
|
+
const mergePolicy = approvedMergePolicyForStage(workflow.stages[candidate.wake.stage]);
|
|
857
|
+
if (approvalResolution.targetResourceUri !== undefined && mergePolicy !== null) {
|
|
858
|
+
const risk = await evaluatePrMergeRisk({
|
|
859
|
+
projection: candidate,
|
|
860
|
+
targetResourceUri: approvalResolution.targetResourceUri,
|
|
861
|
+
policy: mergePolicy,
|
|
862
|
+
});
|
|
863
|
+
if (!risk.passed) {
|
|
864
|
+
return await publishPrMergePolicyBlock({
|
|
865
|
+
projection: candidate,
|
|
866
|
+
approvalResolution,
|
|
867
|
+
reason: risk.reason,
|
|
868
|
+
workflowName,
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
await performApprovedPrMergeActions({
|
|
872
|
+
projection: candidate,
|
|
873
|
+
approvalResolution,
|
|
874
|
+
mergePolicy,
|
|
875
|
+
});
|
|
876
|
+
}
|
|
659
877
|
const approvalId = `approval-${candidate.issue.number}-${deps.clock.now().getTime()}`;
|
|
660
878
|
const approvedAt = deps.clock.now().toISOString();
|
|
661
879
|
const automaticApproval = approvalResolution.automatic === true;
|
|
@@ -684,7 +902,11 @@ export function createTickRunner(deps) {
|
|
|
684
902
|
nextStage,
|
|
685
903
|
runId: approvalId,
|
|
686
904
|
reason: automaticApproval ? 'auto:approved' : 'human:approved',
|
|
687
|
-
...(automaticApproval
|
|
905
|
+
...(automaticApproval
|
|
906
|
+
? {}
|
|
907
|
+
: {
|
|
908
|
+
handledCommentId: approvalResolution.triggeringCommentId ?? latestHumanCommentId(candidate),
|
|
909
|
+
}),
|
|
688
910
|
...(automaticApproval
|
|
689
911
|
? {
|
|
690
912
|
autoResolution: {
|
|
@@ -831,6 +1053,30 @@ export function createTickRunner(deps) {
|
|
|
831
1053
|
lifecycle,
|
|
832
1054
|
});
|
|
833
1055
|
}
|
|
1056
|
+
async function appendRuntimeEvent(event) {
|
|
1057
|
+
const timestamp = event.timestamp ?? deps.clock.now().toISOString();
|
|
1058
|
+
await deps.stateStore.updateRunRecordIf(runId, {
|
|
1059
|
+
expect: (record) => record.status === 'running' &&
|
|
1060
|
+
record.lease?.leaseId === lease.leaseId &&
|
|
1061
|
+
record.lease.ownerInstanceId === ownerInstanceId,
|
|
1062
|
+
update: (record) => {
|
|
1063
|
+
const runtimeEvents = record.runtimeEvents ?? [];
|
|
1064
|
+
const sequence = event.sequence ?? runtimeEvents.length;
|
|
1065
|
+
const normalizedEvent = {
|
|
1066
|
+
...event,
|
|
1067
|
+
timestamp,
|
|
1068
|
+
sequence,
|
|
1069
|
+
};
|
|
1070
|
+
return {
|
|
1071
|
+
...record,
|
|
1072
|
+
runtimeEvents: [...runtimeEvents, normalizedEvent],
|
|
1073
|
+
...(isMeaningfulRuntimeEvent(normalizedEvent)
|
|
1074
|
+
? { lastMeaningfulActivityAt: normalizedEvent.timestamp }
|
|
1075
|
+
: {}),
|
|
1076
|
+
};
|
|
1077
|
+
},
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
834
1080
|
const claimedAt = eventStampNow();
|
|
835
1081
|
const claimedEvent = createEventEnvelope({
|
|
836
1082
|
eventId: `${runId}-claimed`,
|
|
@@ -980,6 +1226,7 @@ export function createTickRunner(deps) {
|
|
|
980
1226
|
}),
|
|
981
1227
|
});
|
|
982
1228
|
},
|
|
1229
|
+
onRuntimeEvent: appendRuntimeEvent,
|
|
983
1230
|
});
|
|
984
1231
|
clearInterval(leaseRenewalTimer);
|
|
985
1232
|
leaseRenewalTimer = undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const runtimeEventTypeValues = [
|
|
2
|
+
'run.claimed',
|
|
3
|
+
'run.started',
|
|
4
|
+
'workspace.preparing',
|
|
5
|
+
'workspace.ready',
|
|
6
|
+
'agent.process.started',
|
|
7
|
+
'agent.session.started',
|
|
8
|
+
'agent.progress',
|
|
9
|
+
'agent.tool.requested',
|
|
10
|
+
'agent.approval.requested',
|
|
11
|
+
'agent.input.required',
|
|
12
|
+
'agent.usage.updated',
|
|
13
|
+
'agent.turn.completed',
|
|
14
|
+
'agent.process.exited',
|
|
15
|
+
'run.completed',
|
|
16
|
+
'run.failed',
|
|
17
|
+
'run.stalled',
|
|
18
|
+
'run.canceled',
|
|
19
|
+
];
|
|
20
|
+
export function isMeaningfulRuntimeEvent(event) {
|
|
21
|
+
return (event.type === 'agent.progress' ||
|
|
22
|
+
event.type === 'agent.tool.requested' ||
|
|
23
|
+
event.type === 'agent.approval.requested' ||
|
|
24
|
+
event.type === 'agent.input.required' ||
|
|
25
|
+
event.type === 'agent.usage.updated' ||
|
|
26
|
+
event.type === 'agent.turn.completed');
|
|
27
|
+
}
|
|
28
|
+
export function runtimeEventPayload(input) {
|
|
29
|
+
return {
|
|
30
|
+
...(input.message === undefined ? {} : { message: input.message }),
|
|
31
|
+
...(input.raw === undefined ? {} : { raw: input.raw }),
|
|
32
|
+
...(input.exitCode === undefined ? {} : { exitCode: input.exitCode }),
|
|
33
|
+
...(input.timedOut === undefined ? {} : { timedOut: input.timedOut }),
|
|
34
|
+
...(input.tokenUsage === undefined ? {} : { tokenUsage: input.tokenUsage }),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -5,6 +5,7 @@ import { z } from 'zod';
|
|
|
5
5
|
import { reservedCommandNames } from './custom-commands.js';
|
|
6
6
|
import { runnerSentinelValues } from './stages.js';
|
|
7
7
|
import { correlationProvenanceSchema, correlationRelationSchema, correlationRoleSchema, resourceUriSchema, } from './resource-uri.js';
|
|
8
|
+
import { runtimeEventTypeValues } from './runtime-events.js';
|
|
8
9
|
import { alwaysManualIgnoredLabels } from './manual-labels.js';
|
|
9
10
|
const isoTimestampSchema = z.string().datetime({ offset: true });
|
|
10
11
|
const identifierSchema = z.string().min(1);
|
|
@@ -47,6 +48,21 @@ export const executionAttemptLifecycleValues = [
|
|
|
47
48
|
export const executionOutcomeSchema = z.enum(executionOutcomeValues);
|
|
48
49
|
export const workflowOutcomeSchema = z.enum(workflowOutcomeValues);
|
|
49
50
|
export const executionAttemptLifecycleSchema = z.enum(executionAttemptLifecycleValues);
|
|
51
|
+
export const runtimeEventSchema = z.object({
|
|
52
|
+
type: z.enum(runtimeEventTypeValues),
|
|
53
|
+
runId: z.string(),
|
|
54
|
+
workItemId: z.string(),
|
|
55
|
+
runner: z.object({
|
|
56
|
+
name: z.string(),
|
|
57
|
+
kind: z.enum(['fake', 'claude', 'codex', 'cursor']),
|
|
58
|
+
cli: z.string(),
|
|
59
|
+
model: z.string().optional(),
|
|
60
|
+
}),
|
|
61
|
+
sessionId: z.string().optional(),
|
|
62
|
+
timestamp: isoTimestampSchema,
|
|
63
|
+
sequence: z.number().int().nonnegative(),
|
|
64
|
+
payload: z.record(z.string(), z.unknown()).default({}),
|
|
65
|
+
});
|
|
50
66
|
export const defaultAgentIdentity = 'Wake';
|
|
51
67
|
export const defaultSmokePrompt = `This is ${defaultAgentIdentity}, reply with "hi ${defaultAgentIdentity} only"`;
|
|
52
68
|
const modelOverridesSchema = z
|
|
@@ -361,6 +377,8 @@ export const runRecordSchema = z.preprocess((input) => {
|
|
|
361
377
|
workerProcessStartedAt: z.string().optional(),
|
|
362
378
|
agentPid: z.number().int().positive().optional(),
|
|
363
379
|
agentProcessStartedAt: z.string().optional(),
|
|
380
|
+
lastMeaningfulActivityAt: isoTimestampSchema.optional(),
|
|
381
|
+
runtimeEvents: z.array(runtimeEventSchema).optional(),
|
|
364
382
|
tokenUsage: runTokenUsageSchema.optional(),
|
|
365
383
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
366
384
|
}));
|
|
@@ -377,6 +395,20 @@ const workflowWorkspaceSchema = z.enum(['none', 'read-only', 'branch']);
|
|
|
377
395
|
const workflowTriggerScheduleSchema = z.object({
|
|
378
396
|
cron: z.string().min(1),
|
|
379
397
|
});
|
|
398
|
+
const approvedMergePolicySchema = z
|
|
399
|
+
.object({
|
|
400
|
+
approve: z.boolean().default(false),
|
|
401
|
+
autoMerge: z.boolean().default(false),
|
|
402
|
+
maxFilesChanged: z.number().int().positive().optional(),
|
|
403
|
+
blockedPaths: z.array(z.string().min(1)).default([]),
|
|
404
|
+
blockedLabels: z.array(z.string().min(1)).default([]),
|
|
405
|
+
})
|
|
406
|
+
.default({
|
|
407
|
+
approve: false,
|
|
408
|
+
autoMerge: false,
|
|
409
|
+
blockedPaths: [],
|
|
410
|
+
blockedLabels: [],
|
|
411
|
+
});
|
|
380
412
|
const workflowStageSchema = stageRouteSchema.extend({
|
|
381
413
|
workspace: workflowWorkspaceSchema,
|
|
382
414
|
onDone: identifierSchema,
|
|
@@ -392,6 +424,11 @@ const workflowStageSchema = stageRouteSchema.extend({
|
|
|
392
424
|
.optional(),
|
|
393
425
|
schedule: workflowTriggerScheduleSchema.optional(),
|
|
394
426
|
workflow: identifierSchema,
|
|
427
|
+
onApproved: z
|
|
428
|
+
.object({
|
|
429
|
+
merge: approvedMergePolicySchema.optional(),
|
|
430
|
+
})
|
|
431
|
+
.optional(),
|
|
395
432
|
}))
|
|
396
433
|
.optional(),
|
|
397
434
|
});
|
package/dist/src/main.js
CHANGED
|
@@ -19,6 +19,7 @@ import { createGitHubArtifactVerifier } from './adapters/github/github-artifact-
|
|
|
19
19
|
import { createGitHubClient } from './adapters/github/github-client.js';
|
|
20
20
|
import { createGitHubIssuesWorkSource } from './adapters/github/github-issues-work-source.js';
|
|
21
21
|
import { createGitHubPullRequestActivitySource } from './adapters/github/github-pull-request-activity-source.js';
|
|
22
|
+
import { createGitHubPullRequestMergeActor } from './adapters/github/github-pull-request-merge-actor.js';
|
|
22
23
|
import { runAuditCommand } from './cli/audit-command.js';
|
|
23
24
|
import { runCorrelateCommand } from './cli/correlate-command.js';
|
|
24
25
|
import { runDoctorCommand } from './cli/doctor-command.js';
|
|
@@ -497,6 +498,9 @@ export async function buildRuntime(args) {
|
|
|
497
498
|
const artifactVerifier = prTrackingEnabled && githubClient !== undefined
|
|
498
499
|
? createGitHubArtifactVerifier({ client: githubClient })
|
|
499
500
|
: undefined;
|
|
501
|
+
const prMergeActor = prTrackingEnabled && githubClient !== undefined
|
|
502
|
+
? createGitHubPullRequestMergeActor({ client: githubClient })
|
|
503
|
+
: undefined;
|
|
500
504
|
const ticketingSystem = githubClient !== undefined
|
|
501
505
|
? createGitHubIssuesWorkSource({
|
|
502
506
|
client: githubClient,
|
|
@@ -577,6 +581,7 @@ export async function buildRuntime(args) {
|
|
|
577
581
|
workspaceManager,
|
|
578
582
|
resourceIndex,
|
|
579
583
|
...(artifactVerifier === undefined ? {} : { artifactVerifier }),
|
|
584
|
+
...(prMergeActor === undefined ? {} : { prMergeActor }),
|
|
580
585
|
});
|
|
581
586
|
return {
|
|
582
587
|
config,
|
package/dist/src/version.js
CHANGED