@exaudeus/workrail 3.70.5 → 3.70.7
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/cli/commands/worktrain-daemon.d.ts +3 -0
- package/dist/cli/commands/worktrain-daemon.js +122 -47
- package/dist/cli-worktrain.js +6 -2
- package/dist/console-ui/assets/index-iqWCy6dR.js +28 -0
- package/dist/console-ui/index.html +1 -1
- package/dist/daemon/workflow-runner.d.ts +76 -1
- package/dist/daemon/workflow-runner.js +328 -229
- package/dist/manifest.json +14 -14
- package/docs/ideas/backlog.md +604 -7512
- package/docs/reference/worktrain-daemon-invariants.md +225 -0
- package/package.json +1 -1
- package/dist/console-ui/assets/index-smIzJo9R.js +0 -28
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.DAEMON_SIGNALS_DIR = exports.DAEMON_SOUL_TEMPLATE = exports.DAEMON_SOUL_DEFAULT = exports.WORKTREES_DIR = exports.DAEMON_SESSIONS_DIR = void 0;
|
|
39
|
+
exports.DAEMON_SIGNALS_DIR = exports.DAEMON_SOUL_TEMPLATE = exports.DAEMON_SOUL_DEFAULT = exports.WORKTREES_DIR = exports.DAEMON_SESSIONS_DIR = exports.DEFAULT_MAX_TURNS = exports.DEFAULT_SESSION_TIMEOUT_MINUTES = void 0;
|
|
40
40
|
exports.readDaemonSessionState = readDaemonSessionState;
|
|
41
41
|
exports.readAllDaemonSessions = readAllDaemonSessions;
|
|
42
42
|
exports.runStartupRecovery = runStartupRecovery;
|
|
@@ -58,6 +58,12 @@ exports.makeReportIssueTool = makeReportIssueTool;
|
|
|
58
58
|
exports.makeSignalCoordinatorTool = makeSignalCoordinatorTool;
|
|
59
59
|
exports.buildSessionRecap = buildSessionRecap;
|
|
60
60
|
exports.buildSystemPrompt = buildSystemPrompt;
|
|
61
|
+
exports.tagToStatsOutcome = tagToStatsOutcome;
|
|
62
|
+
exports.buildAgentClient = buildAgentClient;
|
|
63
|
+
exports.createSessionState = createSessionState;
|
|
64
|
+
exports.evaluateStuckSignals = evaluateStuckSignals;
|
|
65
|
+
exports.finalizeSession = finalizeSession;
|
|
66
|
+
exports.buildSessionContext = buildSessionContext;
|
|
61
67
|
exports.runWorkflow = runWorkflow;
|
|
62
68
|
require("reflect-metadata");
|
|
63
69
|
const fs = __importStar(require("node:fs/promises"));
|
|
@@ -83,8 +89,8 @@ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
|
83
89
|
const BASH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
84
90
|
const MAX_SESSION_RECAP_NOTES = 3;
|
|
85
91
|
const MAX_SESSION_NOTE_CHARS = 800;
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
exports.DEFAULT_SESSION_TIMEOUT_MINUTES = 30;
|
|
93
|
+
exports.DEFAULT_MAX_TURNS = 200;
|
|
88
94
|
function withWorkrailSession(sid) {
|
|
89
95
|
return sid != null ? { workrailSessionId: sid } : {};
|
|
90
96
|
}
|
|
@@ -1613,6 +1619,77 @@ function buildUserMessage(text) {
|
|
|
1613
1619
|
timestamp: Date.now(),
|
|
1614
1620
|
};
|
|
1615
1621
|
}
|
|
1622
|
+
function tagToStatsOutcome(tag) {
|
|
1623
|
+
switch (tag) {
|
|
1624
|
+
case 'success': return 'success';
|
|
1625
|
+
case 'error': return 'error';
|
|
1626
|
+
case 'timeout': return 'timeout';
|
|
1627
|
+
case 'stuck': return 'stuck';
|
|
1628
|
+
case 'delivery_failed': return 'success';
|
|
1629
|
+
default: return (0, assert_never_js_1.assertNever)(tag);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
function buildAgentClient(trigger, apiKey, env) {
|
|
1633
|
+
if (trigger.agentConfig?.model) {
|
|
1634
|
+
const slashIdx = trigger.agentConfig.model.indexOf('/');
|
|
1635
|
+
if (slashIdx === -1) {
|
|
1636
|
+
throw new Error(`agentConfig.model must be in "provider/model-id" format, got: "${trigger.agentConfig.model}"`);
|
|
1637
|
+
}
|
|
1638
|
+
const provider = trigger.agentConfig.model.slice(0, slashIdx);
|
|
1639
|
+
const modelId = trigger.agentConfig.model.slice(slashIdx + 1);
|
|
1640
|
+
const agentClient = provider === 'amazon-bedrock' ? new bedrock_sdk_1.AnthropicBedrock() : new sdk_1.default({ apiKey });
|
|
1641
|
+
return { agentClient, modelId };
|
|
1642
|
+
}
|
|
1643
|
+
const usesBedrock = !!env['AWS_PROFILE'] || !!env['AWS_ACCESS_KEY_ID'];
|
|
1644
|
+
if (usesBedrock) {
|
|
1645
|
+
return {
|
|
1646
|
+
agentClient: new bedrock_sdk_1.AnthropicBedrock(),
|
|
1647
|
+
modelId: 'us.anthropic.claude-sonnet-4-6',
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
return {
|
|
1651
|
+
agentClient: new sdk_1.default({ apiKey }),
|
|
1652
|
+
modelId: 'claude-sonnet-4-6',
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
function createSessionState(initialToken) {
|
|
1656
|
+
return {
|
|
1657
|
+
isComplete: false,
|
|
1658
|
+
lastStepNotes: undefined,
|
|
1659
|
+
lastStepArtifacts: undefined,
|
|
1660
|
+
currentContinueToken: initialToken,
|
|
1661
|
+
workrailSessionId: null,
|
|
1662
|
+
stepAdvanceCount: 0,
|
|
1663
|
+
lastNToolCalls: [],
|
|
1664
|
+
issueSummaries: [],
|
|
1665
|
+
pendingSteerParts: [],
|
|
1666
|
+
stuckReason: null,
|
|
1667
|
+
timeoutReason: null,
|
|
1668
|
+
turnCount: 0,
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
function evaluateStuckSignals(state, config) {
|
|
1672
|
+
if (config.maxTurns > 0 && state.turnCount >= config.maxTurns && state.timeoutReason === null) {
|
|
1673
|
+
return { kind: 'max_turns_exceeded' };
|
|
1674
|
+
}
|
|
1675
|
+
if (state.lastNToolCalls.length === config.stuckRepeatThreshold &&
|
|
1676
|
+
state.lastNToolCalls.every((c) => c.toolName === state.lastNToolCalls[0]?.toolName && c.argsSummary === state.lastNToolCalls[0]?.argsSummary)) {
|
|
1677
|
+
return {
|
|
1678
|
+
kind: 'repeated_tool_call',
|
|
1679
|
+
toolName: state.lastNToolCalls[0]?.toolName ?? 'unknown',
|
|
1680
|
+
argsSummary: state.lastNToolCalls[0]?.argsSummary ?? '',
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
if (config.maxTurns > 0 &&
|
|
1684
|
+
state.turnCount >= Math.floor(config.maxTurns * 0.8) &&
|
|
1685
|
+
state.stepAdvanceCount === 0) {
|
|
1686
|
+
return { kind: 'no_progress', turnCount: state.turnCount, maxTurns: config.maxTurns };
|
|
1687
|
+
}
|
|
1688
|
+
if (state.timeoutReason !== null) {
|
|
1689
|
+
return { kind: 'timeout_imminent', timeoutReason: state.timeoutReason };
|
|
1690
|
+
}
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1616
1693
|
function writeExecutionStats(statsDir, sessionId, workflowId, startMs, outcome, stepCount) {
|
|
1617
1694
|
const endMs = Date.now();
|
|
1618
1695
|
const statsPath = path.join(statsDir, 'execution-stats.jsonl');
|
|
@@ -1630,9 +1707,50 @@ function writeExecutionStats(statsDir, sessionId, workflowId, startMs, outcome,
|
|
|
1630
1707
|
.then(() => { (0, stats_summary_js_1.writeStatsSummary)(statsDir).catch(() => { }); })
|
|
1631
1708
|
.catch(() => { });
|
|
1632
1709
|
}
|
|
1633
|
-
async function
|
|
1710
|
+
async function finalizeSession(result, ctx) {
|
|
1711
|
+
const outcome = tagToStatsOutcome(result._tag);
|
|
1712
|
+
const detail = result._tag === 'stuck' ? result.reason
|
|
1713
|
+
: result._tag === 'timeout' ? result.reason
|
|
1714
|
+
: result._tag === 'error' ? result.message.slice(0, 200)
|
|
1715
|
+
: result._tag === 'delivery_failed' ? result.deliveryError.slice(0, 200)
|
|
1716
|
+
: result.stopReason;
|
|
1717
|
+
ctx.emitter?.emit({
|
|
1718
|
+
kind: 'session_completed',
|
|
1719
|
+
sessionId: ctx.sessionId,
|
|
1720
|
+
workflowId: ctx.workflowId,
|
|
1721
|
+
outcome,
|
|
1722
|
+
detail,
|
|
1723
|
+
...withWorkrailSession(ctx.workrailSessionId),
|
|
1724
|
+
});
|
|
1725
|
+
if (ctx.workrailSessionId !== null) {
|
|
1726
|
+
ctx.daemonRegistry?.unregister(ctx.workrailSessionId, result._tag === 'success' || result._tag === 'delivery_failed' ? 'completed' : 'failed');
|
|
1727
|
+
}
|
|
1728
|
+
writeExecutionStats(ctx.statsDir, ctx.sessionId, ctx.workflowId, ctx.startMs, outcome, ctx.stepAdvanceCount);
|
|
1729
|
+
const isWorktreeSuccess = result._tag === 'success' && ctx.branchStrategy === 'worktree';
|
|
1730
|
+
if (!isWorktreeSuccess) {
|
|
1731
|
+
await fs.unlink(path.join(ctx.sessionsDir, `${ctx.sessionId}.json`)).catch(() => { });
|
|
1732
|
+
}
|
|
1733
|
+
if (result._tag === 'success' && ctx.branchStrategy !== 'worktree') {
|
|
1734
|
+
await fs.unlink(ctx.conversationPath).catch(() => { });
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
function buildSessionContext(trigger, inputs) {
|
|
1738
|
+
const sessionState = buildSessionRecap(inputs.sessionNotes);
|
|
1739
|
+
const systemPrompt = buildSystemPrompt(trigger, sessionState, inputs.soulContent, inputs.workspaceContext);
|
|
1740
|
+
const contextJson = trigger.context
|
|
1741
|
+
? `\n\nTrigger context:\n\`\`\`json\n${JSON.stringify(trigger.context, null, 2)}\n\`\`\``
|
|
1742
|
+
: '';
|
|
1743
|
+
const initialPrompt = inputs.firstStepPrompt +
|
|
1744
|
+
contextJson +
|
|
1745
|
+
'\n\nComplete all step work, then call complete_step with your notes to advance.';
|
|
1746
|
+
const sessionTimeoutMs = (trigger.agentConfig?.maxSessionMinutes ?? exports.DEFAULT_SESSION_TIMEOUT_MINUTES) * 60 * 1000;
|
|
1747
|
+
const maxTurns = trigger.agentConfig?.maxTurns ?? exports.DEFAULT_MAX_TURNS;
|
|
1748
|
+
return { systemPrompt, initialPrompt, sessionTimeoutMs, maxTurns };
|
|
1749
|
+
}
|
|
1750
|
+
async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerRegistry, abortRegistry, _statsDir, _sessionsDir) {
|
|
1751
|
+
const statsDir = _statsDir ?? DAEMON_STATS_DIR;
|
|
1752
|
+
const sessionsDir = _sessionsDir ?? exports.DAEMON_SESSIONS_DIR;
|
|
1634
1753
|
const startMs = Date.now();
|
|
1635
|
-
let sessionOutcome = 'unknown';
|
|
1636
1754
|
const sessionId = (0, node_crypto_1.randomUUID)();
|
|
1637
1755
|
console.log(`[WorkflowRunner] Session started: sessionId=${sessionId} workflowId=${trigger.workflowId}`);
|
|
1638
1756
|
emitter?.emit({
|
|
@@ -1641,58 +1759,48 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1641
1759
|
workflowId: trigger.workflowId,
|
|
1642
1760
|
workspacePath: trigger.workspacePath,
|
|
1643
1761
|
});
|
|
1644
|
-
let workrailSessionId = null;
|
|
1645
1762
|
let agentClient;
|
|
1646
1763
|
let modelId;
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
if (
|
|
1650
|
-
|
|
1651
|
-
return {
|
|
1652
|
-
_tag: 'error',
|
|
1653
|
-
workflowId: trigger.workflowId,
|
|
1654
|
-
message: `agentConfig.model must be in "provider/model-id" format, got: "${trigger.agentConfig.model}"`,
|
|
1655
|
-
stopReason: 'error',
|
|
1656
|
-
};
|
|
1657
|
-
}
|
|
1658
|
-
const provider = trigger.agentConfig.model.slice(0, slashIdx);
|
|
1659
|
-
modelId = trigger.agentConfig.model.slice(slashIdx + 1);
|
|
1660
|
-
agentClient = provider === 'amazon-bedrock' ? new bedrock_sdk_1.AnthropicBedrock() : new sdk_1.default({ apiKey });
|
|
1661
|
-
}
|
|
1662
|
-
else {
|
|
1663
|
-
const usesBedrock = !!process.env['AWS_PROFILE'] || !!process.env['AWS_ACCESS_KEY_ID'];
|
|
1664
|
-
if (usesBedrock) {
|
|
1665
|
-
agentClient = new bedrock_sdk_1.AnthropicBedrock();
|
|
1666
|
-
modelId = 'us.anthropic.claude-sonnet-4-6';
|
|
1667
|
-
console.log(`[WorkflowRunner] Model: ${modelId} (amazon-bedrock, detected from AWS env)`);
|
|
1764
|
+
try {
|
|
1765
|
+
({ agentClient, modelId } = buildAgentClient(trigger, apiKey, process.env));
|
|
1766
|
+
if (trigger.agentConfig?.model) {
|
|
1767
|
+
console.log(`[WorkflowRunner] Model: ${modelId} (override from agentConfig.model)`);
|
|
1668
1768
|
}
|
|
1669
1769
|
else {
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1770
|
+
const usesBedrock = !!process.env['AWS_PROFILE'] || !!process.env['AWS_ACCESS_KEY_ID'];
|
|
1771
|
+
if (usesBedrock) {
|
|
1772
|
+
console.log(`[WorkflowRunner] Model: ${modelId} (amazon-bedrock, detected from AWS env)`);
|
|
1773
|
+
}
|
|
1774
|
+
else {
|
|
1775
|
+
console.log(`[WorkflowRunner] Model: ${modelId} (anthropic direct). Set agentConfig.model or AWS env vars to use Bedrock.`);
|
|
1776
|
+
}
|
|
1673
1777
|
}
|
|
1674
1778
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1779
|
+
catch (err) {
|
|
1780
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1781
|
+
writeExecutionStats(statsDir, sessionId, trigger.workflowId, startMs, 'error', 0);
|
|
1782
|
+
return {
|
|
1783
|
+
_tag: 'error',
|
|
1784
|
+
workflowId: trigger.workflowId,
|
|
1785
|
+
message,
|
|
1786
|
+
stopReason: 'error',
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
const state = createSessionState('');
|
|
1683
1790
|
const MAX_ISSUE_SUMMARIES = 10;
|
|
1791
|
+
const STUCK_REPEAT_THRESHOLD = 3;
|
|
1684
1792
|
const onAdvance = (stepText, continueToken) => {
|
|
1685
|
-
pendingSteerParts.push(stepText);
|
|
1686
|
-
stepAdvanceCount++;
|
|
1687
|
-
currentContinueToken = continueToken;
|
|
1688
|
-
if (workrailSessionId !== null)
|
|
1689
|
-
daemonRegistry?.heartbeat(workrailSessionId);
|
|
1690
|
-
emitter?.emit({ kind: 'step_advanced', sessionId, ...withWorkrailSession(workrailSessionId) });
|
|
1793
|
+
state.pendingSteerParts.push(stepText);
|
|
1794
|
+
state.stepAdvanceCount++;
|
|
1795
|
+
state.currentContinueToken = continueToken;
|
|
1796
|
+
if (state.workrailSessionId !== null)
|
|
1797
|
+
daemonRegistry?.heartbeat(state.workrailSessionId);
|
|
1798
|
+
emitter?.emit({ kind: 'step_advanced', sessionId, ...withWorkrailSession(state.workrailSessionId) });
|
|
1691
1799
|
};
|
|
1692
1800
|
const onComplete = (notes, artifacts) => {
|
|
1693
|
-
isComplete = true;
|
|
1694
|
-
lastStepNotes = notes;
|
|
1695
|
-
lastStepArtifacts = artifacts;
|
|
1801
|
+
state.isComplete = true;
|
|
1802
|
+
state.lastStepNotes = notes;
|
|
1803
|
+
state.lastStepArtifacts = artifacts;
|
|
1696
1804
|
};
|
|
1697
1805
|
let firstStep;
|
|
1698
1806
|
if (trigger._preAllocatedStartResponse !== undefined) {
|
|
@@ -1701,7 +1809,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1701
1809
|
else {
|
|
1702
1810
|
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId: trigger.workflowId, workspacePath: trigger.workspacePath, goal: trigger.goal }, ctx, { is_autonomous: 'true', workspacePath: trigger.workspacePath });
|
|
1703
1811
|
if (startResult.isErr()) {
|
|
1704
|
-
writeExecutionStats(
|
|
1812
|
+
writeExecutionStats(statsDir, sessionId, trigger.workflowId, startMs, 'error', 0);
|
|
1705
1813
|
return {
|
|
1706
1814
|
_tag: 'error',
|
|
1707
1815
|
workflowId: trigger.workflowId,
|
|
@@ -1713,24 +1821,24 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1713
1821
|
}
|
|
1714
1822
|
const startContinueToken = firstStep.continueToken ?? '';
|
|
1715
1823
|
const startCheckpointToken = firstStep.checkpointToken ?? null;
|
|
1716
|
-
|
|
1824
|
+
state.currentContinueToken = startContinueToken;
|
|
1717
1825
|
if (startContinueToken) {
|
|
1718
1826
|
const decoded = await (0, v2_token_ops_js_1.parseContinueTokenOrFail)(startContinueToken, ctx.v2.tokenCodecPorts, ctx.v2.tokenAliasStore);
|
|
1719
1827
|
if (decoded.isOk()) {
|
|
1720
|
-
workrailSessionId = decoded.value.sessionId;
|
|
1828
|
+
state.workrailSessionId = decoded.value.sessionId;
|
|
1721
1829
|
}
|
|
1722
1830
|
else {
|
|
1723
1831
|
console.error(`[WorkflowRunner] Error: could not decode WorkRail session ID from continueToken -- isLive and liveActivity will not work for this session. Reason: ${decoded.error.message}`);
|
|
1724
1832
|
}
|
|
1725
1833
|
}
|
|
1726
|
-
if (workrailSessionId !== null) {
|
|
1727
|
-
daemonRegistry?.register(workrailSessionId, trigger.workflowId);
|
|
1834
|
+
if (state.workrailSessionId !== null) {
|
|
1835
|
+
daemonRegistry?.register(state.workrailSessionId, trigger.workflowId);
|
|
1728
1836
|
}
|
|
1729
|
-
if (workrailSessionId !== null) {
|
|
1730
|
-
steerRegistry?.set(workrailSessionId, (text) => { pendingSteerParts.push(text); });
|
|
1837
|
+
if (state.workrailSessionId !== null) {
|
|
1838
|
+
steerRegistry?.set(state.workrailSessionId, (text) => { state.pendingSteerParts.push(text); });
|
|
1731
1839
|
}
|
|
1732
|
-
if (workrailSessionId !== null) {
|
|
1733
|
-
abortRegistry?.set(workrailSessionId, () => { agent.abort(); });
|
|
1840
|
+
if (state.workrailSessionId !== null) {
|
|
1841
|
+
abortRegistry?.set(state.workrailSessionId, () => { agent.abort(); });
|
|
1734
1842
|
}
|
|
1735
1843
|
if (startContinueToken) {
|
|
1736
1844
|
await persistTokens(sessionId, startContinueToken, startCheckpointToken, undefined, {
|
|
@@ -1756,7 +1864,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1756
1864
|
'-b', `${branchPrefix}${sessionId}`,
|
|
1757
1865
|
`origin/${baseBranch}`,
|
|
1758
1866
|
]);
|
|
1759
|
-
await persistTokens(sessionId, startContinueToken ?? currentContinueToken, startCheckpointToken, sessionWorktreePath, {
|
|
1867
|
+
await persistTokens(sessionId, startContinueToken ?? state.currentContinueToken, startCheckpointToken, sessionWorktreePath, {
|
|
1760
1868
|
workflowId: trigger.workflowId,
|
|
1761
1869
|
goal: trigger.goal,
|
|
1762
1870
|
workspacePath: trigger.workspacePath,
|
|
@@ -1767,14 +1875,14 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1767
1875
|
catch (err) {
|
|
1768
1876
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1769
1877
|
console.error(`[WorkflowRunner] Worktree creation failed: sessionId=${sessionId} error=${errMsg}`);
|
|
1770
|
-
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'error', detail: errMsg.slice(0, 200), ...withWorkrailSession(workrailSessionId) });
|
|
1771
|
-
if (workrailSessionId !== null)
|
|
1772
|
-
daemonRegistry?.unregister(workrailSessionId, 'failed');
|
|
1773
|
-
if (workrailSessionId !== null) {
|
|
1774
|
-
steerRegistry?.delete(workrailSessionId);
|
|
1775
|
-
abortRegistry?.delete(workrailSessionId);
|
|
1776
|
-
}
|
|
1777
|
-
writeExecutionStats(
|
|
1878
|
+
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'error', detail: errMsg.slice(0, 200), ...withWorkrailSession(state.workrailSessionId) });
|
|
1879
|
+
if (state.workrailSessionId !== null)
|
|
1880
|
+
daemonRegistry?.unregister(state.workrailSessionId, 'failed');
|
|
1881
|
+
if (state.workrailSessionId !== null) {
|
|
1882
|
+
steerRegistry?.delete(state.workrailSessionId);
|
|
1883
|
+
abortRegistry?.delete(state.workrailSessionId);
|
|
1884
|
+
}
|
|
1885
|
+
writeExecutionStats(statsDir, sessionId, trigger.workflowId, startMs, 'error', 0);
|
|
1778
1886
|
return {
|
|
1779
1887
|
_tag: 'error',
|
|
1780
1888
|
workflowId: trigger.workflowId,
|
|
@@ -1785,16 +1893,16 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1785
1893
|
}
|
|
1786
1894
|
if (firstStep.isComplete) {
|
|
1787
1895
|
if (trigger.branchStrategy !== 'worktree') {
|
|
1788
|
-
await fs.unlink(path.join(
|
|
1896
|
+
await fs.unlink(path.join(sessionsDir, `${sessionId}.json`)).catch(() => { });
|
|
1789
1897
|
}
|
|
1790
|
-
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: 'stop', ...withWorkrailSession(workrailSessionId) });
|
|
1791
|
-
if (workrailSessionId !== null)
|
|
1792
|
-
daemonRegistry?.unregister(workrailSessionId, 'completed');
|
|
1793
|
-
if (workrailSessionId !== null) {
|
|
1794
|
-
steerRegistry?.delete(workrailSessionId);
|
|
1795
|
-
abortRegistry?.delete(workrailSessionId);
|
|
1898
|
+
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: 'stop', ...withWorkrailSession(state.workrailSessionId) });
|
|
1899
|
+
if (state.workrailSessionId !== null)
|
|
1900
|
+
daemonRegistry?.unregister(state.workrailSessionId, 'completed');
|
|
1901
|
+
if (state.workrailSessionId !== null) {
|
|
1902
|
+
steerRegistry?.delete(state.workrailSessionId);
|
|
1903
|
+
abortRegistry?.delete(state.workrailSessionId);
|
|
1796
1904
|
}
|
|
1797
|
-
writeExecutionStats(
|
|
1905
|
+
writeExecutionStats(statsDir, sessionId, trigger.workflowId, startMs, 'success', 0);
|
|
1798
1906
|
return {
|
|
1799
1907
|
_tag: 'success',
|
|
1800
1908
|
workflowId: trigger.workflowId,
|
|
@@ -1809,34 +1917,33 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1809
1917
|
const spawnMaxDepth = trigger.agentConfig?.maxSubagentDepth ?? 3;
|
|
1810
1918
|
const readFileState = new Map();
|
|
1811
1919
|
const tools = [
|
|
1812
|
-
makeCompleteStepTool(sessionId, ctx, () => currentContinueToken, onAdvance, onComplete, (t) => { currentContinueToken = t; }, schemas, index_js_1.executeContinueWorkflow, emitter, workrailSessionId),
|
|
1813
|
-
makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, index_js_1.executeContinueWorkflow, emitter, workrailSessionId),
|
|
1814
|
-
makeBashTool(sessionWorkspacePath, schemas, sessionId, emitter, workrailSessionId),
|
|
1815
|
-
makeReadTool(readFileState, schemas, sessionId, emitter, workrailSessionId),
|
|
1816
|
-
makeWriteTool(readFileState, schemas, sessionId, emitter, workrailSessionId),
|
|
1817
|
-
makeGlobTool(sessionWorkspacePath, schemas, sessionId, emitter, workrailSessionId),
|
|
1818
|
-
makeGrepTool(sessionWorkspacePath, schemas, sessionId, emitter, workrailSessionId),
|
|
1819
|
-
makeEditTool(sessionWorkspacePath, readFileState, schemas, sessionId, emitter, workrailSessionId),
|
|
1820
|
-
makeReportIssueTool(sessionId, emitter, workrailSessionId, undefined, (summary) => {
|
|
1821
|
-
if (issueSummaries.length < MAX_ISSUE_SUMMARIES) {
|
|
1822
|
-
issueSummaries.push(summary);
|
|
1920
|
+
makeCompleteStepTool(sessionId, ctx, () => state.currentContinueToken, onAdvance, onComplete, (t) => { state.currentContinueToken = t; }, schemas, index_js_1.executeContinueWorkflow, emitter, state.workrailSessionId),
|
|
1921
|
+
makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, index_js_1.executeContinueWorkflow, emitter, state.workrailSessionId),
|
|
1922
|
+
makeBashTool(sessionWorkspacePath, schemas, sessionId, emitter, state.workrailSessionId),
|
|
1923
|
+
makeReadTool(readFileState, schemas, sessionId, emitter, state.workrailSessionId),
|
|
1924
|
+
makeWriteTool(readFileState, schemas, sessionId, emitter, state.workrailSessionId),
|
|
1925
|
+
makeGlobTool(sessionWorkspacePath, schemas, sessionId, emitter, state.workrailSessionId),
|
|
1926
|
+
makeGrepTool(sessionWorkspacePath, schemas, sessionId, emitter, state.workrailSessionId),
|
|
1927
|
+
makeEditTool(sessionWorkspacePath, readFileState, schemas, sessionId, emitter, state.workrailSessionId),
|
|
1928
|
+
makeReportIssueTool(sessionId, emitter, state.workrailSessionId, undefined, (summary) => {
|
|
1929
|
+
if (state.issueSummaries.length < MAX_ISSUE_SUMMARIES) {
|
|
1930
|
+
state.issueSummaries.push(summary);
|
|
1823
1931
|
}
|
|
1824
1932
|
}),
|
|
1825
|
-
makeSpawnAgentTool(sessionId, ctx, apiKey, workrailSessionId ?? '', spawnCurrentDepth, spawnMaxDepth, runWorkflow, schemas, emitter, abortRegistry),
|
|
1826
|
-
makeSignalCoordinatorTool(sessionId, emitter, workrailSessionId),
|
|
1933
|
+
makeSpawnAgentTool(sessionId, ctx, apiKey, state.workrailSessionId ?? '', spawnCurrentDepth, spawnMaxDepth, runWorkflow, schemas, emitter, abortRegistry),
|
|
1934
|
+
makeSignalCoordinatorTool(sessionId, emitter, state.workrailSessionId),
|
|
1827
1935
|
];
|
|
1828
1936
|
const [soulContent, workspaceContext, sessionNotes] = await Promise.all([
|
|
1829
1937
|
loadDaemonSoul(trigger.soulFile),
|
|
1830
1938
|
loadWorkspaceContext(trigger.workspacePath),
|
|
1831
1939
|
startContinueToken ? loadSessionNotes(startContinueToken, ctx) : Promise.resolve([]),
|
|
1832
1940
|
]);
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
'\n\nComplete all step work, then call complete_step with your notes to advance.';
|
|
1941
|
+
const sessionCtx = buildSessionContext(trigger, {
|
|
1942
|
+
soulContent,
|
|
1943
|
+
workspaceContext,
|
|
1944
|
+
sessionNotes,
|
|
1945
|
+
firstStepPrompt: firstStep.pending?.prompt ?? 'No step content available',
|
|
1946
|
+
});
|
|
1840
1947
|
const agentCallbacks = {
|
|
1841
1948
|
onLlmTurnStarted: ({ messageCount }) => {
|
|
1842
1949
|
emitter?.emit({
|
|
@@ -1844,7 +1951,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1844
1951
|
sessionId,
|
|
1845
1952
|
messageCount,
|
|
1846
1953
|
modelId,
|
|
1847
|
-
...withWorkrailSession(workrailSessionId),
|
|
1954
|
+
...withWorkrailSession(state.workrailSessionId),
|
|
1848
1955
|
});
|
|
1849
1956
|
},
|
|
1850
1957
|
onLlmTurnCompleted: ({ stopReason, outputTokens, inputTokens, toolNamesRequested }) => {
|
|
@@ -1855,25 +1962,25 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1855
1962
|
outputTokens,
|
|
1856
1963
|
inputTokens,
|
|
1857
1964
|
toolNamesRequested,
|
|
1858
|
-
...withWorkrailSession(workrailSessionId),
|
|
1965
|
+
...withWorkrailSession(state.workrailSessionId),
|
|
1859
1966
|
});
|
|
1860
1967
|
},
|
|
1861
1968
|
onToolCallStarted: ({ toolName, argsSummary }) => {
|
|
1862
|
-
emitter?.emit({ kind: 'tool_call_started', sessionId, toolName, argsSummary, ...withWorkrailSession(workrailSessionId) });
|
|
1863
|
-
lastNToolCalls.push({ toolName, argsSummary });
|
|
1864
|
-
if (lastNToolCalls.length > STUCK_REPEAT_THRESHOLD) {
|
|
1865
|
-
lastNToolCalls.shift();
|
|
1969
|
+
emitter?.emit({ kind: 'tool_call_started', sessionId, toolName, argsSummary, ...withWorkrailSession(state.workrailSessionId) });
|
|
1970
|
+
state.lastNToolCalls.push({ toolName, argsSummary });
|
|
1971
|
+
if (state.lastNToolCalls.length > STUCK_REPEAT_THRESHOLD) {
|
|
1972
|
+
state.lastNToolCalls.shift();
|
|
1866
1973
|
}
|
|
1867
1974
|
},
|
|
1868
1975
|
onToolCallCompleted: ({ toolName, durationMs, resultSummary }) => {
|
|
1869
|
-
emitter?.emit({ kind: 'tool_call_completed', sessionId, toolName, durationMs, resultSummary, ...withWorkrailSession(workrailSessionId) });
|
|
1976
|
+
emitter?.emit({ kind: 'tool_call_completed', sessionId, toolName, durationMs, resultSummary, ...withWorkrailSession(state.workrailSessionId) });
|
|
1870
1977
|
},
|
|
1871
1978
|
onToolCallFailed: ({ toolName, durationMs, errorMessage }) => {
|
|
1872
|
-
emitter?.emit({ kind: 'tool_call_failed', sessionId, toolName, durationMs, errorMessage, ...withWorkrailSession(workrailSessionId) });
|
|
1979
|
+
emitter?.emit({ kind: 'tool_call_failed', sessionId, toolName, durationMs, errorMessage, ...withWorkrailSession(state.workrailSessionId) });
|
|
1873
1980
|
},
|
|
1874
1981
|
};
|
|
1875
1982
|
const agent = new agent_loop_js_1.AgentLoop({
|
|
1876
|
-
systemPrompt:
|
|
1983
|
+
systemPrompt: sessionCtx.systemPrompt,
|
|
1877
1984
|
modelId,
|
|
1878
1985
|
tools,
|
|
1879
1986
|
client: agentClient,
|
|
@@ -1883,12 +1990,14 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1883
1990
|
? { maxTokens: trigger.agentConfig.maxOutputTokens }
|
|
1884
1991
|
: {}),
|
|
1885
1992
|
});
|
|
1886
|
-
const sessionTimeoutMs
|
|
1887
|
-
const
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1993
|
+
const { sessionTimeoutMs, maxTurns } = sessionCtx;
|
|
1994
|
+
const stuckConfig = {
|
|
1995
|
+
maxTurns,
|
|
1996
|
+
stuckAbortPolicy: trigger.agentConfig?.stuckAbortPolicy ?? 'abort',
|
|
1997
|
+
noProgressAbortEnabled: trigger.agentConfig?.noProgressAbortEnabled ?? false,
|
|
1998
|
+
stuckRepeatThreshold: STUCK_REPEAT_THRESHOLD,
|
|
1999
|
+
};
|
|
2000
|
+
const conversationPath = path.join(sessionsDir, `${sessionId}-conversation.jsonl`);
|
|
1892
2001
|
let lastFlushedMessageCount = 0;
|
|
1893
2002
|
const unsubscribe = agent.subscribe(async (event) => {
|
|
1894
2003
|
if (event.type !== 'turn_end')
|
|
@@ -1896,86 +2005,86 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1896
2005
|
for (const toolResult of event.toolResults) {
|
|
1897
2006
|
if (toolResult.isError) {
|
|
1898
2007
|
const errorText = toolResult.result?.content[0]?.text ?? 'tool error';
|
|
1899
|
-
emitter?.emit({ kind: 'tool_error', sessionId, toolName: toolResult.toolName, error: errorText.slice(0, 200), ...withWorkrailSession(workrailSessionId) });
|
|
2008
|
+
emitter?.emit({ kind: 'tool_error', sessionId, toolName: toolResult.toolName, error: errorText.slice(0, 200), ...withWorkrailSession(state.workrailSessionId) });
|
|
1900
2009
|
}
|
|
1901
2010
|
}
|
|
1902
|
-
turnCount++;
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
}
|
|
1915
|
-
if (lastNToolCalls.length === STUCK_REPEAT_THRESHOLD &&
|
|
1916
|
-
lastNToolCalls.every((c) => c.toolName === lastNToolCalls[0]?.toolName && c.argsSummary === lastNToolCalls[0]?.argsSummary)) {
|
|
1917
|
-
emitter?.emit({
|
|
1918
|
-
kind: 'agent_stuck',
|
|
1919
|
-
sessionId,
|
|
1920
|
-
reason: 'repeated_tool_call',
|
|
1921
|
-
detail: `Same tool+args called ${STUCK_REPEAT_THRESHOLD} times: ${lastNToolCalls[0]?.toolName ?? 'unknown'}`,
|
|
1922
|
-
toolName: lastNToolCalls[0]?.toolName,
|
|
1923
|
-
argsSummary: lastNToolCalls[0]?.argsSummary,
|
|
1924
|
-
...withWorkrailSession(workrailSessionId),
|
|
1925
|
-
});
|
|
1926
|
-
void writeStuckOutboxEntry({
|
|
1927
|
-
workflowId: trigger.workflowId,
|
|
1928
|
-
reason: 'repeated_tool_call',
|
|
1929
|
-
...(issueSummaries.length > 0 ? { issueSummaries: [...issueSummaries] } : {}),
|
|
1930
|
-
});
|
|
1931
|
-
const stuckPolicy = trigger.agentConfig?.stuckAbortPolicy ?? 'abort';
|
|
1932
|
-
if (stuckPolicy !== 'notify_only' && stuckReason === null && timeoutReason === null) {
|
|
1933
|
-
stuckReason = 'repeated_tool_call';
|
|
2011
|
+
state.turnCount++;
|
|
2012
|
+
const signal = evaluateStuckSignals(state, stuckConfig);
|
|
2013
|
+
if (signal !== null) {
|
|
2014
|
+
if (signal.kind === 'max_turns_exceeded') {
|
|
2015
|
+
state.timeoutReason = 'max_turns';
|
|
2016
|
+
emitter?.emit({
|
|
2017
|
+
kind: 'agent_stuck',
|
|
2018
|
+
sessionId,
|
|
2019
|
+
reason: 'timeout_imminent',
|
|
2020
|
+
detail: 'Max-turn limit reached',
|
|
2021
|
+
...withWorkrailSession(state.workrailSessionId),
|
|
2022
|
+
});
|
|
1934
2023
|
agent.abort();
|
|
1935
2024
|
return;
|
|
1936
2025
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
});
|
|
1948
|
-
const noProgressAbortEnabled = trigger.agentConfig?.noProgressAbortEnabled ?? false;
|
|
1949
|
-
if (noProgressAbortEnabled) {
|
|
2026
|
+
else if (signal.kind === 'repeated_tool_call') {
|
|
2027
|
+
emitter?.emit({
|
|
2028
|
+
kind: 'agent_stuck',
|
|
2029
|
+
sessionId,
|
|
2030
|
+
reason: 'repeated_tool_call',
|
|
2031
|
+
detail: `Same tool+args called ${STUCK_REPEAT_THRESHOLD} times: ${signal.toolName}`,
|
|
2032
|
+
toolName: signal.toolName,
|
|
2033
|
+
argsSummary: signal.argsSummary,
|
|
2034
|
+
...withWorkrailSession(state.workrailSessionId),
|
|
2035
|
+
});
|
|
1950
2036
|
void writeStuckOutboxEntry({
|
|
1951
2037
|
workflowId: trigger.workflowId,
|
|
1952
|
-
reason: '
|
|
1953
|
-
...(issueSummaries.length > 0 ? { issueSummaries: [...issueSummaries] } : {}),
|
|
2038
|
+
reason: 'repeated_tool_call',
|
|
2039
|
+
...(state.issueSummaries.length > 0 ? { issueSummaries: [...state.issueSummaries] } : {}),
|
|
1954
2040
|
});
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
stuckReason = 'no_progress';
|
|
2041
|
+
if (stuckConfig.stuckAbortPolicy !== 'notify_only' && state.stuckReason === null && state.timeoutReason === null) {
|
|
2042
|
+
state.stuckReason = 'repeated_tool_call';
|
|
1958
2043
|
agent.abort();
|
|
1959
2044
|
return;
|
|
1960
2045
|
}
|
|
1961
2046
|
}
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2047
|
+
else if (signal.kind === 'no_progress') {
|
|
2048
|
+
emitter?.emit({
|
|
2049
|
+
kind: 'agent_stuck',
|
|
2050
|
+
sessionId,
|
|
2051
|
+
reason: 'no_progress',
|
|
2052
|
+
detail: `${signal.turnCount} turns used, 0 step advances (${signal.maxTurns} turn limit)`,
|
|
2053
|
+
...withWorkrailSession(state.workrailSessionId),
|
|
2054
|
+
});
|
|
2055
|
+
if (stuckConfig.noProgressAbortEnabled) {
|
|
2056
|
+
void writeStuckOutboxEntry({
|
|
2057
|
+
workflowId: trigger.workflowId,
|
|
2058
|
+
reason: 'no_progress',
|
|
2059
|
+
...(state.issueSummaries.length > 0 ? { issueSummaries: [...state.issueSummaries] } : {}),
|
|
2060
|
+
});
|
|
2061
|
+
if (stuckConfig.stuckAbortPolicy !== 'notify_only' && state.stuckReason === null && state.timeoutReason === null) {
|
|
2062
|
+
state.stuckReason = 'no_progress';
|
|
2063
|
+
agent.abort();
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
else if (signal.kind === 'timeout_imminent') {
|
|
2069
|
+
emitter?.emit({
|
|
2070
|
+
kind: 'agent_stuck',
|
|
2071
|
+
sessionId,
|
|
2072
|
+
reason: 'timeout_imminent',
|
|
2073
|
+
detail: `${signal.timeoutReason === 'wall_clock' ? 'Wall-clock timeout' : 'Max-turn limit'} reached`,
|
|
2074
|
+
...withWorkrailSession(state.workrailSessionId),
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
else {
|
|
2078
|
+
(0, assert_never_js_1.assertNever)(signal);
|
|
2079
|
+
}
|
|
1971
2080
|
}
|
|
1972
2081
|
const currentMessages = agent.state.messages;
|
|
1973
2082
|
const newMessages = currentMessages.slice(lastFlushedMessageCount);
|
|
1974
2083
|
lastFlushedMessageCount = currentMessages.length;
|
|
1975
2084
|
void appendConversationMessages(conversationPath, newMessages).catch(() => { });
|
|
1976
|
-
if (pendingSteerParts.length > 0 && !isComplete) {
|
|
1977
|
-
const joined = pendingSteerParts.join('\n\n');
|
|
1978
|
-
pendingSteerParts.length = 0;
|
|
2085
|
+
if (state.pendingSteerParts.length > 0 && !state.isComplete) {
|
|
2086
|
+
const joined = state.pendingSteerParts.join('\n\n');
|
|
2087
|
+
state.pendingSteerParts.length = 0;
|
|
1979
2088
|
agent.steer(buildUserMessage(joined));
|
|
1980
2089
|
}
|
|
1981
2090
|
});
|
|
@@ -1985,14 +2094,14 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
1985
2094
|
try {
|
|
1986
2095
|
const timeoutPromise = new Promise((_, reject) => {
|
|
1987
2096
|
timeoutHandle = setTimeout(() => {
|
|
1988
|
-
if (timeoutReason === null) {
|
|
1989
|
-
timeoutReason = 'wall_clock';
|
|
2097
|
+
if (state.timeoutReason === null) {
|
|
2098
|
+
state.timeoutReason = 'wall_clock';
|
|
1990
2099
|
}
|
|
1991
2100
|
reject(new Error('Workflow timed out'));
|
|
1992
2101
|
}, sessionTimeoutMs);
|
|
1993
2102
|
});
|
|
1994
2103
|
console.log(`[WorkflowRunner] Agent loop started: sessionId=${sessionId} workflowId=${trigger.workflowId} modelId=${modelId}`);
|
|
1995
|
-
await Promise.race([agent.prompt(buildUserMessage(initialPrompt)), timeoutPromise])
|
|
2104
|
+
await Promise.race([agent.prompt(buildUserMessage(sessionCtx.initialPrompt)), timeoutPromise])
|
|
1996
2105
|
.catch((err) => {
|
|
1997
2106
|
agent.abort();
|
|
1998
2107
|
throw err;
|
|
@@ -2019,96 +2128,86 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerR
|
|
|
2019
2128
|
void appendConversationMessages(conversationPath, remainingMessages).catch(() => { });
|
|
2020
2129
|
if (timeoutHandle !== undefined)
|
|
2021
2130
|
clearTimeout(timeoutHandle);
|
|
2022
|
-
if (workrailSessionId !== null) {
|
|
2023
|
-
steerRegistry?.delete(workrailSessionId);
|
|
2131
|
+
if (state.workrailSessionId !== null) {
|
|
2132
|
+
steerRegistry?.delete(state.workrailSessionId);
|
|
2024
2133
|
}
|
|
2025
|
-
if (workrailSessionId !== null) {
|
|
2026
|
-
abortRegistry?.delete(workrailSessionId);
|
|
2134
|
+
if (state.workrailSessionId !== null) {
|
|
2135
|
+
abortRegistry?.delete(state.workrailSessionId);
|
|
2027
2136
|
}
|
|
2028
2137
|
console.log(`[WorkflowRunner] Agent loop ended: sessionId=${sessionId} stopReason=${stopReason}${errorMessage ? ` error=${errorMessage.slice(0, 120)}` : ''}`);
|
|
2029
|
-
writeExecutionStats(DAEMON_STATS_DIR, sessionId, trigger.workflowId, startMs, sessionOutcome, stepAdvanceCount);
|
|
2030
2138
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2139
|
+
const finalizationCtx = {
|
|
2140
|
+
sessionId,
|
|
2141
|
+
workrailSessionId: state.workrailSessionId,
|
|
2142
|
+
startMs,
|
|
2143
|
+
stepAdvanceCount: state.stepAdvanceCount,
|
|
2144
|
+
branchStrategy: trigger.branchStrategy,
|
|
2145
|
+
statsDir,
|
|
2146
|
+
sessionsDir,
|
|
2147
|
+
conversationPath,
|
|
2148
|
+
emitter,
|
|
2149
|
+
daemonRegistry,
|
|
2150
|
+
workflowId: trigger.workflowId,
|
|
2151
|
+
};
|
|
2152
|
+
if (state.stuckReason !== null) {
|
|
2153
|
+
const result = {
|
|
2044
2154
|
_tag: 'stuck',
|
|
2045
2155
|
workflowId: trigger.workflowId,
|
|
2046
|
-
reason: stuckReason,
|
|
2047
|
-
message: `Session aborted: stuck heuristic fired (${stuckReason})`,
|
|
2156
|
+
reason: state.stuckReason,
|
|
2157
|
+
message: `Session aborted: stuck heuristic fired (${state.stuckReason})`,
|
|
2048
2158
|
stopReason: 'aborted',
|
|
2049
|
-
...(issueSummaries.length > 0 ? { issueSummaries: [...issueSummaries] } : {}),
|
|
2159
|
+
...(state.issueSummaries.length > 0 ? { issueSummaries: [...state.issueSummaries] } : {}),
|
|
2050
2160
|
};
|
|
2161
|
+
await finalizeSession(result, finalizationCtx);
|
|
2162
|
+
return result;
|
|
2051
2163
|
}
|
|
2052
|
-
if (timeoutReason !== null) {
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
const
|
|
2057
|
-
? `${trigger.agentConfig?.maxSessionMinutes ?? DEFAULT_SESSION_TIMEOUT_MINUTES} minutes`
|
|
2058
|
-
: `${trigger.agentConfig?.maxTurns ?? DEFAULT_MAX_TURNS} turns`;
|
|
2059
|
-
await fs.unlink(path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`)).catch(() => { });
|
|
2060
|
-
sessionOutcome = 'timeout';
|
|
2061
|
-
return {
|
|
2164
|
+
if (state.timeoutReason !== null) {
|
|
2165
|
+
const limitDescription = state.timeoutReason === 'wall_clock'
|
|
2166
|
+
? `${trigger.agentConfig?.maxSessionMinutes ?? exports.DEFAULT_SESSION_TIMEOUT_MINUTES} minutes`
|
|
2167
|
+
: `${trigger.agentConfig?.maxTurns ?? exports.DEFAULT_MAX_TURNS} turns`;
|
|
2168
|
+
const result = {
|
|
2062
2169
|
_tag: 'timeout',
|
|
2063
2170
|
workflowId: trigger.workflowId,
|
|
2064
|
-
reason: timeoutReason,
|
|
2065
|
-
message: `Workflow ${timeoutReason === 'wall_clock' ? 'timed out' : 'exceeded turn limit'} after ${limitDescription}`,
|
|
2171
|
+
reason: state.timeoutReason,
|
|
2172
|
+
message: `Workflow ${state.timeoutReason === 'wall_clock' ? 'timed out' : 'exceeded turn limit'} after ${limitDescription}`,
|
|
2066
2173
|
stopReason: 'aborted',
|
|
2067
2174
|
};
|
|
2175
|
+
await finalizeSession(result, finalizationCtx);
|
|
2176
|
+
return result;
|
|
2068
2177
|
}
|
|
2069
2178
|
if (stopReason === 'error' || errorMessage) {
|
|
2070
2179
|
const errMsg = errorMessage ?? 'Agent stopped with error reason';
|
|
2071
|
-
|
|
2072
|
-
if (workrailSessionId !== null)
|
|
2073
|
-
daemonRegistry?.unregister(workrailSessionId, 'failed');
|
|
2074
|
-
const lastToolCalled = lastNToolCalls.length > 0 ? lastNToolCalls[lastNToolCalls.length - 1] : null;
|
|
2180
|
+
const lastToolCalled = state.lastNToolCalls.length > 0 ? state.lastNToolCalls[state.lastNToolCalls.length - 1] : null;
|
|
2075
2181
|
const stuckMarker = `\n\nWORKTRAIN_STUCK: ${JSON.stringify({
|
|
2076
2182
|
reason: 'session_error',
|
|
2077
2183
|
error: errMsg.slice(0, 500),
|
|
2078
2184
|
workflowId: trigger.workflowId,
|
|
2079
2185
|
sessionId,
|
|
2080
|
-
turnCount,
|
|
2081
|
-
stepAdvanceCount,
|
|
2186
|
+
turnCount: state.turnCount,
|
|
2187
|
+
stepAdvanceCount: state.stepAdvanceCount,
|
|
2082
2188
|
...(lastToolCalled !== null && { lastToolCalled }),
|
|
2083
|
-
...(issueSummaries.length > 0 && { issueSummaries }),
|
|
2189
|
+
...(state.issueSummaries.length > 0 && { issueSummaries: state.issueSummaries }),
|
|
2084
2190
|
})}`;
|
|
2085
|
-
|
|
2086
|
-
sessionOutcome = 'error';
|
|
2087
|
-
return {
|
|
2191
|
+
const result = {
|
|
2088
2192
|
_tag: 'error',
|
|
2089
2193
|
workflowId: trigger.workflowId,
|
|
2090
2194
|
message: errMsg,
|
|
2091
2195
|
stopReason,
|
|
2092
2196
|
lastStepNotes: stuckMarker,
|
|
2093
2197
|
};
|
|
2198
|
+
await finalizeSession(result, finalizationCtx);
|
|
2199
|
+
return result;
|
|
2094
2200
|
}
|
|
2095
|
-
|
|
2096
|
-
await fs.unlink(path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`)).catch(() => {
|
|
2097
|
-
});
|
|
2098
|
-
await fs.unlink(conversationPath).catch(() => { });
|
|
2099
|
-
}
|
|
2100
|
-
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: stopReason, ...withWorkrailSession(workrailSessionId) });
|
|
2101
|
-
if (workrailSessionId !== null)
|
|
2102
|
-
daemonRegistry?.unregister(workrailSessionId, 'completed');
|
|
2103
|
-
sessionOutcome = 'success';
|
|
2104
|
-
return {
|
|
2201
|
+
const result = {
|
|
2105
2202
|
_tag: 'success',
|
|
2106
2203
|
workflowId: trigger.workflowId,
|
|
2107
2204
|
stopReason,
|
|
2108
|
-
...(lastStepNotes !== undefined ? { lastStepNotes } : {}),
|
|
2109
|
-
...(lastStepArtifacts !== undefined ? { lastStepArtifacts } : {}),
|
|
2205
|
+
...(state.lastStepNotes !== undefined ? { lastStepNotes: state.lastStepNotes } : {}),
|
|
2206
|
+
...(state.lastStepArtifacts !== undefined ? { lastStepArtifacts: state.lastStepArtifacts } : {}),
|
|
2110
2207
|
...(sessionWorktreePath !== undefined ? { sessionWorkspacePath: sessionWorktreePath } : {}),
|
|
2111
2208
|
...(sessionWorktreePath !== undefined ? { sessionId } : {}),
|
|
2112
2209
|
...(trigger.botIdentity !== undefined ? { botIdentity: trigger.botIdentity } : {}),
|
|
2113
2210
|
};
|
|
2211
|
+
await finalizeSession(result, finalizationCtx);
|
|
2212
|
+
return result;
|
|
2114
2213
|
}
|