@exaudeus/workrail 3.76.0 → 3.77.1

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.
Files changed (71) hide show
  1. package/dist/console-ui/assets/{index-DFZjlsUM.js → index-BooFww1c.js} +1 -1
  2. package/dist/console-ui/index.html +1 -1
  3. package/dist/daemon/context-loader.d.ts +1 -1
  4. package/dist/daemon/core/agent-client.d.ts +7 -0
  5. package/dist/daemon/core/agent-client.js +31 -0
  6. package/dist/daemon/core/index.d.ts +6 -0
  7. package/dist/daemon/core/index.js +19 -0
  8. package/dist/daemon/core/session-context.d.ts +14 -0
  9. package/dist/daemon/core/session-context.js +21 -0
  10. package/dist/daemon/core/session-result.d.ts +10 -0
  11. package/dist/daemon/core/session-result.js +92 -0
  12. package/dist/daemon/core/system-prompt.d.ts +6 -0
  13. package/dist/daemon/core/system-prompt.js +151 -0
  14. package/dist/daemon/io/conversation-log.d.ts +2 -0
  15. package/dist/daemon/io/conversation-log.js +45 -0
  16. package/dist/daemon/io/execution-stats.d.ts +7 -0
  17. package/dist/daemon/io/execution-stats.js +86 -0
  18. package/dist/daemon/io/index.d.ts +5 -0
  19. package/dist/daemon/io/index.js +24 -0
  20. package/dist/daemon/io/session-notes-loader.d.ts +4 -0
  21. package/dist/daemon/io/session-notes-loader.js +45 -0
  22. package/dist/daemon/io/soul-loader.d.ts +3 -0
  23. package/dist/daemon/io/soul-loader.js +68 -0
  24. package/dist/daemon/io/workspace-context-loader.d.ts +17 -0
  25. package/dist/daemon/io/workspace-context-loader.js +137 -0
  26. package/dist/daemon/runner/agent-loop-runner.d.ts +28 -0
  27. package/dist/daemon/runner/agent-loop-runner.js +250 -0
  28. package/dist/daemon/runner/construct-tools.d.ts +5 -0
  29. package/dist/daemon/runner/construct-tools.js +30 -0
  30. package/dist/daemon/runner/finalize-session.d.ts +3 -0
  31. package/dist/daemon/runner/finalize-session.js +75 -0
  32. package/dist/daemon/runner/index.d.ts +8 -0
  33. package/dist/daemon/runner/index.js +18 -0
  34. package/dist/daemon/runner/pre-agent-session.d.ts +7 -0
  35. package/dist/daemon/runner/pre-agent-session.js +227 -0
  36. package/dist/daemon/runner/runner-types.d.ts +73 -0
  37. package/dist/daemon/runner/runner-types.js +39 -0
  38. package/dist/daemon/runner/tool-schemas.d.ts +1 -0
  39. package/dist/daemon/runner/tool-schemas.js +151 -0
  40. package/dist/daemon/session-scope.d.ts +1 -1
  41. package/dist/daemon/startup-recovery.d.ts +20 -0
  42. package/dist/daemon/startup-recovery.js +323 -0
  43. package/dist/daemon/state/index.d.ts +6 -0
  44. package/dist/daemon/state/index.js +14 -0
  45. package/dist/daemon/state/session-state.d.ts +23 -0
  46. package/dist/daemon/state/session-state.js +44 -0
  47. package/dist/daemon/state/stuck-detection.d.ts +22 -0
  48. package/dist/daemon/state/stuck-detection.js +25 -0
  49. package/dist/daemon/state/terminal-signal.d.ts +9 -0
  50. package/dist/daemon/state/terminal-signal.js +10 -0
  51. package/dist/daemon/tools/file-tools.d.ts +1 -1
  52. package/dist/daemon/turn-end/detect-stuck.d.ts +2 -2
  53. package/dist/daemon/turn-end/detect-stuck.js +2 -2
  54. package/dist/daemon/turn-end/step-injector.d.ts +1 -1
  55. package/dist/daemon/types.d.ts +105 -0
  56. package/dist/daemon/types.js +11 -0
  57. package/dist/daemon/workflow-enricher.d.ts +16 -0
  58. package/dist/daemon/workflow-enricher.js +58 -0
  59. package/dist/daemon/workflow-runner.d.ts +13 -277
  60. package/dist/daemon/workflow-runner.js +63 -1421
  61. package/dist/manifest.json +231 -31
  62. package/dist/trigger/coordinator-deps.d.ts +1 -1
  63. package/dist/trigger/delivery-client.d.ts +1 -1
  64. package/dist/trigger/delivery-pipeline.d.ts +1 -1
  65. package/dist/trigger/notification-service.d.ts +1 -1
  66. package/dist/trigger/trigger-listener.js +6 -2
  67. package/dist/trigger/trigger-router.d.ts +2 -2
  68. package/docs/ideas/backlog.md +249 -25
  69. package/docs/reference/worktrain-daemon-invariants.md +33 -49
  70. package/docs/vision.md +5 -15
  71. package/package.json +2 -2
