@exaudeus/workrail 3.75.0 → 3.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/console-ui/assets/index-D9pYbwS0.js +28 -0
- package/dist/console-ui/index.html +1 -1
- package/dist/coordinators/adaptive-pipeline.d.ts +8 -0
- package/dist/coordinators/context-assembly.d.ts +4 -0
- package/dist/coordinators/context-assembly.js +156 -0
- package/dist/coordinators/modes/full-pipeline.d.ts +1 -1
- package/dist/coordinators/modes/full-pipeline.js +140 -27
- package/dist/coordinators/modes/implement-shared.d.ts +3 -2
- package/dist/coordinators/modes/implement-shared.js +16 -6
- package/dist/coordinators/modes/implement.js +49 -3
- package/dist/coordinators/pipeline-run-context.d.ts +1811 -0
- package/dist/coordinators/pipeline-run-context.js +114 -0
- package/dist/daemon/context-loader.d.ts +1 -1
- package/dist/daemon/core/agent-client.d.ts +7 -0
- package/dist/daemon/core/agent-client.js +31 -0
- package/dist/daemon/core/index.d.ts +6 -0
- package/dist/daemon/core/index.js +19 -0
- package/dist/daemon/core/session-context.d.ts +14 -0
- package/dist/daemon/core/session-context.js +24 -0
- package/dist/daemon/core/session-result.d.ts +10 -0
- package/dist/daemon/core/session-result.js +92 -0
- package/dist/daemon/core/system-prompt.d.ts +6 -0
- package/dist/daemon/core/system-prompt.js +151 -0
- package/dist/daemon/io/conversation-log.d.ts +2 -0
- package/dist/daemon/io/conversation-log.js +45 -0
- package/dist/daemon/io/execution-stats.d.ts +7 -0
- package/dist/daemon/io/execution-stats.js +86 -0
- package/dist/daemon/io/index.d.ts +5 -0
- package/dist/daemon/io/index.js +24 -0
- package/dist/daemon/io/session-notes-loader.d.ts +4 -0
- package/dist/daemon/io/session-notes-loader.js +45 -0
- package/dist/daemon/io/soul-loader.d.ts +3 -0
- package/dist/daemon/io/soul-loader.js +68 -0
- package/dist/daemon/io/workspace-context-loader.d.ts +17 -0
- package/dist/daemon/io/workspace-context-loader.js +137 -0
- package/dist/daemon/runner/agent-loop-runner.d.ts +28 -0
- package/dist/daemon/runner/agent-loop-runner.js +250 -0
- package/dist/daemon/runner/construct-tools.d.ts +5 -0
- package/dist/daemon/runner/construct-tools.js +30 -0
- package/dist/daemon/runner/finalize-session.d.ts +3 -0
- package/dist/daemon/runner/finalize-session.js +75 -0
- package/dist/daemon/runner/index.d.ts +8 -0
- package/dist/daemon/runner/index.js +18 -0
- package/dist/daemon/runner/pre-agent-session.d.ts +7 -0
- package/dist/daemon/runner/pre-agent-session.js +227 -0
- package/dist/daemon/runner/runner-types.d.ts +73 -0
- package/dist/daemon/runner/runner-types.js +39 -0
- package/dist/daemon/runner/tool-schemas.d.ts +1 -0
- package/dist/daemon/runner/tool-schemas.js +151 -0
- package/dist/daemon/session-scope.d.ts +1 -1
- package/dist/daemon/startup-recovery.d.ts +20 -0
- package/dist/daemon/startup-recovery.js +323 -0
- package/dist/daemon/state/index.d.ts +6 -0
- package/dist/daemon/state/index.js +14 -0
- package/dist/daemon/state/session-state.d.ts +23 -0
- package/dist/daemon/state/session-state.js +44 -0
- package/dist/daemon/state/stuck-detection.d.ts +22 -0
- package/dist/daemon/state/stuck-detection.js +25 -0
- package/dist/daemon/state/terminal-signal.d.ts +9 -0
- package/dist/daemon/state/terminal-signal.js +10 -0
- package/dist/daemon/tools/file-tools.d.ts +1 -1
- package/dist/daemon/turn-end/detect-stuck.d.ts +2 -2
- package/dist/daemon/turn-end/detect-stuck.js +2 -2
- package/dist/daemon/turn-end/step-injector.d.ts +1 -1
- package/dist/daemon/types.d.ts +105 -0
- package/dist/daemon/types.js +11 -0
- package/dist/daemon/workflow-enricher.d.ts +16 -0
- package/dist/daemon/workflow-enricher.js +58 -0
- package/dist/daemon/workflow-runner.d.ts +13 -277
- package/dist/daemon/workflow-runner.js +63 -1421
- package/dist/manifest.json +280 -56
- package/dist/trigger/coordinator-deps.d.ts +1 -1
- package/dist/trigger/coordinator-deps.js +131 -0
- package/dist/trigger/delivery-client.d.ts +1 -1
- package/dist/trigger/delivery-pipeline.d.ts +1 -1
- package/dist/trigger/notification-service.d.ts +1 -1
- package/dist/trigger/trigger-listener.js +6 -2
- package/dist/trigger/trigger-router.d.ts +2 -2
- package/dist/v2/durable-core/domain/artifact-contract-validator.js +99 -0
- package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.d.ts +39 -0
- package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.js +10 -1
- package/dist/v2/durable-core/schemas/artifacts/index.d.ts +2 -1
- package/dist/v2/durable-core/schemas/artifacts/index.js +12 -1
- package/dist/v2/durable-core/schemas/artifacts/phase-handoff.d.ts +89 -0
- package/dist/v2/durable-core/schemas/artifacts/phase-handoff.js +56 -0
- package/docs/authoring-v2.md +12 -0
- package/docs/ideas/backlog.md +639 -25
- package/docs/reference/worktrain-daemon-invariants.md +33 -49
- package/docs/vision.md +5 -15
- package/package.json +2 -2
- package/workflows/coding-task-workflow-agentic.json +9 -6
- package/workflows/mr-review-workflow.agentic.v2.json +2 -2
- package/workflows/wr.discovery.json +2 -1
- package/workflows/wr.shaping.json +7 -4
- package/dist/console-ui/assets/index-BvBihscd.js +0 -28
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readDaemonSessionState = readDaemonSessionState;
|
|
37
|
+
exports.readAllDaemonSessions = readAllDaemonSessions;
|
|
38
|
+
exports.runStartupRecovery = runStartupRecovery;
|
|
39
|
+
exports.countOrphanStepAdvances = countOrphanStepAdvances;
|
|
40
|
+
exports.clearQueueIssueSidecars = clearQueueIssueSidecars;
|
|
41
|
+
require("reflect-metadata");
|
|
42
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const node_child_process_1 = require("node:child_process");
|
|
45
|
+
const node_util_1 = require("node:util");
|
|
46
|
+
const index_js_1 = require("../mcp/handlers/v2-execution/index.js");
|
|
47
|
+
const v2_token_ops_js_1 = require("../mcp/handlers/v2-token-ops.js");
|
|
48
|
+
const index_js_2 = require("../v2/durable-core/ids/index.js");
|
|
49
|
+
const assert_never_js_1 = require("../runtime/assert-never.js");
|
|
50
|
+
const session_recovery_policy_js_1 = require("./session-recovery-policy.js");
|
|
51
|
+
const _shared_js_1 = require("./tools/_shared.js");
|
|
52
|
+
const workflow_runner_js_1 = require("./workflow-runner.js");
|
|
53
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
54
|
+
const MAX_ORPHAN_AGE_MS = 2 * 60 * 60 * 1000;
|
|
55
|
+
const MAX_WORKTREE_ORPHAN_AGE_MS = 24 * 60 * 60 * 1000;
|
|
56
|
+
async function readDaemonSessionState(sessionId) {
|
|
57
|
+
const sessionPath = path.join(_shared_js_1.DAEMON_SESSIONS_DIR, `${sessionId}.json`);
|
|
58
|
+
try {
|
|
59
|
+
const raw = await fs.readFile(sessionPath, 'utf8');
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
return { continueToken: parsed.continueToken, checkpointToken: parsed.checkpointToken };
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function readAllDaemonSessions(sessionsDir = _shared_js_1.DAEMON_SESSIONS_DIR) {
|
|
68
|
+
let entries;
|
|
69
|
+
try {
|
|
70
|
+
entries = await fs.readdir(sessionsDir);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
const isEnoent = err instanceof Error && 'code' in err && err.code === 'ENOENT';
|
|
74
|
+
if (!isEnoent) {
|
|
75
|
+
console.warn(`[WorkflowRunner] Could not read sessions directory ${sessionsDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
76
|
+
}
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
const sessions = [];
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
if (!entry.endsWith('.json') || entry.startsWith('queue-issue-'))
|
|
82
|
+
continue;
|
|
83
|
+
const sessionId = entry.slice(0, -5);
|
|
84
|
+
const filePath = path.join(sessionsDir, entry);
|
|
85
|
+
try {
|
|
86
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
87
|
+
const parsed = JSON.parse(raw);
|
|
88
|
+
if (typeof parsed.continueToken !== 'string' || typeof parsed.ts !== 'number') {
|
|
89
|
+
console.warn(`[WorkflowRunner] Skipping malformed session file: ${filePath}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
sessions.push({
|
|
93
|
+
sessionId,
|
|
94
|
+
continueToken: parsed.continueToken,
|
|
95
|
+
checkpointToken: typeof parsed.checkpointToken === 'string' ? parsed.checkpointToken : null,
|
|
96
|
+
ts: parsed.ts,
|
|
97
|
+
...(typeof parsed.worktreePath === 'string' ? { worktreePath: parsed.worktreePath } : {}),
|
|
98
|
+
...(typeof parsed.workflowId === 'string' ? { workflowId: parsed.workflowId } : {}),
|
|
99
|
+
...(typeof parsed.goal === 'string' ? { goal: parsed.goal } : {}),
|
|
100
|
+
...(typeof parsed.workspacePath === 'string' ? { workspacePath: parsed.workspacePath } : {}),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.warn(`[WorkflowRunner] Skipping unreadable session file ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return sessions;
|
|
108
|
+
}
|
|
109
|
+
async function runStartupRecovery(sessionsDir = _shared_js_1.DAEMON_SESSIONS_DIR, execFn = execFileAsync, ctx, _countStepAdvancesFn = countOrphanStepAdvances, _executeContinueWorkflowFn = index_js_1.executeContinueWorkflow, _runWorkflowFn = workflow_runner_js_1.runWorkflow, apiKey = '') {
|
|
110
|
+
await clearQueueIssueSidecars(sessionsDir);
|
|
111
|
+
const sessions = await readAllDaemonSessions(sessionsDir);
|
|
112
|
+
if (sessions.length === 0) {
|
|
113
|
+
await clearStrayTmpFiles(sessionsDir);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.log(`[WorkflowRunner] Startup recovery: found ${sessions.length} orphaned session(s).`);
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
let cleared = 0;
|
|
119
|
+
let preserved = 0;
|
|
120
|
+
for (const session of sessions) {
|
|
121
|
+
const ageMs = now - session.ts;
|
|
122
|
+
const isStale = ageMs > MAX_ORPHAN_AGE_MS;
|
|
123
|
+
const ageSec = Math.round(ageMs / 1000);
|
|
124
|
+
if (session.worktreePath && ageMs > MAX_WORKTREE_ORPHAN_AGE_MS) {
|
|
125
|
+
console.log(`[WorkflowRunner] Removing orphan worktree: sessionId=${session.sessionId} worktreePath=${session.worktreePath}`);
|
|
126
|
+
try {
|
|
127
|
+
await execFn('git', ['worktree', 'remove', '--force', session.worktreePath]);
|
|
128
|
+
console.log(`[WorkflowRunner] Removed orphan worktree: ${session.worktreePath}`);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
console.warn(`[WorkflowRunner] Could not remove orphan worktree ${session.worktreePath}: ` +
|
|
132
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (session.worktreePath && ageMs <= MAX_WORKTREE_ORPHAN_AGE_MS) {
|
|
136
|
+
const ageHours = (ageMs / (60 * 60 * 1000)).toFixed(1);
|
|
137
|
+
console.log(`[WorkflowRunner] Keeping recent orphan worktree: sessionId=${session.sessionId} ` +
|
|
138
|
+
`age=${ageHours}h (threshold=24h) worktreePath=${session.worktreePath}`);
|
|
139
|
+
}
|
|
140
|
+
if (ctx !== undefined) {
|
|
141
|
+
let stepAdvances = 0;
|
|
142
|
+
try {
|
|
143
|
+
stepAdvances = await _countStepAdvancesFn(session.continueToken, ctx);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
console.warn(`[WorkflowRunner] Could not count step advances for orphaned session ${session.sessionId}: ` +
|
|
147
|
+
`${err instanceof Error ? err.message : String(err)} -- falling back to discard`);
|
|
148
|
+
}
|
|
149
|
+
const action = (0, session_recovery_policy_js_1.evaluateRecovery)({ stepAdvances, ageMs });
|
|
150
|
+
switch (action) {
|
|
151
|
+
case 'resume': {
|
|
152
|
+
const hasContext = typeof session.workflowId === 'string' &&
|
|
153
|
+
typeof session.workspacePath === 'string';
|
|
154
|
+
if (!hasContext) {
|
|
155
|
+
console.log(`[WorkflowRunner] Startup recovery: cannot resume session ${session.sessionId} -- ` +
|
|
156
|
+
`missing workflowId/workspacePath in sidecar (old format). Discarding.`);
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
if (isStale) {
|
|
160
|
+
console.log(`[WorkflowRunner] Startup recovery: discarding stale resumable session ${session.sessionId} ` +
|
|
161
|
+
`(age=${ageSec}s > ${MAX_ORPHAN_AGE_MS / 1000}s threshold).`);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
if (session.worktreePath !== undefined) {
|
|
165
|
+
let worktreeExists = true;
|
|
166
|
+
try {
|
|
167
|
+
await fs.access(session.worktreePath);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
worktreeExists = false;
|
|
171
|
+
}
|
|
172
|
+
if (!worktreeExists) {
|
|
173
|
+
console.log(`[WorkflowRunner] Startup recovery: discarding session ${session.sessionId} -- ` +
|
|
174
|
+
`worktree no longer exists at ${session.worktreePath}.`);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
let rehydrateResult;
|
|
179
|
+
try {
|
|
180
|
+
rehydrateResult = await _executeContinueWorkflowFn({ continueToken: session.continueToken, intent: 'rehydrate' }, ctx);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
console.warn(`[WorkflowRunner] Startup recovery: rehydrate failed for session ${session.sessionId}: ` +
|
|
184
|
+
`${err instanceof Error ? err.message : String(err)}. Discarding.`);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (rehydrateResult.isErr()) {
|
|
188
|
+
console.warn(`[WorkflowRunner] Startup recovery: rehydrate error for session ${session.sessionId}: ` +
|
|
189
|
+
`${rehydrateResult.error.kind}. Discarding.`);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
const rehydrated = rehydrateResult.value.response;
|
|
193
|
+
if (rehydrated.isComplete || !rehydrated.pending) {
|
|
194
|
+
console.log(`[WorkflowRunner] Startup recovery: session ${session.sessionId} is already complete ` +
|
|
195
|
+
`or has no pending step. Discarding.`);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
const recoveryAllocatedSession = {
|
|
199
|
+
continueToken: rehydrated.continueToken ?? '',
|
|
200
|
+
checkpointToken: rehydrated.checkpointToken,
|
|
201
|
+
firstStepPrompt: rehydrated.pending.prompt ?? '',
|
|
202
|
+
isComplete: rehydrated.isComplete,
|
|
203
|
+
triggerSource: 'daemon',
|
|
204
|
+
...(session.worktreePath !== undefined
|
|
205
|
+
? { sessionWorkspacePath: session.worktreePath }
|
|
206
|
+
: {}),
|
|
207
|
+
};
|
|
208
|
+
const branchStrategy = 'none';
|
|
209
|
+
const recoveredTrigger = {
|
|
210
|
+
workflowId: session.workflowId,
|
|
211
|
+
goal: session.goal ?? 'Resumed session (crash recovery)',
|
|
212
|
+
workspacePath: session.workspacePath,
|
|
213
|
+
branchStrategy,
|
|
214
|
+
};
|
|
215
|
+
const recoverySource = {
|
|
216
|
+
kind: 'pre_allocated',
|
|
217
|
+
trigger: recoveredTrigger,
|
|
218
|
+
session: recoveryAllocatedSession,
|
|
219
|
+
};
|
|
220
|
+
console.log(`[WorkflowRunner] Startup recovery: resuming session ${session.sessionId} ` +
|
|
221
|
+
`workflowId=${session.workflowId} stepAdvances=${stepAdvances}`);
|
|
222
|
+
void _runWorkflowFn(recoveredTrigger, ctx, apiKey, undefined, undefined, undefined, undefined, undefined, recoverySource).then((result) => {
|
|
223
|
+
console.log(`[WorkflowRunner] Startup recovery: resumed session ${session.sessionId} completed: ${result._tag}`);
|
|
224
|
+
}).catch((err) => {
|
|
225
|
+
console.warn(`[WorkflowRunner] Startup recovery: resumed session ${session.sessionId} failed: ` +
|
|
226
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
227
|
+
});
|
|
228
|
+
preserved++;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
case 'discard': {
|
|
232
|
+
const label = isStale ? 'stale orphaned session' : 'orphaned session';
|
|
233
|
+
console.log(`[WorkflowRunner] Discarding ${label}: sessionId=${session.sessionId} ` +
|
|
234
|
+
`stepAdvances=${stepAdvances} age=${ageSec}s`);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
default:
|
|
238
|
+
(0, assert_never_js_1.assertNever)(action);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
const label = isStale ? 'stale orphaned session' : 'orphaned session';
|
|
243
|
+
console.log(`[WorkflowRunner] Clearing ${label}: sessionId=${session.sessionId} age=${ageSec}s`);
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
await fs.unlink(path.join(sessionsDir, `${session.sessionId}.json`));
|
|
247
|
+
cleared++;
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const isEnoent = err instanceof Error && 'code' in err && err.code === 'ENOENT';
|
|
251
|
+
if (!isEnoent) {
|
|
252
|
+
console.warn(`[WorkflowRunner] Could not clear session file ${session.sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
await clearStrayTmpFiles(sessionsDir);
|
|
257
|
+
if (ctx !== undefined) {
|
|
258
|
+
console.log(`[WorkflowRunner] Startup recovery complete: preserved=${preserved} discarded=${cleared}/${sessions.length} orphaned session(s).`);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
console.log(`[WorkflowRunner] Startup recovery complete: cleared ${cleared}/${sessions.length} orphaned session(s).`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function countOrphanStepAdvances(continueToken, ctx, _parseFn = undefined, _loadFn = undefined) {
|
|
265
|
+
const parseFn = _parseFn ?? ((raw) => (0, v2_token_ops_js_1.parseContinueTokenOrFail)(raw, ctx.v2.tokenCodecPorts, ctx.v2.tokenAliasStore));
|
|
266
|
+
const loadFn = _loadFn ?? ctx.v2.sessionStore.loadValidatedPrefix.bind(ctx.v2.sessionStore);
|
|
267
|
+
const resolvedResult = await parseFn(continueToken);
|
|
268
|
+
if (resolvedResult.isErr()) {
|
|
269
|
+
console.warn(`[WorkflowRunner] Could not decode continueToken for orphaned session: ${resolvedResult.error.message}`);
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
const sessionId = (0, index_js_2.asSessionId)(resolvedResult.value.sessionId);
|
|
273
|
+
const loadResult = await loadFn(sessionId);
|
|
274
|
+
if (loadResult.isErr()) {
|
|
275
|
+
console.warn(`[WorkflowRunner] Could not load session event log for orphaned session: ${loadResult.error.code} -- ${loadResult.error.message}`);
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
const events = loadResult.value.truth.events;
|
|
279
|
+
return events.filter((e) => e.kind === 'advance_recorded').length;
|
|
280
|
+
}
|
|
281
|
+
async function clearQueueIssueSidecars(sessionsDir) {
|
|
282
|
+
let entries;
|
|
283
|
+
try {
|
|
284
|
+
entries = await fs.readdir(sessionsDir);
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
for (const entry of entries) {
|
|
290
|
+
if (!entry.startsWith('queue-issue-') || !entry.endsWith('.json'))
|
|
291
|
+
continue;
|
|
292
|
+
try {
|
|
293
|
+
await fs.unlink(path.join(sessionsDir, entry));
|
|
294
|
+
const issueNum = entry.slice('queue-issue-'.length, -'.json'.length);
|
|
295
|
+
console.log(`[WorkflowRunner] Cleared queue-issue sidecar: issue=${issueNum}`);
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
const isEnoent = err instanceof Error && 'code' in err && err.code === 'ENOENT';
|
|
299
|
+
if (!isEnoent) {
|
|
300
|
+
console.warn(`[WorkflowRunner] Could not clear queue-issue sidecar ${entry}: ${err instanceof Error ? err.message : String(err)}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async function clearStrayTmpFiles(sessionsDir) {
|
|
306
|
+
let entries;
|
|
307
|
+
try {
|
|
308
|
+
entries = await fs.readdir(sessionsDir);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
for (const entry of entries) {
|
|
314
|
+
if (!entry.endsWith('.tmp'))
|
|
315
|
+
continue;
|
|
316
|
+
try {
|
|
317
|
+
await fs.unlink(path.join(sessionsDir, entry));
|
|
318
|
+
console.log(`[WorkflowRunner] Cleared stray temp file: ${entry}`);
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { SessionState } from './session-state.js';
|
|
2
|
+
export { createSessionState, advanceStep, recordCompletion, updateToken, setSessionId, recordToolCall, } from './session-state.js';
|
|
3
|
+
export type { TerminalSignal } from './terminal-signal.js';
|
|
4
|
+
export { setTerminalSignal } from './terminal-signal.js';
|
|
5
|
+
export type { StuckConfig, StuckSignal } from './stuck-detection.js';
|
|
6
|
+
export { evaluateStuckSignals } from './stuck-detection.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateStuckSignals = exports.setTerminalSignal = exports.recordToolCall = exports.setSessionId = exports.updateToken = exports.recordCompletion = exports.advanceStep = exports.createSessionState = void 0;
|
|
4
|
+
var session_state_js_1 = require("./session-state.js");
|
|
5
|
+
Object.defineProperty(exports, "createSessionState", { enumerable: true, get: function () { return session_state_js_1.createSessionState; } });
|
|
6
|
+
Object.defineProperty(exports, "advanceStep", { enumerable: true, get: function () { return session_state_js_1.advanceStep; } });
|
|
7
|
+
Object.defineProperty(exports, "recordCompletion", { enumerable: true, get: function () { return session_state_js_1.recordCompletion; } });
|
|
8
|
+
Object.defineProperty(exports, "updateToken", { enumerable: true, get: function () { return session_state_js_1.updateToken; } });
|
|
9
|
+
Object.defineProperty(exports, "setSessionId", { enumerable: true, get: function () { return session_state_js_1.setSessionId; } });
|
|
10
|
+
Object.defineProperty(exports, "recordToolCall", { enumerable: true, get: function () { return session_state_js_1.recordToolCall; } });
|
|
11
|
+
var terminal_signal_js_1 = require("./terminal-signal.js");
|
|
12
|
+
Object.defineProperty(exports, "setTerminalSignal", { enumerable: true, get: function () { return terminal_signal_js_1.setTerminalSignal; } });
|
|
13
|
+
var stuck_detection_js_1 = require("./stuck-detection.js");
|
|
14
|
+
Object.defineProperty(exports, "evaluateStuckSignals", { enumerable: true, get: function () { return stuck_detection_js_1.evaluateStuckSignals; } });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TerminalSignal } from './terminal-signal.js';
|
|
2
|
+
export interface SessionState {
|
|
3
|
+
isComplete: boolean;
|
|
4
|
+
lastStepNotes: string | undefined;
|
|
5
|
+
lastStepArtifacts: readonly unknown[] | undefined;
|
|
6
|
+
currentContinueToken: string;
|
|
7
|
+
workrailSessionId: string | null;
|
|
8
|
+
stepAdvanceCount: number;
|
|
9
|
+
lastNToolCalls: Array<{
|
|
10
|
+
toolName: string;
|
|
11
|
+
argsSummary: string;
|
|
12
|
+
}>;
|
|
13
|
+
issueSummaries: string[];
|
|
14
|
+
pendingSteerParts: string[];
|
|
15
|
+
terminalSignal: TerminalSignal | null;
|
|
16
|
+
turnCount: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function createSessionState(initialToken: string): SessionState;
|
|
19
|
+
export declare function advanceStep(state: SessionState, stepText: string, continueToken: string): void;
|
|
20
|
+
export declare function recordCompletion(state: SessionState, notes: string | undefined, artifacts?: readonly unknown[]): void;
|
|
21
|
+
export declare function updateToken(state: SessionState, token: string): void;
|
|
22
|
+
export declare function setSessionId(state: SessionState, id: string): void;
|
|
23
|
+
export declare function recordToolCall(state: SessionState, toolName: string, argsSummary: string, threshold: number): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSessionState = createSessionState;
|
|
4
|
+
exports.advanceStep = advanceStep;
|
|
5
|
+
exports.recordCompletion = recordCompletion;
|
|
6
|
+
exports.updateToken = updateToken;
|
|
7
|
+
exports.setSessionId = setSessionId;
|
|
8
|
+
exports.recordToolCall = recordToolCall;
|
|
9
|
+
function createSessionState(initialToken) {
|
|
10
|
+
return {
|
|
11
|
+
isComplete: false,
|
|
12
|
+
lastStepNotes: undefined,
|
|
13
|
+
lastStepArtifacts: undefined,
|
|
14
|
+
currentContinueToken: initialToken,
|
|
15
|
+
workrailSessionId: null,
|
|
16
|
+
stepAdvanceCount: 0,
|
|
17
|
+
lastNToolCalls: [],
|
|
18
|
+
issueSummaries: [],
|
|
19
|
+
pendingSteerParts: [],
|
|
20
|
+
terminalSignal: null,
|
|
21
|
+
turnCount: 0,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function advanceStep(state, stepText, continueToken) {
|
|
25
|
+
state.pendingSteerParts.push(stepText);
|
|
26
|
+
state.stepAdvanceCount++;
|
|
27
|
+
state.currentContinueToken = continueToken;
|
|
28
|
+
}
|
|
29
|
+
function recordCompletion(state, notes, artifacts) {
|
|
30
|
+
state.isComplete = true;
|
|
31
|
+
state.lastStepNotes = notes;
|
|
32
|
+
state.lastStepArtifacts = artifacts;
|
|
33
|
+
}
|
|
34
|
+
function updateToken(state, token) {
|
|
35
|
+
state.currentContinueToken = token;
|
|
36
|
+
}
|
|
37
|
+
function setSessionId(state, id) {
|
|
38
|
+
state.workrailSessionId = id;
|
|
39
|
+
}
|
|
40
|
+
function recordToolCall(state, toolName, argsSummary, threshold) {
|
|
41
|
+
state.lastNToolCalls.push({ toolName, argsSummary });
|
|
42
|
+
if (state.lastNToolCalls.length > threshold)
|
|
43
|
+
state.lastNToolCalls.shift();
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SessionState } from './session-state.js';
|
|
2
|
+
export interface StuckConfig {
|
|
3
|
+
maxTurns: number;
|
|
4
|
+
stuckAbortPolicy: 'abort' | 'notify_only';
|
|
5
|
+
noProgressAbortEnabled: boolean;
|
|
6
|
+
stuckRepeatThreshold: number;
|
|
7
|
+
}
|
|
8
|
+
export type StuckSignal = {
|
|
9
|
+
kind: 'repeated_tool_call';
|
|
10
|
+
toolName: string;
|
|
11
|
+
argsSummary: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'no_progress';
|
|
14
|
+
turnCount: number;
|
|
15
|
+
maxTurns: number;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'max_turns_exceeded';
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'timeout_imminent';
|
|
20
|
+
timeoutReason: 'wall_clock' | 'max_turns';
|
|
21
|
+
};
|
|
22
|
+
export declare function evaluateStuckSignals(state: Readonly<SessionState>, config: StuckConfig): StuckSignal | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateStuckSignals = evaluateStuckSignals;
|
|
4
|
+
function evaluateStuckSignals(state, config) {
|
|
5
|
+
if (config.maxTurns > 0 && state.turnCount >= config.maxTurns && state.terminalSignal === null) {
|
|
6
|
+
return { kind: 'max_turns_exceeded' };
|
|
7
|
+
}
|
|
8
|
+
if (state.lastNToolCalls.length === config.stuckRepeatThreshold &&
|
|
9
|
+
state.lastNToolCalls.every((c) => c.toolName === state.lastNToolCalls[0]?.toolName && c.argsSummary === state.lastNToolCalls[0]?.argsSummary)) {
|
|
10
|
+
return {
|
|
11
|
+
kind: 'repeated_tool_call',
|
|
12
|
+
toolName: state.lastNToolCalls[0]?.toolName ?? 'unknown',
|
|
13
|
+
argsSummary: state.lastNToolCalls[0]?.argsSummary ?? '',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (config.maxTurns > 0 &&
|
|
17
|
+
state.turnCount >= Math.floor(config.maxTurns * 0.8) &&
|
|
18
|
+
state.stepAdvanceCount === 0) {
|
|
19
|
+
return { kind: 'no_progress', turnCount: state.turnCount, maxTurns: config.maxTurns };
|
|
20
|
+
}
|
|
21
|
+
if (state.terminalSignal?.kind === 'timeout') {
|
|
22
|
+
return { kind: 'timeout_imminent', timeoutReason: state.terminalSignal.reason };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SessionState } from './session-state.js';
|
|
2
|
+
export type TerminalSignal = {
|
|
3
|
+
readonly kind: 'stuck';
|
|
4
|
+
readonly reason: 'repeated_tool_call' | 'no_progress' | 'stall';
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: 'timeout';
|
|
7
|
+
readonly reason: 'wall_clock' | 'max_turns';
|
|
8
|
+
};
|
|
9
|
+
export declare function setTerminalSignal(state: SessionState, signal: TerminalSignal): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setTerminalSignal = setTerminalSignal;
|
|
4
|
+
function setTerminalSignal(state, signal) {
|
|
5
|
+
if (state.terminalSignal === null) {
|
|
6
|
+
state.terminalSignal = signal;
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentTool } from '../agent-loop.js';
|
|
2
2
|
import type { DaemonEventEmitter } from '../daemon-events.js';
|
|
3
|
-
import type { ReadFileState } from '../
|
|
3
|
+
import type { ReadFileState } from '../types.js';
|
|
4
4
|
export declare function makeReadTool(workspacePath: string, readFileState: Map<string, ReadFileState>, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
|
|
5
5
|
export declare function makeWriteTool(workspacePath: string, readFileState: Map<string, ReadFileState>, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
|
|
6
6
|
export declare function makeEditTool(workspacePath: string, readFileState: Map<string, ReadFileState>, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { StuckSignal, StuckConfig } from '../
|
|
2
|
-
export { evaluateStuckSignals as detectStuck } from '../
|
|
1
|
+
export type { StuckSignal, StuckConfig } from '../state/stuck-detection.js';
|
|
2
|
+
export { evaluateStuckSignals as detectStuck } from '../state/stuck-detection.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.detectStuck = void 0;
|
|
4
|
-
var
|
|
5
|
-
Object.defineProperty(exports, "detectStuck", { enumerable: true, get: function () { return
|
|
4
|
+
var stuck_detection_js_1 = require("../state/stuck-detection.js");
|
|
5
|
+
Object.defineProperty(exports, "detectStuck", { enumerable: true, get: function () { return stuck_detection_js_1.evaluateStuckSignals; } });
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export type ReadFileState = {
|
|
2
|
+
content: string;
|
|
3
|
+
timestamp: number;
|
|
4
|
+
isPartialView: boolean;
|
|
5
|
+
};
|
|
6
|
+
export interface WorkflowTrigger {
|
|
7
|
+
readonly workflowId: string;
|
|
8
|
+
readonly goal: string;
|
|
9
|
+
readonly workspacePath: string;
|
|
10
|
+
readonly context?: Readonly<Record<string, unknown>>;
|
|
11
|
+
readonly referenceUrls?: readonly string[];
|
|
12
|
+
readonly agentConfig?: {
|
|
13
|
+
readonly model?: string;
|
|
14
|
+
readonly maxSessionMinutes?: number;
|
|
15
|
+
readonly maxTurns?: number;
|
|
16
|
+
readonly maxOutputTokens?: number;
|
|
17
|
+
readonly maxSubagentDepth?: number;
|
|
18
|
+
readonly stuckAbortPolicy?: 'abort' | 'notify_only';
|
|
19
|
+
readonly noProgressAbortEnabled?: boolean;
|
|
20
|
+
readonly stallTimeoutSeconds?: number;
|
|
21
|
+
};
|
|
22
|
+
readonly parentSessionId?: string;
|
|
23
|
+
readonly spawnDepth?: number;
|
|
24
|
+
readonly soulFile?: string;
|
|
25
|
+
readonly botIdentity?: {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly email: string;
|
|
28
|
+
};
|
|
29
|
+
readonly branchStrategy?: 'worktree' | 'none';
|
|
30
|
+
readonly baseBranch?: string;
|
|
31
|
+
readonly branchPrefix?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface AllocatedSession {
|
|
34
|
+
readonly continueToken: string;
|
|
35
|
+
readonly checkpointToken?: string | null;
|
|
36
|
+
readonly firstStepPrompt: string;
|
|
37
|
+
readonly isComplete: boolean;
|
|
38
|
+
readonly triggerSource: 'daemon' | 'mcp';
|
|
39
|
+
readonly sessionWorkspacePath?: string;
|
|
40
|
+
}
|
|
41
|
+
export type SessionSource = {
|
|
42
|
+
readonly kind: 'allocate';
|
|
43
|
+
readonly trigger: WorkflowTrigger;
|
|
44
|
+
} | {
|
|
45
|
+
readonly kind: 'pre_allocated';
|
|
46
|
+
readonly trigger: WorkflowTrigger;
|
|
47
|
+
readonly session: AllocatedSession;
|
|
48
|
+
};
|
|
49
|
+
export interface WorkflowRunSuccess {
|
|
50
|
+
readonly _tag: 'success';
|
|
51
|
+
readonly workflowId: string;
|
|
52
|
+
readonly stopReason: string;
|
|
53
|
+
readonly lastStepNotes?: string;
|
|
54
|
+
readonly lastStepArtifacts?: readonly unknown[];
|
|
55
|
+
readonly sessionWorkspacePath?: string;
|
|
56
|
+
readonly sessionId?: string;
|
|
57
|
+
readonly botIdentity?: {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly email: string;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export interface WorkflowRunError {
|
|
63
|
+
readonly _tag: 'error';
|
|
64
|
+
readonly workflowId: string;
|
|
65
|
+
readonly message: string;
|
|
66
|
+
readonly stopReason: string;
|
|
67
|
+
readonly lastStepNotes?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface WorkflowRunTimeout {
|
|
70
|
+
readonly _tag: 'timeout';
|
|
71
|
+
readonly workflowId: string;
|
|
72
|
+
readonly reason: 'wall_clock' | 'max_turns';
|
|
73
|
+
readonly message: string;
|
|
74
|
+
readonly stopReason: string;
|
|
75
|
+
}
|
|
76
|
+
export interface WorkflowRunStuck {
|
|
77
|
+
readonly _tag: 'stuck';
|
|
78
|
+
readonly workflowId: string;
|
|
79
|
+
readonly reason: 'repeated_tool_call' | 'no_progress' | 'stall';
|
|
80
|
+
readonly message: string;
|
|
81
|
+
readonly stopReason: string;
|
|
82
|
+
readonly issueSummaries?: readonly string[];
|
|
83
|
+
}
|
|
84
|
+
export interface WorkflowDeliveryFailed {
|
|
85
|
+
readonly _tag: 'delivery_failed';
|
|
86
|
+
readonly workflowId: string;
|
|
87
|
+
readonly stopReason: string;
|
|
88
|
+
readonly deliveryError: string;
|
|
89
|
+
}
|
|
90
|
+
export type WorkflowRunResult = WorkflowRunSuccess | WorkflowRunError | WorkflowRunTimeout | WorkflowRunStuck | WorkflowDeliveryFailed;
|
|
91
|
+
export type ChildWorkflowRunResult = WorkflowRunSuccess | WorkflowRunError | WorkflowRunTimeout | WorkflowRunStuck;
|
|
92
|
+
export interface OrphanedSession {
|
|
93
|
+
readonly sessionId: string;
|
|
94
|
+
readonly continueToken: string;
|
|
95
|
+
readonly checkpointToken: string | null;
|
|
96
|
+
readonly ts: number;
|
|
97
|
+
readonly worktreePath?: string;
|
|
98
|
+
readonly workflowId?: string;
|
|
99
|
+
readonly goal?: string;
|
|
100
|
+
readonly workspacePath?: string;
|
|
101
|
+
}
|
|
102
|
+
export interface WorkflowContextSlots {
|
|
103
|
+
readonly assembledContextSummary?: string;
|
|
104
|
+
}
|
|
105
|
+
export declare function extractContextSlots(context: Readonly<Record<string, unknown>> | undefined): WorkflowContextSlots;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractContextSlots = extractContextSlots;
|
|
4
|
+
function extractContextSlots(context) {
|
|
5
|
+
if (!context)
|
|
6
|
+
return {};
|
|
7
|
+
const assembledContextSummary = typeof context['assembledContextSummary'] === 'string'
|
|
8
|
+
? context['assembledContextSummary']
|
|
9
|
+
: undefined;
|
|
10
|
+
return { assembledContextSummary };
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Result } from '../runtime/result.js';
|
|
2
|
+
import type { SessionNote } from '../context-assembly/types.js';
|
|
3
|
+
import type { WorkflowTrigger } from './types.js';
|
|
4
|
+
export interface EnricherResult {
|
|
5
|
+
readonly priorSessionNotes: readonly SessionNote[];
|
|
6
|
+
readonly gitDiffStat: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare const EMPTY_RESULT: EnricherResult;
|
|
9
|
+
export type PriorNotesPolicy = 'inject' | 'skip_coordinator_provided';
|
|
10
|
+
export interface WorkflowEnricherDeps {
|
|
11
|
+
readonly execGit: (args: readonly string[], cwd: string) => Promise<Result<string, string>>;
|
|
12
|
+
readonly listRecentSessions: (workspacePath: string, limit: number) => Promise<Result<readonly SessionNote[], string>>;
|
|
13
|
+
}
|
|
14
|
+
export declare function createWorkflowEnricherDeps(): WorkflowEnricherDeps;
|
|
15
|
+
export declare function enrichTriggerContext(trigger: WorkflowTrigger, deps: WorkflowEnricherDeps, policy: PriorNotesPolicy): Promise<EnricherResult>;
|
|
16
|
+
export declare function shouldEnrich(trigger: WorkflowTrigger): boolean;
|