@exaudeus/workrail 3.31.1 → 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 +4 -22
- package/dist/console/standalone-console.d.ts +28 -0
- package/dist/console/standalone-console.js +142 -0
- package/dist/{console/assets/index-6H9DeFxj.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 +53 -2
- package/dist/daemon/daemon-events.d.ts +103 -0
- package/dist/daemon/daemon-events.js +56 -0
- package/dist/daemon/workflow-runner.d.ts +6 -3
- package/dist/daemon/workflow-runner.js +229 -33
- package/dist/infrastructure/session/HttpServer.js +133 -34
- package/dist/manifest.json +134 -70
- 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/delivery-client.d.ts +2 -1
- package/dist/trigger/delivery-client.js +4 -1
- 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 +5 -0
- package/dist/trigger/trigger-listener.js +53 -4
- package/dist/trigger/trigger-router.d.ts +4 -2
- package/dist/trigger/trigger-router.js +7 -4
- 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 +465 -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
|
@@ -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) {
|
|
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,6 +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}`);
|
|
351
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'continue_workflow', summary: params.intent ?? 'advance', ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
350
352
|
const result = await _executeContinueWorkflowFn({
|
|
351
353
|
continueToken: params.continueToken,
|
|
352
354
|
intent: (params.intent ?? 'advance'),
|
|
@@ -425,7 +427,7 @@ function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas
|
|
|
425
427
|
},
|
|
426
428
|
};
|
|
427
429
|
}
|
|
428
|
-
function makeBashTool(workspacePath, schemas) {
|
|
430
|
+
function makeBashTool(workspacePath, schemas, sessionId, emitter, workrailSessionId) {
|
|
429
431
|
return {
|
|
430
432
|
name: 'Bash',
|
|
431
433
|
description: 'Execute a shell command. Throws on failure (non-zero exit with stderr, or exit code 2+). ' +
|
|
@@ -436,11 +438,14 @@ function makeBashTool(workspacePath, schemas) {
|
|
|
436
438
|
label: 'Bash',
|
|
437
439
|
execute: async (_toolCallId, params) => {
|
|
438
440
|
console.log(`[WorkflowRunner] Tool: bash "${String(params.command).slice(0, 80)}"`);
|
|
441
|
+
if (sessionId)
|
|
442
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Bash', summary: String(params.command).slice(0, 80), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
439
443
|
const cwd = params.cwd ?? workspacePath;
|
|
440
444
|
try {
|
|
441
445
|
const { stdout, stderr } = await execAsync(params.command, {
|
|
442
446
|
cwd,
|
|
443
447
|
timeout: BASH_TIMEOUT_MS,
|
|
448
|
+
shell: '/bin/bash',
|
|
444
449
|
});
|
|
445
450
|
const output = [stdout, stderr].filter(Boolean).join('\n');
|
|
446
451
|
return {
|
|
@@ -470,13 +475,15 @@ function makeBashTool(workspacePath, schemas) {
|
|
|
470
475
|
},
|
|
471
476
|
};
|
|
472
477
|
}
|
|
473
|
-
function makeReadTool(schemas) {
|
|
478
|
+
function makeReadTool(schemas, sessionId, emitter, workrailSessionId) {
|
|
474
479
|
return {
|
|
475
480
|
name: 'Read',
|
|
476
481
|
description: 'Read the contents of a file at the given absolute path.',
|
|
477
482
|
inputSchema: schemas['ReadParams'],
|
|
478
483
|
label: 'Read',
|
|
479
484
|
execute: async (_toolCallId, params) => {
|
|
485
|
+
if (sessionId)
|
|
486
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Read', summary: String(params.filePath).slice(0, 80), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
480
487
|
const content = await fs.readFile(params.filePath, 'utf8');
|
|
481
488
|
return {
|
|
482
489
|
content: [{ type: 'text', text: content }],
|
|
@@ -485,13 +492,15 @@ function makeReadTool(schemas) {
|
|
|
485
492
|
},
|
|
486
493
|
};
|
|
487
494
|
}
|
|
488
|
-
function makeWriteTool(schemas) {
|
|
495
|
+
function makeWriteTool(schemas, sessionId, emitter, workrailSessionId) {
|
|
489
496
|
return {
|
|
490
497
|
name: 'Write',
|
|
491
498
|
description: 'Write content to a file at the given absolute path. Creates parent directories if needed.',
|
|
492
499
|
inputSchema: schemas['WriteParams'],
|
|
493
500
|
label: 'Write',
|
|
494
501
|
execute: async (_toolCallId, params) => {
|
|
502
|
+
if (sessionId)
|
|
503
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Write', summary: String(params.filePath).slice(0, 80), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
495
504
|
await fs.mkdir(path.dirname(params.filePath), { recursive: true });
|
|
496
505
|
await fs.writeFile(params.filePath, params.content, 'utf8');
|
|
497
506
|
return {
|
|
@@ -501,6 +510,140 @@ function makeWriteTool(schemas) {
|
|
|
501
510
|
},
|
|
502
511
|
};
|
|
503
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
|
+
`;
|
|
504
647
|
function buildSessionRecap(notes) {
|
|
505
648
|
if (notes.length === 0)
|
|
506
649
|
return '';
|
|
@@ -511,20 +654,7 @@ function buildSessionRecap(notes) {
|
|
|
511
654
|
}
|
|
512
655
|
function buildSystemPrompt(trigger, sessionState, soulContent, workspaceContext) {
|
|
513
656
|
const lines = [
|
|
514
|
-
|
|
515
|
-
'',
|
|
516
|
-
'## Your tools',
|
|
517
|
-
'- `continue_workflow`: Advance to the next step. Call this after completing each step\'s work.',
|
|
518
|
-
' Always include your notes in notesMarkdown and round-trip the continueToken exactly.',
|
|
519
|
-
'- `Bash`: Run shell commands. Use for building, testing, running scripts.',
|
|
520
|
-
'- `Read`: Read files.',
|
|
521
|
-
'- `Write`: Write files.',
|
|
522
|
-
'',
|
|
523
|
-
'## Execution contract',
|
|
524
|
-
'1. Read the step carefully. Do ALL the work the step asks for.',
|
|
525
|
-
'2. Call `continue_workflow` with your notes. Include the continueToken exactly.',
|
|
526
|
-
'3. Repeat until the workflow reports it is complete.',
|
|
527
|
-
'4. Do NOT skip steps. Do NOT call `continue_workflow` without completing the step\'s work.',
|
|
657
|
+
BASE_SYSTEM_PROMPT,
|
|
528
658
|
'',
|
|
529
659
|
`<workrail_session_state>${sessionState}</workrail_session_state>`,
|
|
530
660
|
'',
|
|
@@ -554,16 +684,21 @@ function buildUserMessage(text) {
|
|
|
554
684
|
timestamp: Date.now(),
|
|
555
685
|
};
|
|
556
686
|
}
|
|
557
|
-
async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
|
|
687
|
+
async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
558
688
|
const sessionId = (0, node_crypto_1.randomUUID)();
|
|
559
689
|
console.log(`[WorkflowRunner] Session started: sessionId=${sessionId} workflowId=${trigger.workflowId}`);
|
|
560
|
-
|
|
690
|
+
emitter?.emit({
|
|
691
|
+
kind: 'session_started',
|
|
692
|
+
sessionId,
|
|
693
|
+
workflowId: trigger.workflowId,
|
|
694
|
+
workspacePath: trigger.workspacePath,
|
|
695
|
+
});
|
|
696
|
+
let workrailSessionId = null;
|
|
561
697
|
let agentClient;
|
|
562
698
|
let modelId;
|
|
563
699
|
if (trigger.agentConfig?.model) {
|
|
564
700
|
const slashIdx = trigger.agentConfig.model.indexOf('/');
|
|
565
701
|
if (slashIdx === -1) {
|
|
566
|
-
daemonRegistry?.unregister(sessionId, 'failed');
|
|
567
702
|
return {
|
|
568
703
|
_tag: 'error',
|
|
569
704
|
workflowId: trigger.workflowId,
|
|
@@ -591,7 +726,9 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
|
|
|
591
726
|
let lastStepNotes;
|
|
592
727
|
const onAdvance = (stepText, _continueToken) => {
|
|
593
728
|
pendingSteerText = stepText;
|
|
594
|
-
|
|
729
|
+
if (workrailSessionId !== null)
|
|
730
|
+
daemonRegistry?.heartbeat(workrailSessionId);
|
|
731
|
+
emitter?.emit({ kind: 'step_advanced', sessionId, ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
595
732
|
};
|
|
596
733
|
const onComplete = (notes) => {
|
|
597
734
|
isComplete = true;
|
|
@@ -602,9 +739,8 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
|
|
|
602
739
|
firstStep = trigger._preAllocatedStartResponse;
|
|
603
740
|
}
|
|
604
741
|
else {
|
|
605
|
-
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 });
|
|
606
743
|
if (startResult.isErr()) {
|
|
607
|
-
daemonRegistry?.unregister(sessionId, 'failed');
|
|
608
744
|
return {
|
|
609
745
|
_tag: 'error',
|
|
610
746
|
workflowId: trigger.workflowId,
|
|
@@ -616,20 +752,35 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
|
|
|
616
752
|
}
|
|
617
753
|
const startContinueToken = firstStep.continueToken ?? '';
|
|
618
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
|
+
}
|
|
619
767
|
if (startContinueToken) {
|
|
620
768
|
await persistTokens(sessionId, startContinueToken, startCheckpointToken);
|
|
621
769
|
}
|
|
622
770
|
if (firstStep.isComplete) {
|
|
623
771
|
await fs.unlink(path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`)).catch(() => { });
|
|
624
|
-
|
|
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');
|
|
625
775
|
return { _tag: 'success', workflowId: trigger.workflowId, stopReason: 'stop' };
|
|
626
776
|
}
|
|
627
777
|
const schemas = getSchemas();
|
|
628
778
|
const tools = [
|
|
629
|
-
makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas),
|
|
630
|
-
makeBashTool(trigger.workspacePath, schemas),
|
|
631
|
-
makeReadTool(schemas),
|
|
632
|
-
makeWriteTool(schemas),
|
|
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),
|
|
633
784
|
];
|
|
634
785
|
const [soulContent, workspaceContext, sessionNotes] = await Promise.all([
|
|
635
786
|
loadDaemonSoul(trigger.soulFile),
|
|
@@ -644,12 +795,37 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
|
|
|
644
795
|
`\n\ncontinueToken: ${startContinueToken}` +
|
|
645
796
|
contextJson +
|
|
646
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
|
+
};
|
|
647
822
|
const agent = new agent_loop_js_1.AgentLoop({
|
|
648
823
|
systemPrompt: buildSystemPrompt(trigger, sessionState, soulContent, workspaceContext),
|
|
649
824
|
modelId,
|
|
650
825
|
tools,
|
|
651
826
|
client: agentClient,
|
|
652
827
|
toolExecution: 'sequential',
|
|
828
|
+
callbacks: agentCallbacks,
|
|
653
829
|
});
|
|
654
830
|
const sessionTimeoutMs = (trigger.agentConfig?.maxSessionMinutes ?? DEFAULT_SESSION_TIMEOUT_MINUTES) * 60 * 1000;
|
|
655
831
|
const maxTurns = trigger.agentConfig?.maxTurns ?? 0;
|
|
@@ -658,6 +834,12 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
|
|
|
658
834
|
const unsubscribe = agent.subscribe(async (event) => {
|
|
659
835
|
if (event.type !== 'turn_end')
|
|
660
836
|
return;
|
|
837
|
+
for (const toolResult of event.toolResults) {
|
|
838
|
+
if (toolResult.isError) {
|
|
839
|
+
const errorText = toolResult.result?.content[0]?.text ?? 'tool error';
|
|
840
|
+
emitter?.emit({ kind: 'tool_error', sessionId, toolName: toolResult.toolName, error: errorText.slice(0, 200), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
|
|
841
|
+
}
|
|
842
|
+
}
|
|
661
843
|
turnCount++;
|
|
662
844
|
if (maxTurns > 0 && turnCount >= maxTurns && timeoutReason === null) {
|
|
663
845
|
timeoutReason = 'max_turns';
|
|
@@ -711,7 +893,9 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
|
|
|
711
893
|
console.log(`[WorkflowRunner] Agent loop ended: sessionId=${sessionId} stopReason=${stopReason}${errorMessage ? ` error=${errorMessage.slice(0, 120)}` : ''}`);
|
|
712
894
|
}
|
|
713
895
|
if (timeoutReason !== null) {
|
|
714
|
-
|
|
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');
|
|
715
899
|
const limitDescription = timeoutReason === 'wall_clock'
|
|
716
900
|
? `${trigger.agentConfig?.maxSessionMinutes ?? DEFAULT_SESSION_TIMEOUT_MINUTES} minutes`
|
|
717
901
|
: `${trigger.agentConfig?.maxTurns} turns`;
|
|
@@ -724,17 +908,29 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
|
|
|
724
908
|
};
|
|
725
909
|
}
|
|
726
910
|
if (stopReason === 'error' || errorMessage) {
|
|
727
|
-
|
|
911
|
+
const errMsg = errorMessage ?? 'Agent stopped with error reason';
|
|
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');
|
|
915
|
+
const stuckMarker = `\n\nWORKTRAIN_STUCK: ${JSON.stringify({
|
|
916
|
+
reason: 'session_error',
|
|
917
|
+
error: errMsg.slice(0, 500),
|
|
918
|
+
workflowId: trigger.workflowId,
|
|
919
|
+
sessionId,
|
|
920
|
+
})}`;
|
|
728
921
|
return {
|
|
729
922
|
_tag: 'error',
|
|
730
923
|
workflowId: trigger.workflowId,
|
|
731
|
-
message:
|
|
924
|
+
message: errMsg,
|
|
732
925
|
stopReason,
|
|
926
|
+
lastStepNotes: stuckMarker,
|
|
733
927
|
};
|
|
734
928
|
}
|
|
735
929
|
await fs.unlink(path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`)).catch(() => {
|
|
736
930
|
});
|
|
737
|
-
|
|
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');
|
|
738
934
|
return {
|
|
739
935
|
_tag: 'success',
|
|
740
936
|
workflowId: trigger.workflowId,
|