@@ -0,0 +1,17 @@
1
+ export declare const WORKSPACE_CONTEXT_MAX_BYTES: number;
2
+ export declare const MAX_GLOB_FILES_PER_PATTERN = 20;
3
+ type LiteralCandidatePath = {
4
+ readonly kind: 'literal';
5
+ readonly relativePath: string;
6
+ };
7
+ type GlobCandidatePath = {
8
+ readonly kind: 'glob';
9
+ readonly pattern: string;
10
+ readonly stripFrontmatter: boolean;
11
+ readonly sort: 'alpha';
12
+ };
13
+ type WorkspaceContextCandidate = LiteralCandidatePath | GlobCandidatePath;
14
+ export declare const WORKSPACE_CONTEXT_CANDIDATE_PATHS: readonly WorkspaceContextCandidate[];
15
+ export declare function stripFrontmatter(content: string): string;
16
+ export declare function loadWorkspaceContext(workspacePath: string): Promise<string | null>;
17
+ export {};
@@ -0,0 +1,137 @@
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.WORKSPACE_CONTEXT_CANDIDATE_PATHS = exports.MAX_GLOB_FILES_PER_PATTERN = exports.WORKSPACE_CONTEXT_MAX_BYTES = void 0;
37
+ exports.stripFrontmatter = stripFrontmatter;
38
+ exports.loadWorkspaceContext = loadWorkspaceContext;
39
+ const fs = __importStar(require("node:fs/promises"));
40
+ const path = __importStar(require("node:path"));
41
+ const tinyglobby_1 = require("tinyglobby");
42
+ exports.WORKSPACE_CONTEXT_MAX_BYTES = 32 * 1024;
43
+ exports.MAX_GLOB_FILES_PER_PATTERN = 20;
44
+ exports.WORKSPACE_CONTEXT_CANDIDATE_PATHS = [
45
+ { kind: 'literal', relativePath: '.claude/CLAUDE.md' },
46
+ { kind: 'literal', relativePath: 'CLAUDE.md' },
47
+ { kind: 'literal', relativePath: 'CLAUDE.local.md' },
48
+ { kind: 'literal', relativePath: 'AGENTS.md' },
49
+ { kind: 'literal', relativePath: '.github/AGENTS.md' },
50
+ { kind: 'glob', pattern: '.cursor/rules/*.mdc', stripFrontmatter: true, sort: 'alpha' },
51
+ { kind: 'literal', relativePath: '.cursorrules' },
52
+ { kind: 'glob', pattern: '.windsurf/rules/*.md', stripFrontmatter: true, sort: 'alpha' },
53
+ { kind: 'glob', pattern: '.firebender/rules/*.mdc', stripFrontmatter: true, sort: 'alpha' },
54
+ { kind: 'literal', relativePath: '.firebender/AGENTS.md' },
55
+ { kind: 'literal', relativePath: '.github/copilot-instructions.md' },
56
+ { kind: 'glob', pattern: '.continue/rules/*.md', stripFrontmatter: false, sort: 'alpha' },
57
+ ];
58
+ function stripFrontmatter(content) {
59
+ if (!content.startsWith('---\n') && !content.startsWith('---\r\n'))
60
+ return content;
61
+ const endIdx = content.indexOf('\n---', 4);
62
+ if (endIdx === -1)
63
+ return content;
64
+ return content.slice(endIdx + 4).trimStart();
65
+ }
66
+ async function loadWorkspaceContext(workspacePath) {
67
+ const parts = [];
68
+ const injectedPaths = [];
69
+ let combinedBytes = 0;
70
+ let truncated = false;
71
+ function accumulateFile(relativePath, content) {
72
+ const contentBytes = Buffer.byteLength(content, 'utf8');
73
+ if (combinedBytes + contentBytes > exports.WORKSPACE_CONTEXT_MAX_BYTES) {
74
+ const remaining = exports.WORKSPACE_CONTEXT_MAX_BYTES - combinedBytes;
75
+ const truncatedContent = content.slice(0, remaining);
76
+ parts.push(`### ${relativePath}\n${truncatedContent}`);
77
+ injectedPaths.push(relativePath);
78
+ truncated = true;
79
+ }
80
+ else {
81
+ parts.push(`### ${relativePath}\n${content}`);
82
+ injectedPaths.push(relativePath);
83
+ combinedBytes += contentBytes;
84
+ }
85
+ }
86
+ for (const entry of exports.WORKSPACE_CONTEXT_CANDIDATE_PATHS) {
87
+ if (truncated)
88
+ break;
89
+ if (entry.kind === 'literal') {
90
+ const fullPath = path.join(workspacePath, entry.relativePath);
91
+ let content;
92
+ try {
93
+ content = await fs.readFile(fullPath, 'utf8');
94
+ }
95
+ catch (err) {
96
+ const isEnoent = err instanceof Error && 'code' in err && err.code === 'ENOENT';
97
+ if (!isEnoent) {
98
+ console.warn(`[WorkflowRunner] Skipping ${fullPath}: ${err instanceof Error ? err.message : String(err)}`);
99
+ }
100
+ continue;
101
+ }
102
+ accumulateFile(entry.relativePath, content);
103
+ }
104
+ else {
105
+ const matches = await (0, tinyglobby_1.glob)(entry.pattern, { cwd: workspacePath, absolute: false });
106
+ const sorted = [...matches].sort();
107
+ if (sorted.length > exports.MAX_GLOB_FILES_PER_PATTERN) {
108
+ console.warn(`[WorkflowRunner] ${entry.pattern}: ${sorted.length} files found, capped at ${exports.MAX_GLOB_FILES_PER_PATTERN}`);
109
+ }
110
+ for (const relativePath of sorted.slice(0, exports.MAX_GLOB_FILES_PER_PATTERN)) {
111
+ if (truncated)
112
+ break;
113
+ const fullPath = path.join(workspacePath, relativePath);
114
+ let content;
115
+ try {
116
+ content = await fs.readFile(fullPath, 'utf8');
117
+ }
118
+ catch (err) {
119
+ const isEnoent = err instanceof Error && 'code' in err && err.code === 'ENOENT';
120
+ if (!isEnoent) {
121
+ console.warn(`[WorkflowRunner] Skipping ${fullPath}: ${err instanceof Error ? err.message : String(err)}`);
122
+ }
123
+ continue;
124
+ }
125
+ accumulateFile(relativePath, entry.stripFrontmatter ? stripFrontmatter(content) : content);
126
+ }
127
+ }
128
+ }
129
+ if (parts.length === 0)
130
+ return null;
131
+ let combined = parts.join('\n\n');
132
+ if (truncated) {
133
+ combined += '\n\n[Workspace context truncated: combined size exceeded 32 KB limit. Some files may be missing.]';
134
+ }
135
+ console.log(`[WorkflowRunner] Injecting workspace context from: ${injectedPaths.join(', ')}`);
136
+ return combined;
137
+ }
@@ -0,0 +1,28 @@
1
+ import type { AgentLoop, AgentEvent, AgentLoopCallbacks } from '../agent-loop.js';
2
+ import type { V2ToolContext } from '../../mcp/types.js';
3
+ import type { DaemonRegistry } from '../../v2/infra/in-memory/daemon-registry/index.js';
4
+ import type { DaemonEventEmitter } from '../daemon-events.js';
5
+ import type { SessionState } from '../state/session-state.js';
6
+ import type { StuckConfig } from '../state/stuck-detection.js';
7
+ import { ActiveSessionSet } from '../active-sessions.js';
8
+ import type { WorkflowTrigger } from '../types.js';
9
+ import type { PreAgentSession, AgentReadySession, SessionOutcome } from './runner-types.js';
10
+ import type { runWorkflow } from '../workflow-runner.js';
11
+ import type { EnricherResult } from '../workflow-enricher.js';
12
+ export interface TurnEndSubscriberContext {
13
+ readonly agent: AgentLoop;
14
+ readonly state: SessionState;
15
+ readonly stuckConfig: StuckConfig;
16
+ readonly sessionId: string;
17
+ readonly workflowId: string;
18
+ readonly emitter: DaemonEventEmitter | undefined;
19
+ readonly conversationPath: string;
20
+ readonly lastFlushedRef: {
21
+ count: number;
22
+ };
23
+ readonly stuckRepeatThreshold: number;
24
+ }
25
+ export declare function buildTurnEndSubscriber(ctx: TurnEndSubscriberContext): (event: AgentEvent) => Promise<void>;
26
+ export declare function buildAgentCallbacks(sessionId: string, state: SessionState, modelId: string, emitter: DaemonEventEmitter | undefined, stuckRepeatThreshold: number, workflowId?: string): AgentLoopCallbacks;
27
+ export declare function buildAgentReadySession(preAgentSession: PreAgentSession, trigger: WorkflowTrigger, ctx: V2ToolContext, apiKey: string, sessionId: string, emitter: DaemonEventEmitter | undefined, daemonRegistry: DaemonRegistry | undefined, activeSessionSet: ActiveSessionSet | undefined, runWorkflowFn: typeof runWorkflow, enricherResult?: EnricherResult): Promise<AgentReadySession>;
28
+ export declare function runAgentLoop(session: AgentReadySession, trigger: WorkflowTrigger, conversationPath: string): Promise<SessionOutcome>;
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildTurnEndSubscriber = buildTurnEndSubscriber;
4
+ exports.buildAgentCallbacks = buildAgentCallbacks;
5
+ exports.buildAgentReadySession = buildAgentReadySession;
6
+ exports.runAgentLoop = runAgentLoop;
7
+ const agent_loop_js_1 = require("../agent-loop.js");
8
+ const index_js_1 = require("../state/index.js");
9
+ const session_context_js_1 = require("../core/session-context.js");
10
+ const assert_never_js_1 = require("../../runtime/assert-never.js");
11
+ const index_js_2 = require("../io/index.js");
12
+ const index_js_3 = require("../io/index.js");
13
+ const context_loader_js_1 = require("../context-loader.js");
14
+ const session_scope_js_1 = require("../session-scope.js");
15
+ const _shared_js_1 = require("../tools/_shared.js");
16
+ const step_injector_js_1 = require("../turn-end/step-injector.js");
17
+ const conversation_flusher_js_1 = require("../turn-end/conversation-flusher.js");
18
+ const tool_schemas_js_1 = require("./tool-schemas.js");
19
+ const construct_tools_js_1 = require("./construct-tools.js");
20
+ function buildTurnEndSubscriber(ctx) {
21
+ return async (event) => {
22
+ if (event.type !== 'turn_end')
23
+ return;
24
+ for (const toolResult of event.toolResults) {
25
+ if (toolResult.isError) {
26
+ const errorText = toolResult.result?.content[0]?.text ?? 'tool error';
27
+ ctx.emitter?.emit({ kind: 'tool_error', sessionId: ctx.sessionId, toolName: toolResult.toolName, error: errorText.slice(0, 200), ...(0, _shared_js_1.withWorkrailSession)(ctx.state.workrailSessionId) });
28
+ }
29
+ }
30
+ ctx.state.turnCount++;
31
+ const signal = (0, index_js_1.evaluateStuckSignals)(ctx.state, ctx.stuckConfig);
32
+ if (signal !== null) {
33
+ if (signal.kind === 'max_turns_exceeded') {
34
+ (0, index_js_1.setTerminalSignal)(ctx.state, { kind: 'timeout', reason: 'max_turns' });
35
+ ctx.emitter?.emit({ kind: 'agent_stuck', sessionId: ctx.sessionId, reason: 'timeout_imminent', detail: 'Max-turn limit reached', ...(0, _shared_js_1.withWorkrailSession)(ctx.state.workrailSessionId) });
36
+ ctx.agent.abort();
37
+ return;
38
+ }
39
+ else if (signal.kind === 'repeated_tool_call') {
40
+ ctx.emitter?.emit({ kind: 'agent_stuck', sessionId: ctx.sessionId, reason: 'repeated_tool_call', detail: `Same tool+args called ${ctx.stuckRepeatThreshold} times: ${signal.toolName}`, toolName: signal.toolName, argsSummary: signal.argsSummary, ...(0, _shared_js_1.withWorkrailSession)(ctx.state.workrailSessionId) });
41
+ void (0, index_js_3.writeStuckOutboxEntry)({ workflowId: ctx.workflowId, reason: 'repeated_tool_call', ...(ctx.state.issueSummaries.length > 0 ? { issueSummaries: [...ctx.state.issueSummaries] } : {}) });
42
+ if (ctx.stuckConfig.stuckAbortPolicy !== 'notify_only') {
43
+ if ((0, index_js_1.setTerminalSignal)(ctx.state, { kind: 'stuck', reason: 'repeated_tool_call' })) {
44
+ ctx.agent.abort();
45
+ return;
46
+ }
47
+ }
48
+ }
49
+ else if (signal.kind === 'no_progress') {
50
+ ctx.emitter?.emit({ kind: 'agent_stuck', sessionId: ctx.sessionId, reason: 'no_progress', detail: `${signal.turnCount} turns used, 0 step advances (${signal.maxTurns} turn limit)`, ...(0, _shared_js_1.withWorkrailSession)(ctx.state.workrailSessionId) });
51
+ if (ctx.stuckConfig.noProgressAbortEnabled) {
52
+ void (0, index_js_3.writeStuckOutboxEntry)({ workflowId: ctx.workflowId, reason: 'no_progress', ...(ctx.state.issueSummaries.length > 0 ? { issueSummaries: [...ctx.state.issueSummaries] } : {}) });
53
+ if (ctx.stuckConfig.stuckAbortPolicy !== 'notify_only') {
54
+ if ((0, index_js_1.setTerminalSignal)(ctx.state, { kind: 'stuck', reason: 'no_progress' })) {
55
+ ctx.agent.abort();
56
+ return;
57
+ }
58
+ }
59
+ }
60
+ }
61
+ else if (signal.kind === 'timeout_imminent') {
62
+ ctx.emitter?.emit({ kind: 'agent_stuck', sessionId: ctx.sessionId, reason: 'timeout_imminent', detail: `${signal.timeoutReason === 'wall_clock' ? 'Wall-clock timeout' : 'Max-turn limit'} reached`, ...(0, _shared_js_1.withWorkrailSession)(ctx.state.workrailSessionId) });
63
+ }
64
+ else {
65
+ (0, assert_never_js_1.assertNever)(signal);
66
+ }
67
+ }
68
+ (0, conversation_flusher_js_1.flushConversation)(ctx.agent.state.messages, ctx.lastFlushedRef, ctx.conversationPath, index_js_3.appendConversationMessages);
69
+ (0, step_injector_js_1.injectPendingSteps)(ctx.state, ctx.agent);
70
+ };
71
+ }
72
+ function buildAgentCallbacks(sessionId, state, modelId, emitter, stuckRepeatThreshold, workflowId) {
73
+ return {
74
+ onLlmTurnStarted: ({ messageCount }) => {
75
+ emitter?.emit({ kind: 'llm_turn_started', sessionId, messageCount, modelId, ...(0, _shared_js_1.withWorkrailSession)(state.workrailSessionId) });
76
+ },
77
+ onLlmTurnCompleted: ({ stopReason, outputTokens, inputTokens, toolNamesRequested }) => {
78
+ emitter?.emit({ kind: 'llm_turn_completed', sessionId, stopReason, outputTokens, inputTokens, toolNamesRequested, ...(0, _shared_js_1.withWorkrailSession)(state.workrailSessionId) });
79
+ },
80
+ onToolCallStarted: ({ toolName, argsSummary }) => {
81
+ emitter?.emit({ kind: 'tool_call_started', sessionId, toolName, argsSummary, ...(0, _shared_js_1.withWorkrailSession)(state.workrailSessionId) });
82
+ (0, index_js_1.recordToolCall)(state, toolName, argsSummary, stuckRepeatThreshold);
83
+ },
84
+ onToolCallCompleted: ({ toolName, durationMs, resultSummary }) => {
85
+ emitter?.emit({ kind: 'tool_call_completed', sessionId, toolName, durationMs, resultSummary, ...(0, _shared_js_1.withWorkrailSession)(state.workrailSessionId) });
86
+ },
87
+ onToolCallFailed: ({ toolName, durationMs, errorMessage }) => {
88
+ emitter?.emit({ kind: 'tool_call_failed', sessionId, toolName, durationMs, errorMessage, ...(0, _shared_js_1.withWorkrailSession)(state.workrailSessionId) });
89
+ },
90
+ onStallDetected: () => {
91
+ (0, index_js_1.setTerminalSignal)(state, { kind: 'stuck', reason: 'stall' });
92
+ emitter?.emit({
93
+ kind: 'agent_stuck',
94
+ sessionId,
95
+ reason: 'stall',
96
+ detail: `No LLM API call started within the stall timeout window. Last tool calls: ${state.lastNToolCalls.map((c) => c.toolName).join(', ') || 'none'}`,
97
+ ...(0, _shared_js_1.withWorkrailSession)(state.workrailSessionId),
98
+ });
99
+ void (0, index_js_3.writeStuckOutboxEntry)({
100
+ workflowId: workflowId ?? sessionId,
101
+ reason: 'stall',
102
+ ...(state.issueSummaries.length > 0 ? { issueSummaries: [...state.issueSummaries] } : {}),
103
+ });
104
+ },
105
+ };
106
+ }
107
+ function buildUserMessage(text) {
108
+ return { role: 'user', content: text, timestamp: Date.now() };
109
+ }
110
+ async function buildAgentReadySession(preAgentSession, trigger, ctx, apiKey, sessionId, emitter, daemonRegistry, activeSessionSet, runWorkflowFn, enricherResult) {
111
+ const { state, firstStepPrompt, sessionWorkspacePath, sessionWorktreePath, agentClient, modelId } = preAgentSession;
112
+ const startContinueToken = preAgentSession.continueToken;
113
+ const handle = preAgentSession.handle;
114
+ const MAX_ISSUE_SUMMARIES = 10;
115
+ const STUCK_REPEAT_THRESHOLD = 3;
116
+ const onAdvance = (stepText, continueToken) => {
117
+ (0, index_js_1.advanceStep)(state, stepText, continueToken);
118
+ if (state.workrailSessionId !== null)
119
+ daemonRegistry?.heartbeat(state.workrailSessionId);
120
+ emitter?.emit({ kind: 'step_advanced', sessionId, ...(0, _shared_js_1.withWorkrailSession)(state.workrailSessionId) });
121
+ };
122
+ const onComplete = (notes, artifacts) => {
123
+ (0, index_js_1.recordCompletion)(state, notes, artifacts);
124
+ };
125
+ const schemas = (0, tool_schemas_js_1.getSchemas)();
126
+ const scope = {
127
+ fileTracker: new session_scope_js_1.DefaultFileStateTracker(preAgentSession.readFileState),
128
+ onAdvance,
129
+ onComplete,
130
+ onTokenUpdate: (t) => { (0, index_js_1.updateToken)(state, t); },
131
+ onIssueReported: (summary) => {
132
+ if (state.issueSummaries.length < MAX_ISSUE_SUMMARIES) {
133
+ state.issueSummaries.push(summary);
134
+ }
135
+ },
136
+ onSteer: (text) => { state.pendingSteerParts.push(text); },
137
+ getCurrentToken: () => state.currentContinueToken,
138
+ sessionWorkspacePath,
139
+ spawnCurrentDepth: preAgentSession.spawnCurrentDepth,
140
+ spawnMaxDepth: preAgentSession.spawnMaxDepth,
141
+ workrailSessionId: state.workrailSessionId,
142
+ emitter,
143
+ sessionId,
144
+ workflowId: trigger.workflowId,
145
+ activeSessionSet,
146
+ };
147
+ const tools = (0, construct_tools_js_1.constructTools)(ctx, apiKey, schemas, scope, runWorkflowFn);
148
+ const contextLoader = new context_loader_js_1.DefaultContextLoader(index_js_2.loadDaemonSoul, index_js_2.loadWorkspaceContext, index_js_2.loadSessionNotes, ctx);
149
+ const baseCtx = await contextLoader.loadBase(trigger);
150
+ const contextBundle = await contextLoader.loadSession(startContinueToken, baseCtx);
151
+ const effectiveWorkspacePath = sessionWorkspacePath;
152
+ const sessionCtx = (0, session_context_js_1.buildSessionContext)(trigger, contextBundle, firstStepPrompt || 'No step content available', effectiveWorkspacePath, enricherResult);
153
+ const agentCallbacks = buildAgentCallbacks(sessionId, state, modelId, emitter, STUCK_REPEAT_THRESHOLD, trigger.workflowId);
154
+ const agent = new agent_loop_js_1.AgentLoop({
155
+ systemPrompt: sessionCtx.systemPrompt,
156
+ modelId,
157
+ tools,
158
+ client: agentClient,
159
+ toolExecution: 'sequential',
160
+ callbacks: agentCallbacks,
161
+ ...(trigger.agentConfig?.maxOutputTokens !== undefined
162
+ ? { maxTokens: trigger.agentConfig.maxOutputTokens }
163
+ : {}),
164
+ stallTimeoutMs: sessionCtx.stallTimeoutMs,
165
+ });
166
+ handle?.setAgent(agent);
167
+ return {
168
+ preAgentSession,
169
+ contextBundle,
170
+ scope,
171
+ tools,
172
+ sessionCtx,
173
+ handle,
174
+ sessionId,
175
+ workflowId: trigger.workflowId,
176
+ worktreePath: sessionWorktreePath,
177
+ agent,
178
+ stuckRepeatThreshold: STUCK_REPEAT_THRESHOLD,
179
+ };
180
+ }
181
+ async function runAgentLoop(session, trigger, conversationPath) {
182
+ const { agent, preAgentSession, sessionCtx, sessionId, handle } = session;
183
+ const { state } = preAgentSession;
184
+ const { emitter } = session.scope;
185
+ const { stuckRepeatThreshold } = session;
186
+ const { sessionTimeoutMs, maxTurns } = sessionCtx;
187
+ const stuckConfig = {
188
+ maxTurns,
189
+ stuckAbortPolicy: trigger.agentConfig?.stuckAbortPolicy ?? 'abort',
190
+ noProgressAbortEnabled: trigger.agentConfig?.noProgressAbortEnabled ?? false,
191
+ stuckRepeatThreshold,
192
+ };
193
+ const lastFlushedRef = { count: 0 };
194
+ const unsubscribe = agent.subscribe(buildTurnEndSubscriber({
195
+ agent,
196
+ state,
197
+ stuckConfig,
198
+ sessionId,
199
+ workflowId: trigger.workflowId,
200
+ emitter,
201
+ conversationPath,
202
+ lastFlushedRef,
203
+ stuckRepeatThreshold,
204
+ }));
205
+ let stopReason = 'stop';
206
+ let errorMessage;
207
+ let timeoutHandle;
208
+ try {
209
+ const timeoutPromise = new Promise((_, reject) => {
210
+ timeoutHandle = setTimeout(() => {
211
+ (0, index_js_1.setTerminalSignal)(state, { kind: 'timeout', reason: 'wall_clock' });
212
+ reject(new Error('Workflow timed out'));
213
+ }, sessionTimeoutMs);
214
+ });
215
+ console.log(`[WorkflowRunner] Agent loop started: sessionId=${sessionId} workflowId=${trigger.workflowId} modelId=${preAgentSession.modelId}`);
216
+ await Promise.race([agent.prompt(buildUserMessage(sessionCtx.initialPrompt)), timeoutPromise])
217
+ .catch((err) => {
218
+ agent.abort();
219
+ throw err;
220
+ });
221
+ const messages = agent.state.messages;
222
+ let lastAssistant;
223
+ for (let i = messages.length - 1; i >= 0; i--) {
224
+ const m = messages[i];
225
+ if ('role' in m && m.role === 'assistant') {
226
+ lastAssistant = m;
227
+ break;
228
+ }
229
+ }
230
+ stopReason = lastAssistant?.stopReason ?? 'stop';
231
+ errorMessage = lastAssistant?.errorMessage;
232
+ }
233
+ catch (err) {
234
+ errorMessage = err instanceof Error ? err.message : String(err);
235
+ stopReason = 'error';
236
+ }
237
+ finally {
238
+ unsubscribe();
239
+ const remainingMessages = agent.state.messages.slice(lastFlushedRef.count);
240
+ void (0, index_js_3.appendConversationMessages)(conversationPath, remainingMessages).catch(() => { });
241
+ if (timeoutHandle !== undefined)
242
+ clearTimeout(timeoutHandle);
243
+ handle?.dispose();
244
+ console.log(`[WorkflowRunner] Agent loop ended: sessionId=${sessionId} stopReason=${stopReason}${errorMessage ? ` error=${errorMessage.slice(0, 120)}` : ''}`);
245
+ }
246
+ if (stopReason === 'error') {
247
+ return { kind: 'aborted', errorMessage };
248
+ }
249
+ return { kind: 'completed', stopReason, errorMessage };
250
+ }
@@ -0,0 +1,5 @@
1
+ import type { AgentTool } from '../agent-loop.js';
2
+ import type { V2ToolContext } from '../../mcp/types.js';
3
+ import type { SessionScope } from '../session-scope.js';
4
+ import type { runWorkflow } from '../workflow-runner.js';
5
+ export declare function constructTools(ctx: V2ToolContext, apiKey: string, schemas: Record<string, any>, scope: SessionScope, runWorkflowFn: typeof runWorkflow): readonly AgentTool[];
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.constructTools = constructTools;
4
+ const index_js_1 = require("../../mcp/handlers/v2-execution/index.js");
5
+ const continue_workflow_js_1 = require("../tools/continue-workflow.js");
6
+ const bash_js_1 = require("../tools/bash.js");
7
+ const file_tools_js_1 = require("../tools/file-tools.js");
8
+ const glob_grep_js_1 = require("../tools/glob-grep.js");
9
+ const spawn_agent_js_1 = require("../tools/spawn-agent.js");
10
+ const report_issue_js_1 = require("../tools/report-issue.js");
11
+ const signal_coordinator_js_1 = require("../tools/signal-coordinator.js");
12
+ function constructTools(ctx, apiKey, schemas, scope, runWorkflowFn) {
13
+ const { fileTracker, onAdvance, onComplete, onTokenUpdate, onIssueReported, getCurrentToken, sessionWorkspacePath, spawnCurrentDepth, spawnMaxDepth, emitter, activeSessionSet, } = scope;
14
+ const sid = scope.sessionId;
15
+ const workrailSid = scope.workrailSessionId;
16
+ const readFileStateMap = fileTracker.toMap();
17
+ return [
18
+ (0, continue_workflow_js_1.makeCompleteStepTool)(sid, ctx, getCurrentToken, onAdvance, onComplete, onTokenUpdate, schemas, index_js_1.executeContinueWorkflow, emitter, workrailSid),
19
+ (0, continue_workflow_js_1.makeContinueWorkflowTool)(sid, ctx, onAdvance, onComplete, schemas, index_js_1.executeContinueWorkflow, emitter, workrailSid),
20
+ (0, bash_js_1.makeBashTool)(sessionWorkspacePath, schemas, sid, emitter, workrailSid),
21
+ (0, file_tools_js_1.makeReadTool)(sessionWorkspacePath, readFileStateMap, schemas, sid, emitter, workrailSid),
22
+ (0, file_tools_js_1.makeWriteTool)(sessionWorkspacePath, readFileStateMap, schemas, sid, emitter, workrailSid),
23
+ (0, glob_grep_js_1.makeGlobTool)(sessionWorkspacePath, schemas, sid, emitter, workrailSid),
24
+ (0, glob_grep_js_1.makeGrepTool)(sessionWorkspacePath, schemas, sid, emitter, workrailSid),
25
+ (0, file_tools_js_1.makeEditTool)(sessionWorkspacePath, readFileStateMap, schemas, sid, emitter, workrailSid),
26
+ (0, report_issue_js_1.makeReportIssueTool)(sid, emitter, workrailSid, undefined, onIssueReported),
27
+ (0, spawn_agent_js_1.makeSpawnAgentTool)(sid, ctx, apiKey, workrailSid ?? '', spawnCurrentDepth, spawnMaxDepth, runWorkflowFn, schemas, emitter, activeSessionSet),
28
+ (0, signal_coordinator_js_1.makeSignalCoordinatorTool)(sid, emitter, workrailSid),
29
+ ];
30
+ }
@@ -0,0 +1,3 @@
1
+ import type { WorkflowRunResult } from '../types.js';
2
+ import type { FinalizationContext } from './runner-types.js';
3
+ export declare function finalizeSession(result: WorkflowRunResult, ctx: FinalizationContext): Promise<void>;
@@ -0,0 +1,75 @@
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.finalizeSession = finalizeSession;
37
+ const fs = __importStar(require("node:fs/promises"));
38
+ const path = __importStar(require("node:path"));
39
+ const index_js_1 = require("../core/index.js");
40
+ const index_js_2 = require("../io/index.js");
41
+ const _shared_js_1 = require("../tools/_shared.js");
42
+ const assert_never_js_1 = require("../../runtime/assert-never.js");
43
+ async function finalizeSession(result, ctx) {
44
+ const outcome = (0, index_js_1.tagToStatsOutcome)(result._tag);
45
+ const detail = result._tag === 'stuck' ? result.reason
46
+ : result._tag === 'timeout' ? result.reason
47
+ : result._tag === 'error' ? result.message.slice(0, 200)
48
+ : result._tag === 'delivery_failed' ? result.deliveryError.slice(0, 200)
49
+ : result.stopReason;
50
+ ctx.emitter?.emit({
51
+ kind: 'session_completed',
52
+ sessionId: ctx.sessionId,
53
+ workflowId: ctx.workflowId,
54
+ outcome,
55
+ detail,
56
+ ...(0, _shared_js_1.withWorkrailSession)(ctx.workrailSessionId),
57
+ });
58
+ if (ctx.workrailSessionId !== null) {
59
+ ctx.daemonRegistry?.unregister(ctx.workrailSessionId, result._tag === 'success' || result._tag === 'delivery_failed' ? 'completed' : 'failed');
60
+ }
61
+ (0, index_js_2.writeExecutionStats)(ctx.statsDir, ctx.sessionId, ctx.workflowId, ctx.startMs, outcome, ctx.stepAdvanceCount);
62
+ const lifecycle = (0, index_js_1.sidecardLifecycleFor)(result._tag, ctx.branchStrategy);
63
+ switch (lifecycle.kind) {
64
+ case 'delete_now':
65
+ await fs.unlink(path.join(ctx.sessionsDir, `${ctx.sessionId}.json`)).catch(() => { });
66
+ break;
67
+ case 'retain_for_delivery':
68
+ break;
69
+ default:
70
+ (0, assert_never_js_1.assertNever)(lifecycle);
71
+ }
72
+ if (result._tag === 'success' && ctx.branchStrategy !== 'worktree') {
73
+ await fs.unlink(ctx.conversationPath).catch(() => { });
74
+ }
75
+ }
@@ -0,0 +1,8 @@
1
+ export { WORKTREES_DIR } from './runner-types.js';
2
+ export type { PreAgentSession, PreAgentSessionResult, AgentReadySession, SessionOutcome, FinalizationContext, } from './runner-types.js';
3
+ export { getSchemas } from './tool-schemas.js';
4
+ export { constructTools } from './construct-tools.js';
5
+ export { finalizeSession } from './finalize-session.js';
6
+ export { buildPreAgentSession } from './pre-agent-session.js';
7
+ export type { TurnEndSubscriberContext } from './agent-loop-runner.js';
8
+ export { buildTurnEndSubscriber, buildAgentCallbacks, buildAgentReadySession, runAgentLoop, } from './agent-loop-runner.js';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAgentLoop = exports.buildAgentReadySession = exports.buildAgentCallbacks = exports.buildTurnEndSubscriber = exports.buildPreAgentSession = exports.finalizeSession = exports.constructTools = exports.getSchemas = exports.WORKTREES_DIR = void 0;
4
+ var runner_types_js_1 = require("./runner-types.js");
5
+ Object.defineProperty(exports, "WORKTREES_DIR", { enumerable: true, get: function () { return runner_types_js_1.WORKTREES_DIR; } });
6
+ var tool_schemas_js_1 = require("./tool-schemas.js");
7
+ Object.defineProperty(exports, "getSchemas", { enumerable: true, get: function () { return tool_schemas_js_1.getSchemas; } });
8
+ var construct_tools_js_1 = require("./construct-tools.js");
9
+ Object.defineProperty(exports, "constructTools", { enumerable: true, get: function () { return construct_tools_js_1.constructTools; } });
10
+ var finalize_session_js_1 = require("./finalize-session.js");
11
+ Object.defineProperty(exports, "finalizeSession", { enumerable: true, get: function () { return finalize_session_js_1.finalizeSession; } });
12
+ var pre_agent_session_js_1 = require("./pre-agent-session.js");
13
+ Object.defineProperty(exports, "buildPreAgentSession", { enumerable: true, get: function () { return pre_agent_session_js_1.buildPreAgentSession; } });
14
+ var agent_loop_runner_js_1 = require("./agent-loop-runner.js");
15
+ Object.defineProperty(exports, "buildTurnEndSubscriber", { enumerable: true, get: function () { return agent_loop_runner_js_1.buildTurnEndSubscriber; } });
16
+ Object.defineProperty(exports, "buildAgentCallbacks", { enumerable: true, get: function () { return agent_loop_runner_js_1.buildAgentCallbacks; } });
17
+ Object.defineProperty(exports, "buildAgentReadySession", { enumerable: true, get: function () { return agent_loop_runner_js_1.buildAgentReadySession; } });
18
+ Object.defineProperty(exports, "runAgentLoop", { enumerable: true, get: function () { return agent_loop_runner_js_1.runAgentLoop; } });
@@ -0,0 +1,7 @@
1
+ import type { V2ToolContext } from '../../mcp/types.js';
2
+ import type { DaemonRegistry } from '../../v2/infra/in-memory/daemon-registry/index.js';
3
+ import type { DaemonEventEmitter } from '../daemon-events.js';
4
+ import { ActiveSessionSet } from '../active-sessions.js';
5
+ import type { WorkflowTrigger, SessionSource } from '../types.js';
6
+ import type { PreAgentSessionResult } from './runner-types.js';
7
+ export declare function buildPreAgentSession(trigger: WorkflowTrigger, ctx: V2ToolContext, apiKey: string, sessionId: string, startMs: number, statsDir: string, sessionsDir: string, emitter: DaemonEventEmitter | undefined, daemonRegistry: DaemonRegistry | undefined, activeSessionSet: ActiveSessionSet | undefined, source?: SessionSource): Promise<PreAgentSessionResult>;