@atolis-hq/wake 0.1.5 → 0.1.6
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/README.md +1 -1
- package/dist/src/adapters/claude/claude-runner.js +9 -30
- package/dist/src/adapters/codex/codex-runner.js +5 -10
- package/dist/src/adapters/fake/fake-github-pull-request-activity-source.js +5 -1
- package/dist/src/adapters/fake/fake-runner.js +2 -2
- package/dist/src/adapters/fake/fake-ticketing-system.js +3 -8
- package/dist/src/adapters/fake/fake-workspace-manager.js +1 -1
- package/dist/src/adapters/fs/state-store.js +8 -10
- package/dist/src/adapters/github/github-auth.js +1 -1
- package/dist/src/adapters/github/github-issues-work-source.js +8 -14
- package/dist/src/adapters/github/github-pull-request-activity-source.js +44 -9
- package/dist/src/adapters/http/ui-data.js +23 -12
- package/dist/src/adapters/http/ui-server.js +14 -4
- package/dist/src/adapters/runner/runner-registry.js +2 -4
- package/dist/src/adapters/runner/stage-prompt.js +15 -18
- package/dist/src/cli/sandbox-command.js +8 -6
- package/dist/src/cli/startup-preflight.js +1 -1
- package/dist/src/core/control-plane.js +6 -2
- package/dist/src/core/policy-engine.js +10 -21
- package/dist/src/core/projection-updater.js +7 -17
- package/dist/src/core/quota-backoff.js +1 -1
- package/dist/src/core/sink-router.js +5 -7
- package/dist/src/core/tick-runner.js +7 -14
- package/dist/src/domain/runner-routing.js +1 -1
- package/dist/src/domain/schema.js +213 -58
- package/dist/src/domain/stages.js +1 -4
- package/dist/src/domain/workflows.js +1 -3
- package/dist/src/lib/lock.js +1 -1
- package/dist/src/main.js +5 -8
- package/dist/src/version.js +1 -1
- package/package.json +10 -2
- package/prompts/implement.md +4 -0
- package/prompts/refine.md +2 -0
- package/prompts/revise.md +4 -3
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ For more detail, see [docs/vision.md](docs/vision.md) and
|
|
|
63
63
|
- **Human resumption.** A human can pick up the exact local agent session when a
|
|
64
64
|
direct terminal intervention is the best way forward.
|
|
65
65
|
- **Operator correlation escape hatch.** `wake correlate <workItemKey>
|
|
66
|
-
|
|
66
|
+
<resourceUri>` lets an operator hand-declare that a resource (a PR, a Slack
|
|
67
67
|
thread, etc.) belongs to an existing work item when nothing detected the
|
|
68
68
|
link automatically. See [docs/configuration.md](docs/configuration.md).
|
|
69
69
|
|
|
@@ -12,12 +12,7 @@ function slugify(value, maxLength = 40) {
|
|
|
12
12
|
.replace(/-+$/g, '');
|
|
13
13
|
}
|
|
14
14
|
export function buildWakeSessionName(input) {
|
|
15
|
-
return [
|
|
16
|
-
input.sessionName,
|
|
17
|
-
`issue-${input.issueNumber}`,
|
|
18
|
-
slugify(input.title),
|
|
19
|
-
input.runId,
|
|
20
|
-
]
|
|
15
|
+
return [input.sessionName, `issue-${input.issueNumber}`, slugify(input.title), input.runId]
|
|
21
16
|
.filter((part) => part.length > 0)
|
|
22
17
|
.join('-');
|
|
23
18
|
}
|
|
@@ -54,13 +49,9 @@ export function buildClaudePrintArgs(options) {
|
|
|
54
49
|
options.sessionName,
|
|
55
50
|
...(options.resumeSessionId === undefined ? [] : ['--resume', options.resumeSessionId]),
|
|
56
51
|
...(options.effort === undefined ? [] : ['--effort', options.effort]),
|
|
57
|
-
...(options.systemPrompt === undefined
|
|
58
|
-
? []
|
|
59
|
-
: ['--append-system-prompt', options.systemPrompt]),
|
|
52
|
+
...(options.systemPrompt === undefined ? [] : ['--append-system-prompt', options.systemPrompt]),
|
|
60
53
|
...(options.maxTurns === undefined ? [] : ['--max-turns', String(options.maxTurns)]),
|
|
61
|
-
...(options.permissionMode === undefined
|
|
62
|
-
? []
|
|
63
|
-
: ['--permission-mode', options.permissionMode]),
|
|
54
|
+
...(options.permissionMode === undefined ? [] : ['--permission-mode', options.permissionMode]),
|
|
64
55
|
...(options.allowedTools === undefined || options.allowedTools.length === 0
|
|
65
56
|
? []
|
|
66
57
|
: ['--allowedTools', options.allowedTools.join(' ')]),
|
|
@@ -199,9 +190,7 @@ export function createClaudeRunner(options) {
|
|
|
199
190
|
...(stagePrompt.permissionMode === undefined
|
|
200
191
|
? {}
|
|
201
192
|
: { permissionMode: stagePrompt.permissionMode }),
|
|
202
|
-
...(options.settings.remoteControl.enabled
|
|
203
|
-
? { remoteControlName: sessionName }
|
|
204
|
-
: {}),
|
|
193
|
+
...(options.settings.remoteControl.enabled ? { remoteControlName: sessionName } : {}),
|
|
205
194
|
...(options.settings.effort === undefined ? {} : { effort: options.settings.effort }),
|
|
206
195
|
...(isResume ? { resumeSessionId: priorSessionId } : {}),
|
|
207
196
|
});
|
|
@@ -222,9 +211,7 @@ export function createClaudeRunner(options) {
|
|
|
222
211
|
repo: input.projection.issue.repo,
|
|
223
212
|
recentEventIds: input.recentEvents.map((event) => event.eventId),
|
|
224
213
|
model,
|
|
225
|
-
...(input.workspacePath === undefined
|
|
226
|
-
? {}
|
|
227
|
-
: { workspacePath: input.workspacePath }),
|
|
214
|
+
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
228
215
|
}));
|
|
229
216
|
const result = await runClaudeCommand({
|
|
230
217
|
command: options.command,
|
|
@@ -256,9 +243,7 @@ export function createClaudeRunner(options) {
|
|
|
256
243
|
repo: input.projection.issue.repo,
|
|
257
244
|
recentEventIds: input.recentEvents.map((event) => event.eventId),
|
|
258
245
|
model,
|
|
259
|
-
...(input.workspacePath === undefined
|
|
260
|
-
? {}
|
|
261
|
-
: { workspacePath: input.workspacePath }),
|
|
246
|
+
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
262
247
|
exitCode: result.exitCode,
|
|
263
248
|
}));
|
|
264
249
|
let parsedReason;
|
|
@@ -314,9 +299,7 @@ export function createClaudeRunner(options) {
|
|
|
314
299
|
repo: input.projection.issue.repo,
|
|
315
300
|
recentEventIds: input.recentEvents.map((event) => event.eventId),
|
|
316
301
|
model,
|
|
317
|
-
...(input.workspacePath === undefined
|
|
318
|
-
? {}
|
|
319
|
-
: { workspacePath: input.workspacePath }),
|
|
302
|
+
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
320
303
|
...(parsed.session_id === undefined ? {} : { sessionId: parsed.session_id }),
|
|
321
304
|
}));
|
|
322
305
|
const tokenUsage = extractTokenUsage(parsed);
|
|
@@ -327,9 +310,7 @@ export function createClaudeRunner(options) {
|
|
|
327
310
|
...(parseRunnerResult(parsed.result).status === 'FAILED'
|
|
328
311
|
? { failureClass: 'task' }
|
|
329
312
|
: {}),
|
|
330
|
-
...(parsed.session_id === undefined
|
|
331
|
-
? {}
|
|
332
|
-
: { session_id: parsed.session_id }),
|
|
313
|
+
...(parsed.session_id === undefined ? {} : { session_id: parsed.session_id }),
|
|
333
314
|
...(tokenUsage === undefined ? {} : { tokenUsage }),
|
|
334
315
|
metadata: {
|
|
335
316
|
stdout: result.stdout,
|
|
@@ -358,9 +339,7 @@ export function createClaudeRunner(options) {
|
|
|
358
339
|
: undefined;
|
|
359
340
|
return {
|
|
360
341
|
text: parsed?.result ?? '',
|
|
361
|
-
...(parsed?.session_id === undefined
|
|
362
|
-
? {}
|
|
363
|
-
: { sessionId: parsed.session_id }),
|
|
342
|
+
...(parsed?.session_id === undefined ? {} : { sessionId: parsed.session_id }),
|
|
364
343
|
stdout: result.stdout,
|
|
365
344
|
stderr: result.stderr,
|
|
366
345
|
exitCode: result.exitCode,
|
|
@@ -246,9 +246,7 @@ export function createCodexRunner(options) {
|
|
|
246
246
|
repo: input.projection.issue.repo,
|
|
247
247
|
recentEventIds: input.recentEvents.map((event) => event.eventId),
|
|
248
248
|
model,
|
|
249
|
-
...(input.workspacePath === undefined
|
|
250
|
-
? {}
|
|
251
|
-
: { workspacePath: input.workspacePath }),
|
|
249
|
+
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
252
250
|
}));
|
|
253
251
|
const result = await runAgentCliCommand({
|
|
254
252
|
command: options.command,
|
|
@@ -291,9 +289,7 @@ export function createCodexRunner(options) {
|
|
|
291
289
|
repo: input.projection.issue.repo,
|
|
292
290
|
recentEventIds: input.recentEvents.map((event) => event.eventId),
|
|
293
291
|
model,
|
|
294
|
-
...(input.workspacePath === undefined
|
|
295
|
-
? {}
|
|
296
|
-
: { workspacePath: input.workspacePath }),
|
|
292
|
+
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
297
293
|
exitCode: result.exitCode,
|
|
298
294
|
}));
|
|
299
295
|
return {
|
|
@@ -302,7 +298,8 @@ export function createCodexRunner(options) {
|
|
|
302
298
|
? `Codex runner timed out after ${options.settings.timeoutMs}ms and was killed`
|
|
303
299
|
: structuredMessage !== undefined
|
|
304
300
|
? `Codex runner failed: ${structuredMessage}`
|
|
305
|
-
: result.stdout.trim().length === 0
|
|
301
|
+
: result.stdout.trim().length === 0
|
|
302
|
+
? 'Codex runner produced no output'
|
|
306
303
|
: 'Codex runner failed',
|
|
307
304
|
result.stderr,
|
|
308
305
|
sandboxLog?.text,
|
|
@@ -334,9 +331,7 @@ export function createCodexRunner(options) {
|
|
|
334
331
|
repo: input.projection.issue.repo,
|
|
335
332
|
recentEventIds: input.recentEvents.map((event) => event.eventId),
|
|
336
333
|
model,
|
|
337
|
-
...(input.workspacePath === undefined
|
|
338
|
-
? {}
|
|
339
|
-
: { workspacePath: input.workspacePath }),
|
|
334
|
+
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
340
335
|
...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
|
|
341
336
|
}));
|
|
342
337
|
return {
|
|
@@ -65,7 +65,11 @@ export function createFakeGitHubPullRequestActivitySource(options) {
|
|
|
65
65
|
occurredAt: publishedAt,
|
|
66
66
|
ingestedAt: publishedAt,
|
|
67
67
|
trigger: 'context-only',
|
|
68
|
-
payload: {
|
|
68
|
+
payload: {
|
|
69
|
+
intentEventId: input.event.eventId,
|
|
70
|
+
kind: input.event.payload.kind,
|
|
71
|
+
body: input.event.payload.body,
|
|
72
|
+
},
|
|
69
73
|
}),
|
|
70
74
|
];
|
|
71
75
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function createFakeRunner(result, options) {
|
|
2
2
|
return {
|
|
3
3
|
async run(_) {
|
|
4
|
-
return result ?? {
|
|
4
|
+
return (result ?? {
|
|
5
5
|
result: [
|
|
6
6
|
'Fake runner completed',
|
|
7
7
|
'',
|
|
@@ -16,7 +16,7 @@ export function createFakeRunner(result, options) {
|
|
|
16
16
|
metadata: {
|
|
17
17
|
source: 'fake-runner',
|
|
18
18
|
},
|
|
19
|
-
};
|
|
19
|
+
});
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
22
|
}
|
|
@@ -92,8 +92,7 @@ export function createFakeTicketingSystem(options) {
|
|
|
92
92
|
? input.event.payload.stageLabel
|
|
93
93
|
: undefined;
|
|
94
94
|
const labels = [
|
|
95
|
-
...currentLabels.filter((label) => !label.startsWith('wake:status.') &&
|
|
96
|
-
!label.startsWith('wake:stage.')),
|
|
95
|
+
...currentLabels.filter((label) => !label.startsWith('wake:status.') && !label.startsWith('wake:stage.')),
|
|
97
96
|
...(statusLabel === undefined
|
|
98
97
|
? currentLabels.filter((label) => label.startsWith('wake:status.'))
|
|
99
98
|
: [statusLabel]),
|
|
@@ -155,12 +154,8 @@ export async function createFileBackedFakeTicketingSystem(options) {
|
|
|
155
154
|
await access(options.fixturePath);
|
|
156
155
|
}
|
|
157
156
|
catch {
|
|
158
|
-
return createFakeTicketingSystem(options.now === undefined
|
|
159
|
-
? { tickets: [] }
|
|
160
|
-
: { tickets: [], now: options.now });
|
|
157
|
+
return createFakeTicketingSystem(options.now === undefined ? { tickets: [] } : { tickets: [], now: options.now });
|
|
161
158
|
}
|
|
162
159
|
const raw = await readJsonFile(options.fixturePath);
|
|
163
|
-
return createFakeTicketingSystem(options.now === undefined
|
|
164
|
-
? { tickets: raw }
|
|
165
|
-
: { tickets: raw, now: options.now });
|
|
160
|
+
return createFakeTicketingSystem(options.now === undefined ? { tickets: raw } : { tickets: raw, now: options.now });
|
|
166
161
|
}
|
|
@@ -2,7 +2,7 @@ import { mkdir, rm } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
export function createFakeWorkspaceManager(root) {
|
|
4
4
|
return {
|
|
5
|
-
async prepareWorkspace({ workId
|
|
5
|
+
async prepareWorkspace({ workId }) {
|
|
6
6
|
// Keyed on the work id, symmetrically with the real git-backed manager.
|
|
7
7
|
const workspacePath = join(root, workId);
|
|
8
8
|
await mkdir(workspacePath, { recursive: true });
|
|
@@ -45,10 +45,10 @@ async function readEventFile(file) {
|
|
|
45
45
|
}
|
|
46
46
|
function issueArchiveAgeDate(item) {
|
|
47
47
|
const stageChangedAt = item.wake.stageHistory.at(-1)?.changedAt;
|
|
48
|
-
return [stageChangedAt, item.wake.syncedAt, item.issue.updatedAt]
|
|
48
|
+
return ([stageChangedAt, item.wake.syncedAt, item.issue.updatedAt]
|
|
49
49
|
.filter((value) => value !== undefined)
|
|
50
50
|
.sort()
|
|
51
|
-
.at(-1) ?? item.wake.syncedAt;
|
|
51
|
+
.at(-1) ?? item.wake.syncedAt);
|
|
52
52
|
}
|
|
53
53
|
function shouldArchiveIssueState(item, options) {
|
|
54
54
|
if (!isTerminalStage(item.wake.stage) && item.issue.state !== 'closed') {
|
|
@@ -61,9 +61,7 @@ export async function listRunRecords(wakeRoot) {
|
|
|
61
61
|
const runsRoot = join(wakeRoot, 'runs');
|
|
62
62
|
const recordsById = new Map();
|
|
63
63
|
try {
|
|
64
|
-
const files = (await readdir(runsRoot))
|
|
65
|
-
.filter((file) => file.endsWith('.json'))
|
|
66
|
-
.sort();
|
|
64
|
+
const files = (await readdir(runsRoot)).filter((file) => file.endsWith('.json')).sort();
|
|
67
65
|
for (const file of files) {
|
|
68
66
|
const record = await readRunRecordFile(join(runsRoot, file));
|
|
69
67
|
if (record !== null) {
|
|
@@ -122,9 +120,7 @@ async function listRunRecordsForDate(wakeRoot, date) {
|
|
|
122
120
|
async function listRecentRunRecords(wakeRoot, limit) {
|
|
123
121
|
const runsRoot = join(wakeRoot, 'runs');
|
|
124
122
|
const recordsById = new Map();
|
|
125
|
-
const dateDirs = (await readdir(join(runsRoot, 'by-date')).catch(() => []))
|
|
126
|
-
.sort()
|
|
127
|
-
.reverse();
|
|
123
|
+
const dateDirs = (await readdir(join(runsRoot, 'by-date')).catch(() => [])).sort().reverse();
|
|
128
124
|
for (const dateDir of dateDirs) {
|
|
129
125
|
const records = await listRunRecordsForDate(wakeRoot, dateDir);
|
|
130
126
|
for (const record of records.reverse()) {
|
|
@@ -265,7 +261,9 @@ export function createStateStore({ wakeRoot }) {
|
|
|
265
261
|
if (record === null) {
|
|
266
262
|
continue;
|
|
267
263
|
}
|
|
268
|
-
if (archiveOptions !== null &&
|
|
264
|
+
if (archiveOptions !== null &&
|
|
265
|
+
!isArchive &&
|
|
266
|
+
shouldArchiveIssueState(record, archiveOptions)) {
|
|
269
267
|
const archivePath = paths.archivedWorkItemStateFile(record.workItemKey);
|
|
270
268
|
await mkdir(dirname(archivePath), { recursive: true });
|
|
271
269
|
await rename(file, archivePath).catch(() => undefined);
|
|
@@ -291,7 +289,7 @@ export function createStateStore({ wakeRoot }) {
|
|
|
291
289
|
const files = (await readdir(eventsRoot)).sort();
|
|
292
290
|
const envelopes = [];
|
|
293
291
|
for (const file of files) {
|
|
294
|
-
envelopes.push(...await readEventFile(join(eventsRoot, file)));
|
|
292
|
+
envelopes.push(...(await readEventFile(join(eventsRoot, file))));
|
|
295
293
|
}
|
|
296
294
|
return envelopes;
|
|
297
295
|
}
|
|
@@ -11,6 +11,6 @@ export async function resolveGitHubToken(deps) {
|
|
|
11
11
|
return token;
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
|
-
throw new Error(`Failed to resolve GitHub token via gh auth token: ${error instanceof Error ? error.message : String(error)}
|
|
14
|
+
throw new Error(`Failed to resolve GitHub token via gh auth token: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -60,14 +60,11 @@ function normalizeTicketUpsert(input) {
|
|
|
60
60
|
issueUpdatedAt: input.issue.updated_at,
|
|
61
61
|
},
|
|
62
62
|
},
|
|
63
|
-
...(input.expectedEcho === true
|
|
64
|
-
? { derivedHints: { expectedEcho: true } }
|
|
65
|
-
: {}),
|
|
63
|
+
...(input.expectedEcho === true ? { derivedHints: { expectedEcho: true } } : {}),
|
|
66
64
|
});
|
|
67
65
|
}
|
|
68
66
|
function normalizeTicketCommentEvent(input) {
|
|
69
|
-
const isUpdate = input.existingUpdatedAt !== undefined &&
|
|
70
|
-
input.existingUpdatedAt !== input.comment.updated_at;
|
|
67
|
+
const isUpdate = input.existingUpdatedAt !== undefined && input.existingUpdatedAt !== input.comment.updated_at;
|
|
71
68
|
return createUnkeyedEventEnvelope({
|
|
72
69
|
eventId: `github-comment-${input.repo}-${input.issueNumber}-${input.comment.id}-${input.comment.updated_at}`,
|
|
73
70
|
streamScope: 'work-item',
|
|
@@ -96,9 +93,7 @@ function normalizeTicketCommentEvent(input) {
|
|
|
96
93
|
createdAt: input.comment.created_at,
|
|
97
94
|
updatedAt: input.comment.updated_at,
|
|
98
95
|
},
|
|
99
|
-
providerEventType: isUpdate
|
|
100
|
-
? 'github.issue.comment.updated'
|
|
101
|
-
: 'github.issue.comment.created',
|
|
96
|
+
providerEventType: isUpdate ? 'github.issue.comment.updated' : 'github.issue.comment.created',
|
|
102
97
|
},
|
|
103
98
|
derivedHints: {
|
|
104
99
|
// Third-party bots/integrations (CI, Dependabot, Renovate, etc.) must
|
|
@@ -209,7 +204,9 @@ export function formatWakeComment(payload, controlPlaneUrl) {
|
|
|
209
204
|
cost === undefined ? undefined : `cost ${cost}`,
|
|
210
205
|
runId === undefined ? undefined : `run \`${runId}\``,
|
|
211
206
|
].filter((part) => part !== undefined);
|
|
212
|
-
const name = controlPlaneUrl === undefined
|
|
207
|
+
const name = controlPlaneUrl === undefined
|
|
208
|
+
? defaultAgentIdentity
|
|
209
|
+
: `[${defaultAgentIdentity}](${controlPlaneUrl})`;
|
|
213
210
|
const header = `**${name}** _(Wake ${wakeVersion}${details.length > 0 ? ` · ${details.join(' · ')}` : ''})_`;
|
|
214
211
|
const sections = [wakeCommentMarker, header, body];
|
|
215
212
|
if (kind === 'approval-request') {
|
|
@@ -304,9 +301,7 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
304
301
|
comment,
|
|
305
302
|
ingestedAt,
|
|
306
303
|
...(deps.selfLogin === undefined ? {} : { selfLogin: deps.selfLogin }),
|
|
307
|
-
...(known?.updatedAt === undefined
|
|
308
|
-
? {}
|
|
309
|
-
: { existingUpdatedAt: known.updatedAt }),
|
|
304
|
+
...(known?.updatedAt === undefined ? {} : { existingUpdatedAt: known.updatedAt }),
|
|
310
305
|
}));
|
|
311
306
|
}
|
|
312
307
|
}
|
|
@@ -346,8 +341,7 @@ export function createGitHubIssuesWorkSource(deps) {
|
|
|
346
341
|
? input.event.payload.stageLabel
|
|
347
342
|
: undefined;
|
|
348
343
|
const nextLabels = [
|
|
349
|
-
...currentLabels.filter((label) => !label.startsWith(wakeStatusLabelPrefix) &&
|
|
350
|
-
!label.startsWith(wakeStageLabelPrefix)),
|
|
344
|
+
...currentLabels.filter((label) => !label.startsWith(wakeStatusLabelPrefix) && !label.startsWith(wakeStageLabelPrefix)),
|
|
351
345
|
...(nextStatusLabel !== undefined
|
|
352
346
|
? [nextStatusLabel]
|
|
353
347
|
: currentLabels.filter((label) => label.startsWith(wakeStatusLabelPrefix))),
|
|
@@ -97,7 +97,11 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
97
97
|
ingestedAt,
|
|
98
98
|
trigger: 'context-only',
|
|
99
99
|
payload: {
|
|
100
|
-
pr: {
|
|
100
|
+
pr: {
|
|
101
|
+
number: pr.number,
|
|
102
|
+
author: pr.user?.login ?? 'unknown',
|
|
103
|
+
headRef: pr.head.ref,
|
|
104
|
+
},
|
|
101
105
|
},
|
|
102
106
|
}));
|
|
103
107
|
}
|
|
@@ -127,7 +131,12 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
127
131
|
direction: 'inbound',
|
|
128
132
|
sourceSystem: githubPrSource,
|
|
129
133
|
sourceEventType: 'pr.comment.created',
|
|
130
|
-
sourceRefs: {
|
|
134
|
+
sourceRefs: {
|
|
135
|
+
repo: ref.repoRef,
|
|
136
|
+
commentId: String(comment.id),
|
|
137
|
+
sourceUrl: comment.html_url,
|
|
138
|
+
resourceUri,
|
|
139
|
+
},
|
|
131
140
|
occurredAt: comment.updated_at,
|
|
132
141
|
ingestedAt,
|
|
133
142
|
trigger: 'context-only',
|
|
@@ -158,7 +167,12 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
158
167
|
direction: 'inbound',
|
|
159
168
|
sourceSystem: githubPrSource,
|
|
160
169
|
sourceEventType: 'pr.review.created',
|
|
161
|
-
sourceRefs: {
|
|
170
|
+
sourceRefs: {
|
|
171
|
+
repo: ref.repoRef,
|
|
172
|
+
commentId: `review-${review.id}`,
|
|
173
|
+
sourceUrl: review.html_url,
|
|
174
|
+
resourceUri,
|
|
175
|
+
},
|
|
162
176
|
occurredAt: submittedAt,
|
|
163
177
|
ingestedAt,
|
|
164
178
|
trigger: 'context-only',
|
|
@@ -207,7 +221,10 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
207
221
|
createdAt: comment.created_at,
|
|
208
222
|
updatedAt: comment.updated_at,
|
|
209
223
|
resourceUri: threadUri,
|
|
210
|
-
reviewThread: {
|
|
224
|
+
reviewThread: {
|
|
225
|
+
path: comment.path,
|
|
226
|
+
line: comment.line ?? comment.original_line ?? undefined,
|
|
227
|
+
},
|
|
211
228
|
},
|
|
212
229
|
},
|
|
213
230
|
derivedHints: { botAuthoredComment: isBotAuthored(comment, deps.selfLogin) },
|
|
@@ -287,7 +304,11 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
287
304
|
direction: 'inbound',
|
|
288
305
|
sourceSystem: githubPrSource,
|
|
289
306
|
sourceEventType: 'pr.checks.failed',
|
|
290
|
-
sourceRefs: {
|
|
307
|
+
sourceRefs: {
|
|
308
|
+
repo: ref.repoRef,
|
|
309
|
+
sourceUrl: status.target_url ?? pr.html_url,
|
|
310
|
+
resourceUri,
|
|
311
|
+
},
|
|
291
312
|
occurredAt: status.updated_at,
|
|
292
313
|
ingestedAt,
|
|
293
314
|
trigger: 'context-only',
|
|
@@ -345,7 +366,10 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
345
366
|
throw new Error(`cannot deliver intent ${input.event.eventId}: malformed review-thread uri ${resourceUri}`);
|
|
346
367
|
}
|
|
347
368
|
const [, owner, repo, numberStr, rootIdStr] = match;
|
|
348
|
-
if (owner === undefined ||
|
|
369
|
+
if (owner === undefined ||
|
|
370
|
+
repo === undefined ||
|
|
371
|
+
numberStr === undefined ||
|
|
372
|
+
rootIdStr === undefined) {
|
|
349
373
|
throw new Error(`cannot deliver intent ${input.event.eventId}: malformed review-thread uri ${resourceUri}`);
|
|
350
374
|
}
|
|
351
375
|
const response = await deps.client.replyToReviewComment(owner, repo, Number(numberStr), Number(rootIdStr), formatWakeComment(input.event.payload, await readControlPlaneUiUrl(deps.config.paths.wakeRoot)));
|
|
@@ -357,11 +381,18 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
357
381
|
direction: 'outbound',
|
|
358
382
|
sourceSystem: githubPrSource,
|
|
359
383
|
sourceEventType: 'pr.review-comment.reply.published',
|
|
360
|
-
sourceRefs: {
|
|
384
|
+
sourceRefs: {
|
|
385
|
+
resourceUri,
|
|
386
|
+
sourceUrl: response?.html_url,
|
|
387
|
+
},
|
|
361
388
|
occurredAt: publishedAt,
|
|
362
389
|
ingestedAt: publishedAt,
|
|
363
390
|
trigger: 'context-only',
|
|
364
|
-
payload: {
|
|
391
|
+
payload: {
|
|
392
|
+
intentEventId: input.event.eventId,
|
|
393
|
+
kind: input.event.payload.kind,
|
|
394
|
+
body: input.event.payload.body,
|
|
395
|
+
},
|
|
365
396
|
}),
|
|
366
397
|
];
|
|
367
398
|
}
|
|
@@ -382,7 +413,11 @@ export function createGitHubPullRequestActivitySource(deps) {
|
|
|
382
413
|
occurredAt: publishedAt,
|
|
383
414
|
ingestedAt: publishedAt,
|
|
384
415
|
trigger: 'context-only',
|
|
385
|
-
payload: {
|
|
416
|
+
payload: {
|
|
417
|
+
intentEventId: input.event.eventId,
|
|
418
|
+
kind: input.event.payload.kind,
|
|
419
|
+
body: input.event.payload.body,
|
|
420
|
+
},
|
|
386
421
|
}),
|
|
387
422
|
];
|
|
388
423
|
},
|
|
@@ -145,7 +145,11 @@ export async function buildStatus(input) {
|
|
|
145
145
|
? { level: 'unknown' }
|
|
146
146
|
: {
|
|
147
147
|
ageMs: worstAgeMs,
|
|
148
|
-
level: worstAgeMs > intervalMs * 10
|
|
148
|
+
level: worstAgeMs > intervalMs * 10
|
|
149
|
+
? 'red'
|
|
150
|
+
: worstAgeMs > intervalMs * 3
|
|
151
|
+
? 'amber'
|
|
152
|
+
: 'ok',
|
|
149
153
|
};
|
|
150
154
|
return {
|
|
151
155
|
loopState,
|
|
@@ -154,7 +158,11 @@ export async function buildStatus(input) {
|
|
|
154
158
|
lock,
|
|
155
159
|
lastEvent: lastEvent === undefined
|
|
156
160
|
? undefined
|
|
157
|
-
: {
|
|
161
|
+
: {
|
|
162
|
+
at: lastEvent.ingestedAt,
|
|
163
|
+
type: lastEvent.sourceEventType,
|
|
164
|
+
workItemKey: lastEvent.workItemKey,
|
|
165
|
+
},
|
|
158
166
|
lastRun: lastRun === undefined
|
|
159
167
|
? undefined
|
|
160
168
|
: {
|
|
@@ -338,8 +346,15 @@ export async function buildHealth(input) {
|
|
|
338
346
|
const integrityChecks = await Promise.all(items
|
|
339
347
|
.filter((item) => item.wake.workspacePath !== undefined)
|
|
340
348
|
.map(async (item) => {
|
|
341
|
-
const exists = await stat(item.wake.workspacePath)
|
|
342
|
-
|
|
349
|
+
const exists = await stat(item.wake.workspacePath)
|
|
350
|
+
.then(() => true)
|
|
351
|
+
.catch(() => false);
|
|
352
|
+
return exists
|
|
353
|
+
? null
|
|
354
|
+
: {
|
|
355
|
+
path: item.wake.workspacePath,
|
|
356
|
+
problem: `workspacePath missing for ${item.workItemKey}`,
|
|
357
|
+
};
|
|
343
358
|
}));
|
|
344
359
|
const integrityIssues = integrityChecks.filter((issue) => issue !== null);
|
|
345
360
|
return {
|
|
@@ -359,16 +374,12 @@ export async function buildHealth(input) {
|
|
|
359
374
|
}
|
|
360
375
|
export async function buildWorkspaces(input) {
|
|
361
376
|
const items = await input.stateStore.listIssueStates();
|
|
362
|
-
const byWorkspacePath = new Map(items
|
|
377
|
+
const byWorkspacePath = new Map(items
|
|
378
|
+
.filter((item) => item.wake.workspacePath !== undefined)
|
|
379
|
+
.map((item) => [item.wake.workspacePath, item]));
|
|
363
380
|
const workspaceRoot = input.stateStore.paths.workspaceRoot;
|
|
364
381
|
const workspaces = [];
|
|
365
|
-
|
|
366
|
-
try {
|
|
367
|
-
repoDirs = await readdir(workspaceRoot);
|
|
368
|
-
}
|
|
369
|
-
catch {
|
|
370
|
-
repoDirs = [];
|
|
371
|
-
}
|
|
382
|
+
const repoDirs = await readdir(workspaceRoot).catch(() => []);
|
|
372
383
|
for (const repoDir of repoDirs) {
|
|
373
384
|
const repoPath = join(workspaceRoot, repoDir);
|
|
374
385
|
const issueDirs = await readdir(repoPath).catch(() => []);
|
|
@@ -17,7 +17,10 @@ function extractBearerToken(req) {
|
|
|
17
17
|
}
|
|
18
18
|
const cookie = req.headers.cookie;
|
|
19
19
|
if (typeof cookie === 'string') {
|
|
20
|
-
const match = cookie
|
|
20
|
+
const match = cookie
|
|
21
|
+
.split(';')
|
|
22
|
+
.map((part) => part.trim())
|
|
23
|
+
.find((part) => part.startsWith('wake_ui_token='));
|
|
21
24
|
if (match !== undefined) {
|
|
22
25
|
return match.slice('wake_ui_token='.length);
|
|
23
26
|
}
|
|
@@ -75,11 +78,17 @@ async function handleRequest(req, res, options, now) {
|
|
|
75
78
|
return;
|
|
76
79
|
}
|
|
77
80
|
if (req.method !== 'GET') {
|
|
78
|
-
sendJson(res, 405, {
|
|
81
|
+
sendJson(res, 405, {
|
|
82
|
+
error: 'this build only serves read endpoints; mutations are not implemented',
|
|
83
|
+
});
|
|
79
84
|
return;
|
|
80
85
|
}
|
|
81
86
|
const { stateStore, resourceIndex, config } = options;
|
|
82
|
-
const segments = url.pathname
|
|
87
|
+
const segments = url.pathname
|
|
88
|
+
.slice('/api/v1/'.length)
|
|
89
|
+
.split('/')
|
|
90
|
+
.filter((part) => part.length > 0)
|
|
91
|
+
.map((s) => decodeURIComponent(s));
|
|
83
92
|
const resource = segments[0];
|
|
84
93
|
if (resource === 'status' && segments.length === 1) {
|
|
85
94
|
sendJson(res, 200, await buildStatus({ stateStore, config, now: now() }));
|
|
@@ -129,7 +138,8 @@ async function handleRequest(req, res, options, now) {
|
|
|
129
138
|
sendJson(res, 200, await buildEventsFeed({
|
|
130
139
|
stateStore,
|
|
131
140
|
workItemKey: url.searchParams.get('workItemKey') ?? undefined,
|
|
132
|
-
direction: url.searchParams.get('direction') ??
|
|
141
|
+
direction: url.searchParams.get('direction') ??
|
|
142
|
+
undefined,
|
|
133
143
|
type: url.searchParams.get('type') ?? undefined,
|
|
134
144
|
limit: limitParam === null ? undefined : Number(limitParam),
|
|
135
145
|
}));
|
|
@@ -3,7 +3,7 @@ import { createCodexRunner } from '../codex/codex-runner.js';
|
|
|
3
3
|
import { createCursorRunner } from '../cursor/cursor-runner.js';
|
|
4
4
|
import { createFakeRunner } from '../fake/fake-runner.js';
|
|
5
5
|
import { resolveRunnerRouting } from '../../domain/runner-routing.js';
|
|
6
|
-
import { chooseAction, workflowForProjection, workflowNameForProjection } from '../../domain/workflows.js';
|
|
6
|
+
import { chooseAction, workflowForProjection, workflowNameForProjection, } from '../../domain/workflows.js';
|
|
7
7
|
export { resolveRunnerRouting } from '../../domain/runner-routing.js';
|
|
8
8
|
function withoutKind(entry) {
|
|
9
9
|
const { kind: _kind, ...settings } = entry;
|
|
@@ -102,9 +102,7 @@ export function createRegistryRunner(input) {
|
|
|
102
102
|
const routing = runInput.routing ??
|
|
103
103
|
(() => {
|
|
104
104
|
const workflow = workflowForProjection(runInput.projection, runInput.config);
|
|
105
|
-
const workflowAction = workflow === null
|
|
106
|
-
? null
|
|
107
|
-
: chooseAction(runInput.projection, workflow);
|
|
105
|
+
const workflowAction = workflow === null ? null : chooseAction(runInput.projection, workflow);
|
|
108
106
|
return resolveRunnerRouting({
|
|
109
107
|
config: runInput.config,
|
|
110
108
|
stage: workflowAction?.stage ?? runInput.projection.wake.stage,
|
|
@@ -52,9 +52,7 @@ function previousCommentsThroughLastRun(projection) {
|
|
|
52
52
|
return cursorIndex === -1 ? [] : projection.comments.slice(0, cursorIndex + 1);
|
|
53
53
|
}
|
|
54
54
|
function matchesCommand(body, pattern) {
|
|
55
|
-
return body
|
|
56
|
-
.split(/\r?\n/)
|
|
57
|
-
.some((line) => pattern.test(line.trim()));
|
|
55
|
+
return body.split(/\r?\n/).some((line) => pattern.test(line.trim()));
|
|
58
56
|
}
|
|
59
57
|
function latestQuestionCommandNote(input) {
|
|
60
58
|
const { comments, successSentinel } = input;
|
|
@@ -147,10 +145,7 @@ function buildUntrustedDataBlock(input) {
|
|
|
147
145
|
'',
|
|
148
146
|
'Issue:',
|
|
149
147
|
...(input.includeRepoDetails
|
|
150
|
-
? [
|
|
151
|
-
`- Repo: ${input.projection.issue.repo}`,
|
|
152
|
-
`- Number: ${input.projection.issue.number}`,
|
|
153
|
-
]
|
|
148
|
+
? [`- Repo: ${input.projection.issue.repo}`, `- Number: ${input.projection.issue.number}`]
|
|
154
149
|
: []),
|
|
155
150
|
`- Title: ${input.projection.issue.title}`,
|
|
156
151
|
`- Stage: ${input.projection.wake.stage}`,
|
|
@@ -196,12 +191,10 @@ export async function buildStagePrompt(input) {
|
|
|
196
191
|
// Default tool capability note — runner adapters can override this via contextOverrides
|
|
197
192
|
// when the runner's tool model differs from Claude Code's named-tool model (e.g. Codex).
|
|
198
193
|
if (mode === 'resume') {
|
|
199
|
-
context.toolCapabilityNote =
|
|
200
|
-
`Reminder: this is still a planning-only stage - your only available tools are: ${allowedToolsListStr}. Do not attempt to use Edit, Write, or any Bash command other than the git commands listed above, or modify any file.`;
|
|
194
|
+
context.toolCapabilityNote = `Reminder: this is still a planning-only stage - your only available tools are: ${allowedToolsListStr}. Do not attempt to use Edit, Write, or any Bash command other than the git commands listed above, or modify any file.`;
|
|
201
195
|
}
|
|
202
196
|
else {
|
|
203
|
-
context.toolCapabilityNote =
|
|
204
|
-
`Your only available tools are: ${allowedToolsListStr}.\nDo not attempt to use Edit, Write, or any Bash command other than the git commands listed above unless this stage's prompt and workspace mode explicitly allow it.`;
|
|
197
|
+
context.toolCapabilityNote = `Your only available tools are: ${allowedToolsListStr}.\nDo not attempt to use Edit, Write, or any Bash command other than the git commands listed above unless this stage's prompt and workspace mode explicitly allow it.`;
|
|
205
198
|
}
|
|
206
199
|
if (input.contextOverrides !== undefined) {
|
|
207
200
|
Object.assign(context, input.contextOverrides);
|
|
@@ -210,12 +203,13 @@ export async function buildStagePrompt(input) {
|
|
|
210
203
|
const permissionMode = template.frontmatter.permissionMode;
|
|
211
204
|
const commentsToAddress = newCommentsSinceLastRun(input.projection);
|
|
212
205
|
const priorComments = previousCommentsThroughLastRun(input.projection);
|
|
213
|
-
context.feedbackCommandNote =
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
206
|
+
context.feedbackCommandNote =
|
|
207
|
+
mode === 'resume'
|
|
208
|
+
? latestQuestionCommandNote({
|
|
209
|
+
comments: commentsToAddress,
|
|
210
|
+
successSentinel: skipApproval ? 'DONE' : 'AWAITING_APPROVAL',
|
|
211
|
+
})
|
|
212
|
+
: '';
|
|
219
213
|
const commentSections = mode === 'resume'
|
|
220
214
|
? [
|
|
221
215
|
{
|
|
@@ -261,7 +255,10 @@ export async function buildStagePrompt(input) {
|
|
|
261
255
|
}),
|
|
262
256
|
allowedTools,
|
|
263
257
|
extraArgs: parseFrontmatterArgs(template.frontmatter.extraArgs),
|
|
264
|
-
maxTurns: parseFrontmatterMaxTurns({
|
|
258
|
+
maxTurns: parseFrontmatterMaxTurns({
|
|
259
|
+
action: input.action,
|
|
260
|
+
value: template.frontmatter.maxTurns,
|
|
261
|
+
}),
|
|
265
262
|
skipApproval,
|
|
266
263
|
...(permissionMode === undefined ? {} : { permissionMode }),
|
|
267
264
|
};
|