@exaudeus/workrail 3.32.0 → 3.33.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/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/worktrain-await.js +11 -9
- package/dist/cli/commands/worktrain-daemon-install.d.ts +35 -0
- package/dist/cli/commands/worktrain-daemon-install.js +291 -0
- package/dist/cli/commands/worktrain-daemon.d.ts +31 -0
- package/dist/cli/commands/worktrain-daemon.js +272 -0
- package/dist/cli/commands/worktrain-spawn.js +11 -9
- package/dist/cli-worktrain.js +329 -0
- package/dist/cli.js +1 -22
- package/dist/console/standalone-console.d.ts +28 -0
- package/dist/console/standalone-console.js +142 -0
- package/dist/{console/assets/index-Cb_LO718.js → console-ui/assets/index-BuJFLLfY.js} +1 -1
- package/dist/{console → console-ui}/index.html +1 -1
- package/dist/daemon/agent-loop.d.ts +26 -0
- package/dist/daemon/agent-loop.js +39 -1
- package/dist/daemon/daemon-events.d.ts +47 -1
- package/dist/daemon/workflow-runner.d.ts +3 -2
- package/dist/daemon/workflow-runner.js +205 -41
- package/dist/infrastructure/session/HttpServer.js +133 -34
- package/dist/manifest.json +118 -62
- package/dist/mcp/output-schemas.d.ts +30 -30
- package/dist/mcp/transports/bridge-events.d.ts +4 -0
- package/dist/mcp/transports/fatal-exit.js +4 -0
- package/dist/mcp/transports/http-entry.js +2 -0
- package/dist/mcp/transports/stdio-entry.js +26 -6
- package/dist/mcp/v2/tools.d.ts +4 -4
- package/dist/trigger/adapters/github-poller.d.ts +44 -0
- package/dist/trigger/adapters/github-poller.js +190 -0
- package/dist/trigger/adapters/gitlab-poller.d.ts +27 -0
- package/dist/trigger/adapters/gitlab-poller.js +81 -0
- package/dist/trigger/index.d.ts +4 -1
- package/dist/trigger/index.js +5 -1
- package/dist/trigger/polled-event-store.d.ts +22 -0
- package/dist/trigger/polled-event-store.js +173 -0
- package/dist/trigger/polling-scheduler.d.ts +20 -0
- package/dist/trigger/polling-scheduler.js +249 -0
- package/dist/trigger/trigger-listener.d.ts +3 -0
- package/dist/trigger/trigger-listener.js +47 -3
- package/dist/trigger/trigger-store.js +114 -33
- package/dist/trigger/types.d.ts +17 -1
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +224 -224
- package/dist/v2/durable-core/schemas/session/events.d.ts +42 -42
- package/dist/v2/durable-core/schemas/session/manifest.d.ts +6 -6
- package/dist/v2/durable-core/schemas/session/validation-event.d.ts +2 -2
- package/dist/v2/durable-core/tokens/payloads.d.ts +52 -52
- package/dist/v2/usecases/console-routes.js +3 -3
- package/dist/v2/usecases/console-service.js +133 -9
- package/dist/v2/usecases/console-types.d.ts +7 -0
- package/docs/design/daemon-conversation-logging-plan.md +98 -0
- package/docs/design/daemon-conversation-logging-review.md +55 -0
- package/docs/design/daemon-conversation-logging.md +129 -0
- package/docs/design/github-polling-adapter-design-candidates.md +226 -0
- package/docs/design/github-polling-adapter-design-review-findings.md +131 -0
- package/docs/design/github-polling-adapter-implementation-plan.md +284 -0
- package/docs/design/implementation_plan.md +192 -0
- package/docs/design/workflow-id-validation-at-startup.md +146 -0
- package/docs/design/workflow-id-validation-design-review.md +87 -0
- package/docs/design/workflow-id-validation-implementation-plan.md +185 -0
- package/docs/design/worktrain-system-prompt-report-issue-candidates.md +135 -0
- package/docs/design/worktrain-system-prompt-report-issue-design-review.md +73 -0
- package/docs/ideas/backlog.md +361 -0
- package/package.json +1 -1
- package/workflows/architecture-scalability-audit.json +1 -1
- package/workflows/bug-investigation.agentic.v2.json +3 -3
- package/workflows/coding-task-workflow-agentic.json +32 -32
- package/workflows/coding-task-workflow-agentic.lean.v2.json +1 -1
- package/workflows/coding-task-workflow-agentic.v2.json +7 -7
- package/workflows/mr-review-workflow.agentic.v2.json +21 -12
- package/workflows/personal-learning-materials-creation-branched.json +2 -2
- package/workflows/production-readiness-audit.json +1 -1
- package/workflows/relocation-workflow-us.json +2 -2
- package/workflows/ui-ux-design-workflow.json +14 -14
- package/workflows/workflow-for-workflows.json +3 -3
- package/workflows/workflow-for-workflows.v2.json +2 -2
- package/workflows/wr.discovery.json +1 -1
- /package/dist/{console → console-ui}/assets/index-8dh0Psu-.css +0 -0
|
@@ -43,7 +43,7 @@ class AgentLoop {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
async _runLoop() {
|
|
46
|
-
const { client, modelId, systemPrompt, tools, maxTokens = 8192 } = this._options;
|
|
46
|
+
const { client, modelId, systemPrompt, tools, maxTokens = 8192, callbacks } = this._options;
|
|
47
47
|
while (true) {
|
|
48
48
|
if (this._aborted || this._abortController.signal.aborted) {
|
|
49
49
|
this._appendErrorMessage('aborted');
|
|
@@ -56,6 +56,10 @@ class AgentLoop {
|
|
|
56
56
|
description: t.description,
|
|
57
57
|
input_schema: t.inputSchema,
|
|
58
58
|
}));
|
|
59
|
+
try {
|
|
60
|
+
callbacks?.onLlmTurnStarted?.({ messageCount: apiMessages.length });
|
|
61
|
+
}
|
|
62
|
+
catch { }
|
|
59
63
|
let response;
|
|
60
64
|
try {
|
|
61
65
|
response = await client.messages.create({
|
|
@@ -74,6 +78,20 @@ class AgentLoop {
|
|
|
74
78
|
await this._emitEvent({ type: 'agent_end' });
|
|
75
79
|
return;
|
|
76
80
|
}
|
|
81
|
+
{
|
|
82
|
+
const toolNamesRequested = response.content
|
|
83
|
+
.filter((block) => block.type === 'tool_use')
|
|
84
|
+
.map((block) => block.name);
|
|
85
|
+
try {
|
|
86
|
+
callbacks?.onLlmTurnCompleted?.({
|
|
87
|
+
stopReason: response.stop_reason ?? 'unknown',
|
|
88
|
+
outputTokens: response.usage.output_tokens,
|
|
89
|
+
inputTokens: response.usage.input_tokens,
|
|
90
|
+
toolNamesRequested,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
94
|
+
}
|
|
77
95
|
const stopReason = this._mapStopReason(response.stop_reason);
|
|
78
96
|
const assistantMsg = {
|
|
79
97
|
role: 'assistant',
|
|
@@ -116,6 +134,7 @@ class AgentLoop {
|
|
|
116
134
|
}
|
|
117
135
|
}
|
|
118
136
|
async _executeTools(toolUseBlocks) {
|
|
137
|
+
const { callbacks } = this._options;
|
|
119
138
|
const results = [];
|
|
120
139
|
for (const block of toolUseBlocks) {
|
|
121
140
|
if (this._abortController.signal.aborted) {
|
|
@@ -141,12 +160,23 @@ class AgentLoop {
|
|
|
141
160
|
continue;
|
|
142
161
|
}
|
|
143
162
|
const params = (block.input ?? {});
|
|
163
|
+
const argsSummary = JSON.stringify(params).slice(0, 200);
|
|
164
|
+
try {
|
|
165
|
+
callbacks?.onToolCallStarted?.({ toolName: block.name, argsSummary });
|
|
166
|
+
}
|
|
167
|
+
catch { }
|
|
168
|
+
const toolStartMs = Date.now();
|
|
144
169
|
let result;
|
|
145
170
|
try {
|
|
146
171
|
result = await tool.execute(block.id, params);
|
|
147
172
|
}
|
|
148
173
|
catch (err) {
|
|
174
|
+
const durationMs = Date.now() - toolStartMs;
|
|
149
175
|
const message = err instanceof Error ? err.message : String(err);
|
|
176
|
+
try {
|
|
177
|
+
callbacks?.onToolCallFailed?.({ toolName: block.name, durationMs, errorMessage: message.slice(0, 200) });
|
|
178
|
+
}
|
|
179
|
+
catch { }
|
|
150
180
|
results.push({
|
|
151
181
|
toolCallId: block.id,
|
|
152
182
|
toolName: block.name,
|
|
@@ -155,6 +185,14 @@ class AgentLoop {
|
|
|
155
185
|
});
|
|
156
186
|
continue;
|
|
157
187
|
}
|
|
188
|
+
{
|
|
189
|
+
const durationMs = Date.now() - toolStartMs;
|
|
190
|
+
const resultSummary = (result.content[0]?.text ?? '(no output)').slice(0, 200);
|
|
191
|
+
try {
|
|
192
|
+
callbacks?.onToolCallCompleted?.({ toolName: block.name, durationMs, resultSummary });
|
|
193
|
+
}
|
|
194
|
+
catch { }
|
|
195
|
+
}
|
|
158
196
|
results.push({
|
|
159
197
|
toolCallId: block.id,
|
|
160
198
|
toolName: block.name,
|
|
@@ -18,22 +18,26 @@ export interface SessionStartedEvent {
|
|
|
18
18
|
readonly sessionId: string;
|
|
19
19
|
readonly workflowId: string;
|
|
20
20
|
readonly workspacePath: string;
|
|
21
|
+
readonly workrailSessionId?: string;
|
|
21
22
|
}
|
|
22
23
|
export interface ToolCalledEvent {
|
|
23
24
|
readonly kind: 'tool_called';
|
|
24
25
|
readonly sessionId: string;
|
|
25
26
|
readonly toolName: string;
|
|
26
27
|
readonly summary?: string;
|
|
28
|
+
readonly workrailSessionId?: string;
|
|
27
29
|
}
|
|
28
30
|
export interface ToolErrorEvent {
|
|
29
31
|
readonly kind: 'tool_error';
|
|
30
32
|
readonly sessionId: string;
|
|
31
33
|
readonly toolName: string;
|
|
32
34
|
readonly error: string;
|
|
35
|
+
readonly workrailSessionId?: string;
|
|
33
36
|
}
|
|
34
37
|
export interface StepAdvancedEvent {
|
|
35
38
|
readonly kind: 'step_advanced';
|
|
36
39
|
readonly sessionId: string;
|
|
40
|
+
readonly workrailSessionId?: string;
|
|
37
41
|
}
|
|
38
42
|
export interface SessionCompletedEvent {
|
|
39
43
|
readonly kind: 'session_completed';
|
|
@@ -41,6 +45,7 @@ export interface SessionCompletedEvent {
|
|
|
41
45
|
readonly workflowId: string;
|
|
42
46
|
readonly outcome: 'success' | 'error' | 'timeout';
|
|
43
47
|
readonly detail?: string;
|
|
48
|
+
readonly workrailSessionId?: string;
|
|
44
49
|
}
|
|
45
50
|
export interface DeliveryAttemptedEvent {
|
|
46
51
|
readonly kind: 'delivery_attempted';
|
|
@@ -48,7 +53,48 @@ export interface DeliveryAttemptedEvent {
|
|
|
48
53
|
readonly outcome: 'success' | 'http_error' | 'network_error';
|
|
49
54
|
readonly statusCode?: number;
|
|
50
55
|
}
|
|
51
|
-
export
|
|
56
|
+
export interface IssueReportedEvent {
|
|
57
|
+
readonly kind: 'issue_reported';
|
|
58
|
+
readonly sessionId: string;
|
|
59
|
+
readonly issueKind: 'tool_failure' | 'blocked' | 'unexpected_behavior' | 'needs_human' | 'self_correction';
|
|
60
|
+
readonly severity: 'info' | 'warn' | 'error' | 'fatal';
|
|
61
|
+
readonly summary: string;
|
|
62
|
+
readonly continueToken?: string;
|
|
63
|
+
}
|
|
64
|
+
export interface LlmTurnStartedEvent {
|
|
65
|
+
readonly kind: 'llm_turn_started';
|
|
66
|
+
readonly sessionId: string;
|
|
67
|
+
readonly messageCount: number;
|
|
68
|
+
}
|
|
69
|
+
export interface LlmTurnCompletedEvent {
|
|
70
|
+
readonly kind: 'llm_turn_completed';
|
|
71
|
+
readonly sessionId: string;
|
|
72
|
+
readonly stopReason: string;
|
|
73
|
+
readonly outputTokens: number;
|
|
74
|
+
readonly inputTokens: number;
|
|
75
|
+
readonly toolNamesRequested: readonly string[];
|
|
76
|
+
}
|
|
77
|
+
export interface ToolCallStartedEvent {
|
|
78
|
+
readonly kind: 'tool_call_started';
|
|
79
|
+
readonly sessionId: string;
|
|
80
|
+
readonly toolName: string;
|
|
81
|
+
readonly argsSummary: string;
|
|
82
|
+
}
|
|
83
|
+
export interface ToolCallCompletedEvent {
|
|
84
|
+
readonly kind: 'tool_call_completed';
|
|
85
|
+
readonly sessionId: string;
|
|
86
|
+
readonly toolName: string;
|
|
87
|
+
readonly durationMs: number;
|
|
88
|
+
readonly resultSummary: string;
|
|
89
|
+
}
|
|
90
|
+
export interface ToolCallFailedEvent {
|
|
91
|
+
readonly kind: 'tool_call_failed';
|
|
92
|
+
readonly sessionId: string;
|
|
93
|
+
readonly toolName: string;
|
|
94
|
+
readonly durationMs: number;
|
|
95
|
+
readonly errorMessage: string;
|
|
96
|
+
}
|
|
97
|
+
export type DaemonEvent = DaemonStartedEvent | TriggerFiredEvent | SessionQueuedEvent | SessionStartedEvent | ToolCalledEvent | ToolErrorEvent | StepAdvancedEvent | SessionCompletedEvent | DeliveryAttemptedEvent | IssueReportedEvent | LlmTurnStartedEvent | LlmTurnCompletedEvent | ToolCallStartedEvent | ToolCallCompletedEvent | ToolCallFailedEvent;
|
|
52
98
|
export declare class DaemonEventEmitter {
|
|
53
99
|
private readonly _dir;
|
|
54
100
|
constructor(dirOverride?: string);
|
|
@@ -60,8 +60,9 @@ export declare function readDaemonSessionState(sessionId: string): Promise<{
|
|
|
60
60
|
} | null>;
|
|
61
61
|
export declare function readAllDaemonSessions(sessionsDir?: string): Promise<OrphanedSession[]>;
|
|
62
62
|
export declare function runStartupRecovery(sessionsDir?: string): Promise<void>;
|
|
63
|
-
export declare function makeContinueWorkflowTool(sessionId: string, ctx: V2ToolContext, onAdvance: (nextStepText: string, continueToken: string) => void, onComplete: (notes: string | undefined) => void, schemas: Record<string, any>, _executeContinueWorkflowFn?: typeof executeContinueWorkflow, emitter?: DaemonEventEmitter): AgentTool;
|
|
64
|
-
export declare function makeBashTool(workspacePath: string, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter): AgentTool;
|
|
63
|
+
export declare function makeContinueWorkflowTool(sessionId: string, ctx: V2ToolContext, onAdvance: (nextStepText: string, continueToken: string) => void, onComplete: (notes: string | undefined) => void, schemas: Record<string, any>, _executeContinueWorkflowFn?: typeof executeContinueWorkflow, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
|
|
64
|
+
export declare function makeBashTool(workspacePath: string, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
|
|
65
|
+
export declare function makeReportIssueTool(sessionId: string, emitter?: DaemonEventEmitter, issuesDirOverride?: string): AgentTool;
|
|
65
66
|
export declare function buildSessionRecap(notes: readonly string[]): string;
|
|
66
67
|
export declare function buildSystemPrompt(trigger: WorkflowTrigger, sessionState: string, soulContent: string, workspaceContext: string | null): string;
|
|
67
68
|
export declare function runWorkflow(trigger: WorkflowTrigger, ctx: V2ToolContext, apiKey: string, daemonRegistry?: DaemonRegistry, emitter?: DaemonEventEmitter): Promise<WorkflowRunResult>;
|
|
@@ -42,6 +42,7 @@ exports.readAllDaemonSessions = readAllDaemonSessions;
|
|
|
42
42
|
exports.runStartupRecovery = runStartupRecovery;
|
|
43
43
|
exports.makeContinueWorkflowTool = makeContinueWorkflowTool;
|
|
44
44
|
exports.makeBashTool = makeBashTool;
|
|
45
|
+
exports.makeReportIssueTool = makeReportIssueTool;
|
|
45
46
|
exports.buildSessionRecap = buildSessionRecap;
|
|
46
47
|
exports.buildSystemPrompt = buildSystemPrompt;
|
|
47
48
|
exports.runWorkflow = runWorkflow;
|
|
@@ -338,7 +339,7 @@ function getSchemas() {
|
|
|
338
339
|
};
|
|
339
340
|
return _schemas;
|
|
340
341
|
}
|
|
341
|
-
function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, _executeContinueWorkflowFn = index_js_1.executeContinueWorkflow, emitter) {
|
|
342
|
+
function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, _executeContinueWorkflowFn = index_js_1.executeContinueWorkflow, emitter, workrailSessionId) {
|
|
342
343
|
return {
|
|
343
344
|
name: 'continue_workflow',
|
|
344
345
|
description: 'Advance the WorkRail workflow to the next step. Call this after completing all work ' +
|
|
@@ -347,7 +348,7 @@ function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas
|
|
|
347
348
|
label: 'Continue Workflow',
|
|
348
349
|
execute: async (_toolCallId, params) => {
|
|
349
350
|
console.log(`[WorkflowRunner] Tool: continue_workflow sessionId=${sessionId}`);
|
|
350
|
-
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'continue_workflow', summary: params.intent ?? 'advance' });
|
|
351
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'continue_workflow', summary: params.intent ?? 'advance', ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
351
352
|
const result = await _executeContinueWorkflowFn({
|
|
352
353
|
continueToken: params.continueToken,
|
|
353
354
|
intent: (params.intent ?? 'advance'),
|
|
@@ -426,7 +427,7 @@ function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas
|
|
|
426
427
|
},
|
|
427
428
|
};
|
|
428
429
|
}
|
|
429
|
-
function makeBashTool(workspacePath, schemas, sessionId, emitter) {
|
|
430
|
+
function makeBashTool(workspacePath, schemas, sessionId, emitter, workrailSessionId) {
|
|
430
431
|
return {
|
|
431
432
|
name: 'Bash',
|
|
432
433
|
description: 'Execute a shell command. Throws on failure (non-zero exit with stderr, or exit code 2+). ' +
|
|
@@ -438,12 +439,13 @@ function makeBashTool(workspacePath, schemas, sessionId, emitter) {
|
|
|
438
439
|
execute: async (_toolCallId, params) => {
|
|
439
440
|
console.log(`[WorkflowRunner] Tool: bash "${String(params.command).slice(0, 80)}"`);
|
|
440
441
|
if (sessionId)
|
|
441
|
-
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Bash', summary: String(params.command).slice(0, 80) });
|
|
442
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Bash', summary: String(params.command).slice(0, 80), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
442
443
|
const cwd = params.cwd ?? workspacePath;
|
|
443
444
|
try {
|
|
444
445
|
const { stdout, stderr } = await execAsync(params.command, {
|
|
445
446
|
cwd,
|
|
446
447
|
timeout: BASH_TIMEOUT_MS,
|
|
448
|
+
shell: '/bin/bash',
|
|
447
449
|
});
|
|
448
450
|
const output = [stdout, stderr].filter(Boolean).join('\n');
|
|
449
451
|
return {
|
|
@@ -473,7 +475,7 @@ function makeBashTool(workspacePath, schemas, sessionId, emitter) {
|
|
|
473
475
|
},
|
|
474
476
|
};
|
|
475
477
|
}
|
|
476
|
-
function makeReadTool(schemas, sessionId, emitter) {
|
|
478
|
+
function makeReadTool(schemas, sessionId, emitter, workrailSessionId) {
|
|
477
479
|
return {
|
|
478
480
|
name: 'Read',
|
|
479
481
|
description: 'Read the contents of a file at the given absolute path.',
|
|
@@ -481,7 +483,7 @@ function makeReadTool(schemas, sessionId, emitter) {
|
|
|
481
483
|
label: 'Read',
|
|
482
484
|
execute: async (_toolCallId, params) => {
|
|
483
485
|
if (sessionId)
|
|
484
|
-
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Read', summary: String(params.filePath).slice(0, 80) });
|
|
486
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Read', summary: String(params.filePath).slice(0, 80), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
485
487
|
const content = await fs.readFile(params.filePath, 'utf8');
|
|
486
488
|
return {
|
|
487
489
|
content: [{ type: 'text', text: content }],
|
|
@@ -490,7 +492,7 @@ function makeReadTool(schemas, sessionId, emitter) {
|
|
|
490
492
|
},
|
|
491
493
|
};
|
|
492
494
|
}
|
|
493
|
-
function makeWriteTool(schemas, sessionId, emitter) {
|
|
495
|
+
function makeWriteTool(schemas, sessionId, emitter, workrailSessionId) {
|
|
494
496
|
return {
|
|
495
497
|
name: 'Write',
|
|
496
498
|
description: 'Write content to a file at the given absolute path. Creates parent directories if needed.',
|
|
@@ -498,7 +500,7 @@ function makeWriteTool(schemas, sessionId, emitter) {
|
|
|
498
500
|
label: 'Write',
|
|
499
501
|
execute: async (_toolCallId, params) => {
|
|
500
502
|
if (sessionId)
|
|
501
|
-
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Write', summary: String(params.filePath).slice(0, 80) });
|
|
503
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Write', summary: String(params.filePath).slice(0, 80), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
502
504
|
await fs.mkdir(path.dirname(params.filePath), { recursive: true });
|
|
503
505
|
await fs.writeFile(params.filePath, params.content, 'utf8');
|
|
504
506
|
return {
|
|
@@ -508,6 +510,140 @@ function makeWriteTool(schemas, sessionId, emitter) {
|
|
|
508
510
|
},
|
|
509
511
|
};
|
|
510
512
|
}
|
|
513
|
+
async function appendIssueAsync(issuesDir, sessionId, record) {
|
|
514
|
+
await fs.mkdir(issuesDir, { recursive: true });
|
|
515
|
+
const filePath = path.join(issuesDir, `${sessionId}.jsonl`);
|
|
516
|
+
const line = JSON.stringify({ ...record, ts: Date.now() }) + '\n';
|
|
517
|
+
await fs.appendFile(filePath, line, 'utf8');
|
|
518
|
+
}
|
|
519
|
+
function makeReportIssueTool(sessionId, emitter, issuesDirOverride) {
|
|
520
|
+
const issuesDir = issuesDirOverride ?? path.join(os.homedir(), '.workrail', 'issues');
|
|
521
|
+
return {
|
|
522
|
+
name: 'report_issue',
|
|
523
|
+
description: "Record a structured issue, error, or unexpected behavior. Call this AND continue_workflow (unless fatal). " +
|
|
524
|
+
"Does not stop the session -- it creates a record for the auto-fix coordinator.",
|
|
525
|
+
inputSchema: {
|
|
526
|
+
type: 'object',
|
|
527
|
+
properties: {
|
|
528
|
+
kind: {
|
|
529
|
+
type: 'string',
|
|
530
|
+
enum: ['tool_failure', 'blocked', 'unexpected_behavior', 'needs_human', 'self_correction'],
|
|
531
|
+
description: 'Category of issue being reported.',
|
|
532
|
+
},
|
|
533
|
+
severity: {
|
|
534
|
+
type: 'string',
|
|
535
|
+
enum: ['info', 'warn', 'error', 'fatal'],
|
|
536
|
+
description: 'Severity level. Fatal means the session cannot continue productively.',
|
|
537
|
+
},
|
|
538
|
+
summary: {
|
|
539
|
+
type: 'string',
|
|
540
|
+
description: 'One-line summary of the issue. Max 200 chars.',
|
|
541
|
+
maxLength: 200,
|
|
542
|
+
},
|
|
543
|
+
context: {
|
|
544
|
+
type: 'string',
|
|
545
|
+
description: 'What you were trying to do when this issue occurred.',
|
|
546
|
+
},
|
|
547
|
+
toolName: {
|
|
548
|
+
type: 'string',
|
|
549
|
+
description: 'Name of the tool that failed or behaved unexpectedly, if applicable.',
|
|
550
|
+
},
|
|
551
|
+
command: {
|
|
552
|
+
type: 'string',
|
|
553
|
+
description: 'The shell command or expression that caused the issue, if applicable.',
|
|
554
|
+
},
|
|
555
|
+
suggestedFix: {
|
|
556
|
+
type: 'string',
|
|
557
|
+
description: 'A suggested fix or recovery action for the auto-fix coordinator.',
|
|
558
|
+
},
|
|
559
|
+
continueToken: {
|
|
560
|
+
type: 'string',
|
|
561
|
+
description: 'The current continueToken, so the coordinator can resume this session.',
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
required: ['kind', 'severity', 'summary'],
|
|
565
|
+
additionalProperties: false,
|
|
566
|
+
},
|
|
567
|
+
label: 'report_issue',
|
|
568
|
+
execute: async (_toolCallId, params) => {
|
|
569
|
+
const record = {
|
|
570
|
+
sessionId,
|
|
571
|
+
kind: params.kind,
|
|
572
|
+
severity: params.severity,
|
|
573
|
+
summary: String(params.summary ?? '').slice(0, 200),
|
|
574
|
+
...(params.context !== undefined && { context: String(params.context) }),
|
|
575
|
+
...(params.toolName !== undefined && { toolName: String(params.toolName) }),
|
|
576
|
+
...(params.command !== undefined && { command: String(params.command) }),
|
|
577
|
+
...(params.suggestedFix !== undefined && { suggestedFix: String(params.suggestedFix) }),
|
|
578
|
+
...(params.continueToken !== undefined && { continueToken: String(params.continueToken) }),
|
|
579
|
+
};
|
|
580
|
+
void appendIssueAsync(issuesDir, sessionId, record).catch(() => {
|
|
581
|
+
});
|
|
582
|
+
emitter?.emit({
|
|
583
|
+
kind: 'issue_reported',
|
|
584
|
+
sessionId,
|
|
585
|
+
issueKind: record.kind,
|
|
586
|
+
severity: record.severity,
|
|
587
|
+
summary: record.summary,
|
|
588
|
+
...(record.continueToken !== undefined && { continueToken: record.continueToken }),
|
|
589
|
+
});
|
|
590
|
+
const isFatal = record.severity === 'fatal';
|
|
591
|
+
const message = isFatal
|
|
592
|
+
? `FATAL issue recorded. Call continue_workflow with notes explaining the blocker, then the session will end.`
|
|
593
|
+
: `Issue recorded (severity=${record.severity}). Continue with your work unless this is fatal.`;
|
|
594
|
+
return {
|
|
595
|
+
content: [{ type: 'text', text: message }],
|
|
596
|
+
details: { sessionId, kind: record.kind, severity: record.severity },
|
|
597
|
+
};
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
const BASE_SYSTEM_PROMPT = `\
|
|
602
|
+
You are WorkRail Auto, an autonomous agent that executes workflows step by step. You are running unattended -- there is no user watching. Your entire job is to faithfully complete the current workflow.
|
|
603
|
+
|
|
604
|
+
## What you are
|
|
605
|
+
You are highly capable. You handle ambitious, multi-step tasks that require real codebase understanding. You don't hedge, ask for permission, or stop to check in. You work.
|
|
606
|
+
|
|
607
|
+
## Your oracle (consult in this order when uncertain)
|
|
608
|
+
1. The daemon soul rules (## Agent Rules and Philosophy below)
|
|
609
|
+
2. AGENTS.md / CLAUDE.md in the workspace (injected below under Workspace Context)
|
|
610
|
+
3. The current workflow step's prompt and guidance
|
|
611
|
+
4. Local code patterns in the relevant module (grep the directory, not the whole repo)
|
|
612
|
+
5. Industry best practices -- only when nothing above applies
|
|
613
|
+
|
|
614
|
+
## Self-directed reasoning
|
|
615
|
+
Ask yourself questions to clarify your approach, then answer them yourself using tools before acting. Never wait for a human to answer -- you are the oracle.
|
|
616
|
+
|
|
617
|
+
Bad pattern: "I'll analyze both layers." (no justification)
|
|
618
|
+
Good pattern: "Question: Should I check the middleware? Answer: The workflow step says 'trace the full call chain', and the AGENTS.md says the entry point is in the middleware layer. Yes, start there."
|
|
619
|
+
|
|
620
|
+
## Your tools
|
|
621
|
+
- \`continue_workflow\`: Advance to the next step. Call this after completing each step's work. Always include your notes in notesMarkdown and round-trip the continueToken exactly.
|
|
622
|
+
- \`Bash\`: Run shell commands. Use for building, testing, running scripts.
|
|
623
|
+
- \`Read\`: Read files.
|
|
624
|
+
- \`Write\`: Write files.
|
|
625
|
+
- \`report_issue\`: Record a structured issue, error, or unexpected behavior. Call this AND continue_workflow (unless fatal). Does not stop the session -- it creates a record for the auto-fix coordinator.
|
|
626
|
+
|
|
627
|
+
## Execution contract
|
|
628
|
+
1. Read the step carefully. Do ALL the work the step asks for.
|
|
629
|
+
2. Call \`continue_workflow\` with your notes. Include the continueToken exactly.
|
|
630
|
+
3. Repeat until the workflow reports it is complete.
|
|
631
|
+
4. Do NOT skip steps. Do NOT call \`continue_workflow\` without completing the step's work.
|
|
632
|
+
|
|
633
|
+
## The workflow is the contract
|
|
634
|
+
Every step must be fully completed before you call continue_workflow. The workflow step prompt is the specification of what 'done' means -- not a suggestion. Don't advance until the work is actually done.
|
|
635
|
+
|
|
636
|
+
Your cognitive mode changes per step: some steps make you a researcher, others a reviewer, others an implementer. Adopt the mode the step describes. Don't bring your own agenda.
|
|
637
|
+
|
|
638
|
+
## Silent failure is the worst outcome
|
|
639
|
+
If something goes wrong: call report_issue, then continue unless severity is 'fatal'. Do NOT silently retry forever, work around failures without noting them, or pretend things worked. The issue record is how the system learns and self-heals.
|
|
640
|
+
|
|
641
|
+
## Tools are your hands, not your voice
|
|
642
|
+
Don't narrate what you're about to do. Use the tool and report what you found. Token efficiency matters -- you have a wall-clock timeout.
|
|
643
|
+
|
|
644
|
+
## You don't have a user. You have a workflow and a soul.
|
|
645
|
+
If you're unsure, consult the oracle above. If nothing answers the question, make a reasoned decision, call report_issue with kind='self_correction' to document it, and continue.\
|
|
646
|
+
`;
|
|
511
647
|
function buildSessionRecap(notes) {
|
|
512
648
|
if (notes.length === 0)
|
|
513
649
|
return '';
|
|
@@ -518,20 +654,7 @@ function buildSessionRecap(notes) {
|
|
|
518
654
|
}
|
|
519
655
|
function buildSystemPrompt(trigger, sessionState, soulContent, workspaceContext) {
|
|
520
656
|
const lines = [
|
|
521
|
-
|
|
522
|
-
'',
|
|
523
|
-
'## Your tools',
|
|
524
|
-
'- `continue_workflow`: Advance to the next step. Call this after completing each step\'s work.',
|
|
525
|
-
' Always include your notes in notesMarkdown and round-trip the continueToken exactly.',
|
|
526
|
-
'- `Bash`: Run shell commands. Use for building, testing, running scripts.',
|
|
527
|
-
'- `Read`: Read files.',
|
|
528
|
-
'- `Write`: Write files.',
|
|
529
|
-
'',
|
|
530
|
-
'## Execution contract',
|
|
531
|
-
'1. Read the step carefully. Do ALL the work the step asks for.',
|
|
532
|
-
'2. Call `continue_workflow` with your notes. Include the continueToken exactly.',
|
|
533
|
-
'3. Repeat until the workflow reports it is complete.',
|
|
534
|
-
'4. Do NOT skip steps. Do NOT call `continue_workflow` without completing the step\'s work.',
|
|
657
|
+
BASE_SYSTEM_PROMPT,
|
|
535
658
|
'',
|
|
536
659
|
`<workrail_session_state>${sessionState}</workrail_session_state>`,
|
|
537
660
|
'',
|
|
@@ -570,13 +693,12 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
570
693
|
workflowId: trigger.workflowId,
|
|
571
694
|
workspacePath: trigger.workspacePath,
|
|
572
695
|
});
|
|
573
|
-
|
|
696
|
+
let workrailSessionId = null;
|
|
574
697
|
let agentClient;
|
|
575
698
|
let modelId;
|
|
576
699
|
if (trigger.agentConfig?.model) {
|
|
577
700
|
const slashIdx = trigger.agentConfig.model.indexOf('/');
|
|
578
701
|
if (slashIdx === -1) {
|
|
579
|
-
daemonRegistry?.unregister(sessionId, 'failed');
|
|
580
702
|
return {
|
|
581
703
|
_tag: 'error',
|
|
582
704
|
workflowId: trigger.workflowId,
|
|
@@ -604,8 +726,9 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
604
726
|
let lastStepNotes;
|
|
605
727
|
const onAdvance = (stepText, _continueToken) => {
|
|
606
728
|
pendingSteerText = stepText;
|
|
607
|
-
|
|
608
|
-
|
|
729
|
+
if (workrailSessionId !== null)
|
|
730
|
+
daemonRegistry?.heartbeat(workrailSessionId);
|
|
731
|
+
emitter?.emit({ kind: 'step_advanced', sessionId, ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
609
732
|
};
|
|
610
733
|
const onComplete = (notes) => {
|
|
611
734
|
isComplete = true;
|
|
@@ -616,9 +739,8 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
616
739
|
firstStep = trigger._preAllocatedStartResponse;
|
|
617
740
|
}
|
|
618
741
|
else {
|
|
619
|
-
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId: trigger.workflowId, workspacePath: trigger.workspacePath, goal: trigger.goal }, ctx, { is_autonomous: 'true' });
|
|
742
|
+
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId: trigger.workflowId, workspacePath: trigger.workspacePath, goal: trigger.goal }, ctx, { is_autonomous: 'true', workspacePath: trigger.workspacePath });
|
|
620
743
|
if (startResult.isErr()) {
|
|
621
|
-
daemonRegistry?.unregister(sessionId, 'failed');
|
|
622
744
|
return {
|
|
623
745
|
_tag: 'error',
|
|
624
746
|
workflowId: trigger.workflowId,
|
|
@@ -630,21 +752,35 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
630
752
|
}
|
|
631
753
|
const startContinueToken = firstStep.continueToken ?? '';
|
|
632
754
|
const startCheckpointToken = firstStep.checkpointToken ?? null;
|
|
755
|
+
if (startContinueToken) {
|
|
756
|
+
const decoded = await (0, v2_token_ops_js_1.parseContinueTokenOrFail)(startContinueToken, ctx.v2.tokenCodecPorts, ctx.v2.tokenAliasStore);
|
|
757
|
+
if (decoded.isOk()) {
|
|
758
|
+
workrailSessionId = decoded.value.sessionId;
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
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}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (workrailSessionId !== null) {
|
|
765
|
+
daemonRegistry?.register(workrailSessionId, trigger.workflowId);
|
|
766
|
+
}
|
|
633
767
|
if (startContinueToken) {
|
|
634
768
|
await persistTokens(sessionId, startContinueToken, startCheckpointToken);
|
|
635
769
|
}
|
|
636
770
|
if (firstStep.isComplete) {
|
|
637
771
|
await fs.unlink(path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`)).catch(() => { });
|
|
638
|
-
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: 'stop' });
|
|
639
|
-
|
|
772
|
+
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: 'stop', ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
773
|
+
if (workrailSessionId !== null)
|
|
774
|
+
daemonRegistry?.unregister(workrailSessionId, 'completed');
|
|
640
775
|
return { _tag: 'success', workflowId: trigger.workflowId, stopReason: 'stop' };
|
|
641
776
|
}
|
|
642
777
|
const schemas = getSchemas();
|
|
643
778
|
const tools = [
|
|
644
|
-
makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, index_js_1.executeContinueWorkflow, emitter),
|
|
645
|
-
makeBashTool(trigger.workspacePath, schemas, sessionId, emitter),
|
|
646
|
-
makeReadTool(schemas, sessionId, emitter),
|
|
647
|
-
makeWriteTool(schemas, sessionId, emitter),
|
|
779
|
+
makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, index_js_1.executeContinueWorkflow, emitter, workrailSessionId),
|
|
780
|
+
makeBashTool(trigger.workspacePath, schemas, sessionId, emitter, workrailSessionId),
|
|
781
|
+
makeReadTool(schemas, sessionId, emitter, workrailSessionId),
|
|
782
|
+
makeWriteTool(schemas, sessionId, emitter, workrailSessionId),
|
|
783
|
+
makeReportIssueTool(sessionId, emitter),
|
|
648
784
|
];
|
|
649
785
|
const [soulContent, workspaceContext, sessionNotes] = await Promise.all([
|
|
650
786
|
loadDaemonSoul(trigger.soulFile),
|
|
@@ -659,12 +795,37 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
659
795
|
`\n\ncontinueToken: ${startContinueToken}` +
|
|
660
796
|
contextJson +
|
|
661
797
|
'\n\nComplete all step work, then call continue_workflow with your notes to begin.';
|
|
798
|
+
const agentCallbacks = {
|
|
799
|
+
onLlmTurnStarted: ({ messageCount }) => {
|
|
800
|
+
emitter?.emit({ kind: 'llm_turn_started', sessionId, messageCount });
|
|
801
|
+
},
|
|
802
|
+
onLlmTurnCompleted: ({ stopReason, outputTokens, inputTokens, toolNamesRequested }) => {
|
|
803
|
+
emitter?.emit({
|
|
804
|
+
kind: 'llm_turn_completed',
|
|
805
|
+
sessionId,
|
|
806
|
+
stopReason,
|
|
807
|
+
outputTokens,
|
|
808
|
+
inputTokens,
|
|
809
|
+
toolNamesRequested,
|
|
810
|
+
});
|
|
811
|
+
},
|
|
812
|
+
onToolCallStarted: ({ toolName, argsSummary }) => {
|
|
813
|
+
emitter?.emit({ kind: 'tool_call_started', sessionId, toolName, argsSummary });
|
|
814
|
+
},
|
|
815
|
+
onToolCallCompleted: ({ toolName, durationMs, resultSummary }) => {
|
|
816
|
+
emitter?.emit({ kind: 'tool_call_completed', sessionId, toolName, durationMs, resultSummary });
|
|
817
|
+
},
|
|
818
|
+
onToolCallFailed: ({ toolName, durationMs, errorMessage }) => {
|
|
819
|
+
emitter?.emit({ kind: 'tool_call_failed', sessionId, toolName, durationMs, errorMessage });
|
|
820
|
+
},
|
|
821
|
+
};
|
|
662
822
|
const agent = new agent_loop_js_1.AgentLoop({
|
|
663
823
|
systemPrompt: buildSystemPrompt(trigger, sessionState, soulContent, workspaceContext),
|
|
664
824
|
modelId,
|
|
665
825
|
tools,
|
|
666
826
|
client: agentClient,
|
|
667
827
|
toolExecution: 'sequential',
|
|
828
|
+
callbacks: agentCallbacks,
|
|
668
829
|
});
|
|
669
830
|
const sessionTimeoutMs = (trigger.agentConfig?.maxSessionMinutes ?? DEFAULT_SESSION_TIMEOUT_MINUTES) * 60 * 1000;
|
|
670
831
|
const maxTurns = trigger.agentConfig?.maxTurns ?? 0;
|
|
@@ -676,7 +837,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
676
837
|
for (const toolResult of event.toolResults) {
|
|
677
838
|
if (toolResult.isError) {
|
|
678
839
|
const errorText = toolResult.result?.content[0]?.text ?? 'tool error';
|
|
679
|
-
emitter?.emit({ kind: 'tool_error', sessionId, toolName: toolResult.toolName, error: errorText.slice(0, 200) });
|
|
840
|
+
emitter?.emit({ kind: 'tool_error', sessionId, toolName: toolResult.toolName, error: errorText.slice(0, 200), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
680
841
|
}
|
|
681
842
|
}
|
|
682
843
|
turnCount++;
|
|
@@ -732,8 +893,9 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
732
893
|
console.log(`[WorkflowRunner] Agent loop ended: sessionId=${sessionId} stopReason=${stopReason}${errorMessage ? ` error=${errorMessage.slice(0, 120)}` : ''}`);
|
|
733
894
|
}
|
|
734
895
|
if (timeoutReason !== null) {
|
|
735
|
-
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'timeout', detail: timeoutReason });
|
|
736
|
-
|
|
896
|
+
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'timeout', detail: timeoutReason, ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
897
|
+
if (workrailSessionId !== null)
|
|
898
|
+
daemonRegistry?.unregister(workrailSessionId, 'failed');
|
|
737
899
|
const limitDescription = timeoutReason === 'wall_clock'
|
|
738
900
|
? `${trigger.agentConfig?.maxSessionMinutes ?? DEFAULT_SESSION_TIMEOUT_MINUTES} minutes`
|
|
739
901
|
: `${trigger.agentConfig?.maxTurns} turns`;
|
|
@@ -747,8 +909,9 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
747
909
|
}
|
|
748
910
|
if (stopReason === 'error' || errorMessage) {
|
|
749
911
|
const errMsg = errorMessage ?? 'Agent stopped with error reason';
|
|
750
|
-
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'error', detail: errMsg.slice(0, 200) });
|
|
751
|
-
|
|
912
|
+
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'error', detail: errMsg.slice(0, 200), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
913
|
+
if (workrailSessionId !== null)
|
|
914
|
+
daemonRegistry?.unregister(workrailSessionId, 'failed');
|
|
752
915
|
const stuckMarker = `\n\nWORKTRAIN_STUCK: ${JSON.stringify({
|
|
753
916
|
reason: 'session_error',
|
|
754
917
|
error: errMsg.slice(0, 500),
|
|
@@ -765,8 +928,9 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
765
928
|
}
|
|
766
929
|
await fs.unlink(path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`)).catch(() => {
|
|
767
930
|
});
|
|
768
|
-
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: stopReason });
|
|
769
|
-
|
|
931
|
+
emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: stopReason, ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
932
|
+
if (workrailSessionId !== null)
|
|
933
|
+
daemonRegistry?.unregister(workrailSessionId, 'completed');
|
|
770
934
|
return {
|
|
771
935
|
_tag: 'success',
|
|
772
936
|
workflowId: trigger.workflowId,
|