@atolis-hq/wake 0.1.4 → 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-client.js +31 -0
- package/dist/src/adapters/github/github-issues-work-source.js +8 -14
- package/dist/src/adapters/github/github-pull-request-activity-source.js +162 -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 +9 -18
- 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 +214 -56
- 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
|
@@ -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
|
};
|
|
@@ -4,13 +4,11 @@ import { createRunnerCliAdapter } from '../adapters/runner/runner-cli-adapter.js
|
|
|
4
4
|
import { runSandboxResumeCommand } from './sandbox-resume.js';
|
|
5
5
|
import { runSelfUpdateCommand, runSelfUpdateLoop } from './self-update-command.js';
|
|
6
6
|
import { runStopCommand } from './stop-command.js';
|
|
7
|
-
import { buildSandboxLoggedCommand
|
|
7
|
+
import { buildSandboxLoggedCommand } from './sandbox-logging.js';
|
|
8
8
|
async function ensureContainerHomeMountParents(input) {
|
|
9
9
|
for (const mount of input.extraMounts) {
|
|
10
10
|
const relativeTarget = posix.relative(input.containerHomeMountPath, mount.target);
|
|
11
|
-
if (relativeTarget.length === 0 ||
|
|
12
|
-
relativeTarget === '.' ||
|
|
13
|
-
relativeTarget.startsWith('..')) {
|
|
11
|
+
if (relativeTarget.length === 0 || relativeTarget === '.' || relativeTarget.startsWith('..')) {
|
|
14
12
|
continue;
|
|
15
13
|
}
|
|
16
14
|
const parentRelativeTarget = posix.dirname(relativeTarget);
|
|
@@ -118,7 +116,9 @@ export async function runSandboxCommand(input) {
|
|
|
118
116
|
throw new Error('Sandbox self-update requires git/issueReporter/ledger dependencies');
|
|
119
117
|
}
|
|
120
118
|
const selfUpdateArgs = input.args.slice(1);
|
|
121
|
-
const runSelfUpdate = selfUpdateArgs.includes('--loop')
|
|
119
|
+
const runSelfUpdate = selfUpdateArgs.includes('--loop')
|
|
120
|
+
? runSelfUpdateLoop
|
|
121
|
+
: runSelfUpdateCommand;
|
|
122
122
|
await runSelfUpdate({
|
|
123
123
|
args: selfUpdateArgs,
|
|
124
124
|
repoRoot,
|
|
@@ -146,7 +146,9 @@ export async function runSandboxCommand(input) {
|
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
if (subcommand === 'setup') {
|
|
149
|
-
await input.docker.exec(input.config.sandbox.containerName, ['bash', '/wake/docker/setup.sh'], {
|
|
149
|
+
await input.docker.exec(input.config.sandbox.containerName, ['bash', '/wake/docker/setup.sh'], {
|
|
150
|
+
interactive: true,
|
|
151
|
+
});
|
|
150
152
|
return;
|
|
151
153
|
}
|
|
152
154
|
if (subcommand === 'exec') {
|
|
@@ -52,7 +52,7 @@ async function defaultCheckRunnerCommand(runnerName, entry) {
|
|
|
52
52
|
}
|
|
53
53
|
catch (error) {
|
|
54
54
|
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
-
throw new Error(`runner "${runnerName}" (${entry.kind}) command "${entry.command}" is not invocable: ${message}
|
|
55
|
+
throw new Error(`runner "${runnerName}" (${entry.kind}) command "${entry.command}" is not invocable: ${message}`, { cause: error });
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
async function assertPromptsRootAccessible(promptsRoot) {
|
|
@@ -91,13 +91,17 @@ export function createControlPlane(deps) {
|
|
|
91
91
|
run: deps.tickRunner.runIntakeTick,
|
|
92
92
|
label: 'intake',
|
|
93
93
|
getIdleTicks: () => consecutiveIntakeIdleTicks,
|
|
94
|
-
setIdleTicks: (value) => {
|
|
94
|
+
setIdleTicks: (value) => {
|
|
95
|
+
consecutiveIntakeIdleTicks = value;
|
|
96
|
+
},
|
|
95
97
|
}),
|
|
96
98
|
startLoop({
|
|
97
99
|
run: deps.tickRunner.runRunnerTick,
|
|
98
100
|
label: 'runner',
|
|
99
101
|
getIdleTicks: () => consecutiveRunnerIdleTicks,
|
|
100
|
-
setIdleTicks: (value) => {
|
|
102
|
+
setIdleTicks: (value) => {
|
|
103
|
+
consecutiveRunnerIdleTicks = value;
|
|
104
|
+
},
|
|
101
105
|
}),
|
|
102
106
|
]);
|
|
103
107
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { awaitingApprovalRunnerSentinel, failedRunnerSentinel
|
|
2
|
-
import { builtInDefaultWorkflowDefinition, chooseAction as chooseWorkflowAction } from '../domain/workflows.js';
|
|
1
|
+
import { awaitingApprovalRunnerSentinel, failedRunnerSentinel } from '../domain/stages.js';
|
|
2
|
+
import { builtInDefaultWorkflowDefinition, chooseAction as chooseWorkflowAction, } from '../domain/workflows.js';
|
|
3
3
|
function isAwaitingApproval(issue) {
|
|
4
4
|
const context = issue.context;
|
|
5
5
|
return context.lastRunSentinel === awaitingApprovalRunnerSentinel;
|
|
@@ -15,9 +15,7 @@ const questionCommandPattern = /^\/question\b/i;
|
|
|
15
15
|
// lateral response to a PR surface, not a workflow stage.
|
|
16
16
|
const reviewFeedbackAction = 'revise';
|
|
17
17
|
function matchesCommand(body, pattern) {
|
|
18
|
-
return body
|
|
19
|
-
.split(/\r?\n/)
|
|
20
|
-
.some((line) => pattern.test(line.trim()));
|
|
18
|
+
return body.split(/\r?\n/).some((line) => pattern.test(line.trim()));
|
|
21
19
|
}
|
|
22
20
|
function labelsAndAssigneesQualify(input) {
|
|
23
21
|
if (input.requiredLabels.length === 0 && input.requiredAssignees.length === 0) {
|
|
@@ -31,16 +29,15 @@ function labelsAndAssigneesQualify(input) {
|
|
|
31
29
|
if (input.ignoredLabels.some((label) => labels.has(label))) {
|
|
32
30
|
return false;
|
|
33
31
|
}
|
|
34
|
-
if (input.requiredAssignees.length > 0 &&
|
|
32
|
+
if (input.requiredAssignees.length > 0 &&
|
|
33
|
+
!input.requiredAssignees.some((login) => assignees.has(login))) {
|
|
35
34
|
return false;
|
|
36
35
|
}
|
|
37
36
|
return true;
|
|
38
37
|
}
|
|
39
38
|
function latestUnhandledHumanComment(issue) {
|
|
40
39
|
const context = issue.context;
|
|
41
|
-
const handledCommentId = typeof context.lastHandledCommentId === 'string'
|
|
42
|
-
? context.lastHandledCommentId
|
|
43
|
-
: undefined;
|
|
40
|
+
const handledCommentId = typeof context.lastHandledCommentId === 'string' ? context.lastHandledCommentId : undefined;
|
|
44
41
|
// Only consider human comments that appear after the last bot comment.
|
|
45
42
|
// A human /approved posted before Wake's approval-request comment must not
|
|
46
43
|
// be re-consumed as approval for a later awaiting-approval cycle.
|
|
@@ -78,18 +75,10 @@ export function createPolicyEngine() {
|
|
|
78
75
|
},
|
|
79
76
|
needsWakeAction(issue, workflow = builtInDefaultWorkflowDefinition) {
|
|
80
77
|
const context = issue.context;
|
|
81
|
-
const handledCommentId = typeof context.lastHandledCommentId === 'string'
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
? context.lastCompletedAction
|
|
86
|
-
: undefined;
|
|
87
|
-
const lastRunSentinel = typeof context.lastRunSentinel === 'string'
|
|
88
|
-
? context.lastRunSentinel
|
|
89
|
-
: undefined;
|
|
90
|
-
const lastFailureClass = typeof context.lastFailureClass === 'string'
|
|
91
|
-
? context.lastFailureClass
|
|
92
|
-
: undefined;
|
|
78
|
+
const handledCommentId = typeof context.lastHandledCommentId === 'string' ? context.lastHandledCommentId : undefined;
|
|
79
|
+
const lastCompletedAction = typeof context.lastCompletedAction === 'string' ? context.lastCompletedAction : undefined;
|
|
80
|
+
const lastRunSentinel = typeof context.lastRunSentinel === 'string' ? context.lastRunSentinel : undefined;
|
|
81
|
+
const lastFailureClass = typeof context.lastFailureClass === 'string' ? context.lastFailureClass : undefined;
|
|
93
82
|
if (issue.wake.lastRunId === undefined) {
|
|
94
83
|
return true;
|
|
95
84
|
}
|
|
@@ -15,14 +15,12 @@ function configuredStagesForLabels(config) {
|
|
|
15
15
|
return workflow === undefined ? undefined : workflowStageVocabulary(workflow);
|
|
16
16
|
}
|
|
17
17
|
function createProjectionFromIssueEvent(event, config) {
|
|
18
|
-
const issue = event.sourceEventType === 'ticket.upsert'
|
|
19
|
-
? event.payload.ticket
|
|
20
|
-
: event.payload.issue;
|
|
18
|
+
const issue = event.sourceEventType === 'ticket.upsert' ? event.payload.ticket : event.payload.issue;
|
|
21
19
|
if (issue === undefined || typeof issue !== 'object' || issue === null) {
|
|
22
20
|
return null;
|
|
23
21
|
}
|
|
24
22
|
const labels = Array.isArray(issue.labels)
|
|
25
|
-
?
|
|
23
|
+
? issue.labels
|
|
26
24
|
: [];
|
|
27
25
|
return parseIssueStateRecord({
|
|
28
26
|
schemaVersion: 1,
|
|
@@ -50,8 +48,7 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
50
48
|
if (event.workItemKey === UNRESOLVED_WORK_ITEM_KEY) {
|
|
51
49
|
return current;
|
|
52
50
|
}
|
|
53
|
-
if (event.sourceEventType === 'fake.issue.upsert' ||
|
|
54
|
-
event.sourceEventType === 'ticket.upsert') {
|
|
51
|
+
if (event.sourceEventType === 'fake.issue.upsert' || event.sourceEventType === 'ticket.upsert') {
|
|
55
52
|
const next = createProjectionFromIssueEvent(event, config);
|
|
56
53
|
if (next === null) {
|
|
57
54
|
return current;
|
|
@@ -91,7 +88,8 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
91
88
|
event.sourceEventType === 'ticket.comment.updated' ||
|
|
92
89
|
event.sourceEventType === 'pr.comment.created' ||
|
|
93
90
|
event.sourceEventType === 'pr.review.created' ||
|
|
94
|
-
event.sourceEventType === 'pr.review-comment.created'
|
|
91
|
+
event.sourceEventType === 'pr.review-comment.created' ||
|
|
92
|
+
event.sourceEventType === 'pr.checks.failed') {
|
|
95
93
|
const comment = event.payload.comment;
|
|
96
94
|
if (comment === undefined || typeof comment !== 'object' || comment === null) {
|
|
97
95
|
return current;
|
|
@@ -149,8 +147,7 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
149
147
|
// Clear the session when the stage moves forward (new action needed) or the
|
|
150
148
|
// run failed outright. Keep it for BLOCKED so the same action can resume
|
|
151
149
|
// the in-progress session after a human replies.
|
|
152
|
-
const isForwardProgression = payload.nextStage !== undefined &&
|
|
153
|
-
payload.nextStage !== current.wake.stage;
|
|
150
|
+
const isForwardProgression = payload.nextStage !== undefined && payload.nextStage !== current.wake.stage;
|
|
154
151
|
const stageChanged = payload.nextStage !== undefined && payload.nextStage !== current.wake.stage;
|
|
155
152
|
const isFailed = payload.sentinel === 'FAILED';
|
|
156
153
|
const shouldClearSession = isForwardProgression || isFailed;
|
|
@@ -162,12 +159,8 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
162
159
|
...(payload.handledCommentId === undefined
|
|
163
160
|
? {}
|
|
164
161
|
: { lastHandledCommentId: payload.handledCommentId }),
|
|
165
|
-
...(payload.sentinel === undefined
|
|
166
|
-
|
|
167
|
-
: { lastRunSentinel: payload.sentinel }),
|
|
168
|
-
...(payload.action === undefined
|
|
169
|
-
? {}
|
|
170
|
-
: { lastRunAction: payload.action }),
|
|
162
|
+
...(payload.sentinel === undefined ? {} : { lastRunSentinel: payload.sentinel }),
|
|
163
|
+
...(payload.action === undefined ? {} : { lastRunAction: payload.action }),
|
|
171
164
|
...(payload.sentinel === doneRunnerSentinel && payload.action !== undefined
|
|
172
165
|
? { lastCompletedAction: payload.action }
|
|
173
166
|
: {}),
|
|
@@ -181,9 +174,7 @@ async function applyEvent(current, event, ctx, config) {
|
|
|
181
174
|
...current.wake,
|
|
182
175
|
stage: payload.nextStage ?? current.wake.stage,
|
|
183
176
|
lastRunId: payload.runId ?? current.wake.lastRunId,
|
|
184
|
-
sessionId: shouldClearSession
|
|
185
|
-
? undefined
|
|
186
|
-
: (payload.sessionId ?? current.wake.sessionId),
|
|
177
|
+
sessionId: shouldClearSession ? undefined : (payload.sessionId ?? current.wake.sessionId),
|
|
187
178
|
sessionCli: shouldClearSession
|
|
188
179
|
? undefined
|
|
189
180
|
: (payload.sessionCli ?? current.wake.sessionCli),
|
|
@@ -61,7 +61,7 @@ export function resolveQuotaPauseUntil(input) {
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
const boundedFailureCount = Math.max(1, Math.min(input.failureCount, 6));
|
|
64
|
-
const delayMs = Math.min(15 * 60_000 *
|
|
64
|
+
const delayMs = Math.min(15 * 60_000 * 2 ** (boundedFailureCount - 1), maxEstimatedPauseMs);
|
|
65
65
|
return {
|
|
66
66
|
pausedUntil: new Date(input.now.getTime() + delayMs).toISOString(),
|
|
67
67
|
source: 'estimated',
|
|
@@ -46,18 +46,16 @@ export function createOutboundSinkRouter(input) {
|
|
|
46
46
|
const origin = typeof event.payload.origin === 'string' ? event.payload.origin : undefined;
|
|
47
47
|
const sourceOrigin = event.sourceRefs.sink ?? origin;
|
|
48
48
|
if (event.sourceEventType === 'wake.labels.requested') {
|
|
49
|
-
const projectionOrigin = typeof event.payload.origin === 'string'
|
|
50
|
-
? event.payload.origin
|
|
51
|
-
: undefined;
|
|
49
|
+
const projectionOrigin = typeof event.payload.origin === 'string' ? event.payload.origin : undefined;
|
|
52
50
|
if (projectionOrigin !== undefined) {
|
|
53
51
|
targetSinks.add(projectionOrigin);
|
|
54
52
|
}
|
|
55
53
|
}
|
|
56
|
-
const kind = intentKind(event);
|
|
57
54
|
const resourceUri = event.sourceRefs.resourceUri;
|
|
58
|
-
if (event.sourceEventType === 'wake.publish.intent.requested' &&
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
if (event.sourceEventType === 'wake.publish.intent.requested' && sourceOrigin !== undefined) {
|
|
56
|
+
const resourceSink = resourceUri === undefined
|
|
57
|
+
? sourceOrigin
|
|
58
|
+
: sinkNameForResourceUri(resourceUri, sourceOrigin);
|
|
61
59
|
// A resource-derived sink name (e.g. a PR surface) may not be
|
|
62
60
|
// registered — the source that owns it can be disabled independently
|
|
63
61
|
// of the origin sink. Falling back to sourceOrigin here, rather than
|
|
@@ -169,9 +169,7 @@ export function createTickRunner(deps) {
|
|
|
169
169
|
...(input.runnerResult.tokenUsage?.costUsd === undefined
|
|
170
170
|
? {}
|
|
171
171
|
: { cost: formatCostUsd(input.runnerResult.tokenUsage.costUsd) }),
|
|
172
|
-
...(input.workspacePath === undefined
|
|
173
|
-
? {}
|
|
174
|
-
: { workspacePath: input.workspacePath }),
|
|
172
|
+
...(input.workspacePath === undefined ? {} : { workspacePath: input.workspacePath }),
|
|
175
173
|
},
|
|
176
174
|
derivedHints: {
|
|
177
175
|
stage: input.sentinel === 'DONE' ? 'done' : input.projection.wake.stage,
|
|
@@ -468,7 +466,8 @@ export function createTickRunner(deps) {
|
|
|
468
466
|
const owner = await deps.resourceIndex.resolve(resourceUri);
|
|
469
467
|
if (persisted.workItemKey !== UNRESOLVED_WORK_ITEM_KEY &&
|
|
470
468
|
owner === undefined &&
|
|
471
|
-
(await deps.stateStore.readEventEnvelope(`${persisted.workItemKey}-origin-correlation`)) ===
|
|
469
|
+
(await deps.stateStore.readEventEnvelope(`${persisted.workItemKey}-origin-correlation`)) ===
|
|
470
|
+
null) {
|
|
472
471
|
return [
|
|
473
472
|
{ envelope: persisted, persisted: true },
|
|
474
473
|
...buildOriginCorrelationEvents(persisted.workItemKey, unkeyed, resourceUri).map((envelope) => ({ envelope, persisted: false })),
|
|
@@ -653,8 +652,7 @@ export function createTickRunner(deps) {
|
|
|
653
652
|
async function reconcileStaleRunningRecords(now) {
|
|
654
653
|
const finishedAt = now.toISOString();
|
|
655
654
|
const runRecords = await deps.stateStore.listRunRecords();
|
|
656
|
-
const staleRecords = runRecords
|
|
657
|
-
.filter((record) => isStaleRunningRecord(record, now));
|
|
655
|
+
const staleRecords = runRecords.filter((record) => isStaleRunningRecord(record, now));
|
|
658
656
|
for (const record of staleRecords) {
|
|
659
657
|
// Run records carry the work item they belong to, so this is a direct
|
|
660
658
|
// O(1) read — no scan, no index, no source ambiguity. The record's
|
|
@@ -949,8 +947,7 @@ export function createTickRunner(deps) {
|
|
|
949
947
|
if (isAwaitingApproval(issue)) {
|
|
950
948
|
return policy.needsWakeAction(issue, workflow);
|
|
951
949
|
}
|
|
952
|
-
const nextAction = policy.chooseAction(issue, workflow) ??
|
|
953
|
-
policy.chooseRetryActionAfterHumanReply(issue);
|
|
950
|
+
const nextAction = policy.chooseAction(issue, workflow) ?? policy.chooseRetryActionAfterHumanReply(issue);
|
|
954
951
|
return nextAction !== null && policy.needsWakeAction(issue, workflow);
|
|
955
952
|
});
|
|
956
953
|
if (candidate === undefined) {
|
|
@@ -1042,9 +1039,7 @@ export function createTickRunner(deps) {
|
|
|
1042
1039
|
// scratch (#258 follow-up incident: a FAILED `revise` run fell
|
|
1043
1040
|
// back to a full fresh `implement` run and lost the PR-feedback
|
|
1044
1041
|
// context).
|
|
1045
|
-
const nextAction = policy.chooseRetryActionAfterHumanReply(candidate) ??
|
|
1046
|
-
workflowAction?.action ??
|
|
1047
|
-
null;
|
|
1042
|
+
const nextAction = policy.chooseRetryActionAfterHumanReply(candidate) ?? workflowAction?.action ?? null;
|
|
1048
1043
|
if (nextAction === null) {
|
|
1049
1044
|
return { status: 'idle' };
|
|
1050
1045
|
}
|
|
@@ -1146,9 +1141,7 @@ export function createTickRunner(deps) {
|
|
|
1146
1141
|
// An agent that writes DONE but was told not to skip approval has violated
|
|
1147
1142
|
// the protocol; treat it as AWAITING_APPROVAL so the gate is enforced.
|
|
1148
1143
|
const skipApproval = runnerResult.metadata?.skipApproval;
|
|
1149
|
-
const sentinel = rawSentinel === 'DONE' && skipApproval === false
|
|
1150
|
-
? 'AWAITING_APPROVAL'
|
|
1151
|
-
: rawSentinel;
|
|
1144
|
+
const sentinel = rawSentinel === 'DONE' && skipApproval === false ? 'AWAITING_APPROVAL' : rawSentinel;
|
|
1152
1145
|
const nextStage = lifecycle.nextStageFromSentinel(claimedStage, sentinel, workflow);
|
|
1153
1146
|
const finishedAt = deps.clock.now().toISOString();
|
|
1154
1147
|
if (workspaceMode === 'branch') {
|
|
@@ -15,7 +15,7 @@ export function maxConfiguredRunnerTimeoutMs(config) {
|
|
|
15
15
|
}
|
|
16
16
|
const registryTimeouts = [...activeRunnerNames]
|
|
17
17
|
.map((name) => config.runners[name])
|
|
18
|
-
.map((entry) => entry === undefined || entry.kind === 'fake' ? undefined : entry.timeoutMs)
|
|
18
|
+
.map((entry) => (entry === undefined || entry.kind === 'fake' ? undefined : entry.timeoutMs))
|
|
19
19
|
.filter((timeout) => timeout !== undefined);
|
|
20
20
|
return registryTimeouts.length > 0 ? Math.max(...registryTimeouts) : Infinity;
|
|
21
21
|
}
|