@exaudeus/workrail 3.72.0 → 3.72.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 (42) hide show
  1. package/dist/cli-worktrain.js +4 -6
  2. package/dist/console-ui/assets/{index-CTza1zb5.js → index-Yj9NHqbR.js} +1 -1
  3. package/dist/console-ui/index.html +1 -1
  4. package/dist/daemon/active-sessions.d.ts +17 -0
  5. package/dist/daemon/active-sessions.js +55 -0
  6. package/dist/daemon/context-loader.d.ts +32 -0
  7. package/dist/daemon/context-loader.js +34 -0
  8. package/dist/daemon/session-scope.d.ts +3 -2
  9. package/dist/daemon/tools/_shared.d.ts +38 -0
  10. package/dist/daemon/tools/_shared.js +101 -0
  11. package/dist/daemon/tools/bash.d.ts +3 -0
  12. package/dist/daemon/tools/bash.js +57 -0
  13. package/dist/daemon/tools/continue-workflow.d.ts +6 -0
  14. package/dist/daemon/tools/continue-workflow.js +208 -0
  15. package/dist/daemon/tools/file-tools.d.ts +6 -0
  16. package/dist/daemon/tools/file-tools.js +195 -0
  17. package/dist/daemon/tools/glob-grep.d.ts +4 -0
  18. package/dist/daemon/tools/glob-grep.js +172 -0
  19. package/dist/daemon/tools/report-issue.d.ts +3 -0
  20. package/dist/daemon/tools/report-issue.js +129 -0
  21. package/dist/daemon/tools/signal-coordinator.d.ts +4 -0
  22. package/dist/daemon/tools/signal-coordinator.js +105 -0
  23. package/dist/daemon/tools/spawn-agent.d.ts +6 -0
  24. package/dist/daemon/tools/spawn-agent.js +135 -0
  25. package/dist/daemon/workflow-runner.d.ts +54 -29
  26. package/dist/daemon/workflow-runner.js +156 -980
  27. package/dist/infrastructure/storage/workflow-resolution.js +5 -6
  28. package/dist/manifest.json +131 -27
  29. package/dist/mcp/handlers/shared/request-workflow-reader.js +14 -0
  30. package/dist/trigger/coordinator-deps.d.ts +15 -0
  31. package/dist/trigger/coordinator-deps.js +322 -0
  32. package/dist/trigger/delivery-pipeline.d.ts +18 -0
  33. package/dist/trigger/delivery-pipeline.js +148 -0
  34. package/dist/trigger/dispatch-deduplicator.d.ts +6 -0
  35. package/dist/trigger/dispatch-deduplicator.js +24 -0
  36. package/dist/trigger/trigger-listener.d.ts +2 -3
  37. package/dist/trigger/trigger-listener.js +9 -276
  38. package/dist/trigger/trigger-router.d.ts +8 -7
  39. package/dist/trigger/trigger-router.js +19 -97
  40. package/dist/v2/usecases/console-routes.js +10 -2
  41. package/docs/ideas/backlog.md +82 -48
  42. package/package.json +1 -1
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ActiveSessionSet = void 0;
4
+ class SessionHandleImpl {
5
+ constructor(sessionId, onSteer, set) {
6
+ this._agent = null;
7
+ this.sessionId = sessionId;
8
+ this._onSteer = onSteer;
9
+ this._set = set;
10
+ }
11
+ steer(text) {
12
+ this._onSteer(text);
13
+ }
14
+ setAgent(agent) {
15
+ if (this._agent === null) {
16
+ this._agent = agent;
17
+ }
18
+ }
19
+ abort() {
20
+ if (this._agent !== null) {
21
+ this._agent.abort();
22
+ }
23
+ }
24
+ dispose() {
25
+ this._set._remove(this.sessionId);
26
+ }
27
+ }
28
+ class ActiveSessionSet {
29
+ constructor() {
30
+ this._handles = new Map();
31
+ }
32
+ register(sessionId, onSteer) {
33
+ const handle = new SessionHandleImpl(sessionId, onSteer, this);
34
+ this._handles.set(sessionId, handle);
35
+ return handle;
36
+ }
37
+ get(sessionId) {
38
+ return this._handles.get(sessionId);
39
+ }
40
+ sessionIds() {
41
+ return this._handles.keys();
42
+ }
43
+ abortAll() {
44
+ for (const handle of this._handles.values()) {
45
+ handle.abort();
46
+ }
47
+ }
48
+ get size() {
49
+ return this._handles.size;
50
+ }
51
+ _remove(sessionId) {
52
+ this._handles.delete(sessionId);
53
+ }
54
+ }
55
+ exports.ActiveSessionSet = ActiveSessionSet;
@@ -0,0 +1,32 @@
1
+ import type { WorkflowTrigger } from './workflow-runner.js';
2
+ import type { V2ToolContext } from '../mcp/types.js';
3
+ export interface ContextRule {
4
+ readonly source: string;
5
+ readonly content: string;
6
+ readonly truncated: boolean;
7
+ }
8
+ export interface SessionNote {
9
+ readonly nodeId: string;
10
+ readonly stepId: string;
11
+ readonly content: string;
12
+ }
13
+ export interface BaseContext {
14
+ readonly soulContent: string;
15
+ readonly workspaceRules: readonly ContextRule[];
16
+ }
17
+ export interface ContextBundle extends BaseContext {
18
+ readonly sessionHistory: readonly SessionNote[];
19
+ }
20
+ export interface ContextLoader {
21
+ loadBase(trigger: WorkflowTrigger): Promise<BaseContext>;
22
+ loadSession(continueToken: string | null, base: BaseContext): Promise<ContextBundle>;
23
+ }
24
+ export declare class DefaultContextLoader implements ContextLoader {
25
+ private readonly _loadSoul;
26
+ private readonly _loadWorkspace;
27
+ private readonly _loadNotes;
28
+ private readonly _ctx;
29
+ constructor(_loadSoul: (resolvedPath?: string) => Promise<string>, _loadWorkspace: (workspacePath: string) => Promise<string | null>, _loadNotes: (continueToken: string, ctx: V2ToolContext) => Promise<readonly string[]>, _ctx: V2ToolContext);
30
+ loadBase(trigger: WorkflowTrigger): Promise<BaseContext>;
31
+ loadSession(continueToken: string | null, base: BaseContext): Promise<ContextBundle>;
32
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultContextLoader = void 0;
4
+ class DefaultContextLoader {
5
+ constructor(_loadSoul, _loadWorkspace, _loadNotes, _ctx) {
6
+ this._loadSoul = _loadSoul;
7
+ this._loadWorkspace = _loadWorkspace;
8
+ this._loadNotes = _loadNotes;
9
+ this._ctx = _ctx;
10
+ }
11
+ async loadBase(trigger) {
12
+ const [soulContent, workspaceContextStr] = await Promise.all([
13
+ this._loadSoul(trigger.soulFile),
14
+ this._loadWorkspace(trigger.workspacePath),
15
+ ]);
16
+ const workspaceRules = workspaceContextStr !== null
17
+ ? [{ source: 'workspace-context', content: workspaceContextStr, truncated: false }]
18
+ : [];
19
+ return { soulContent, workspaceRules };
20
+ }
21
+ async loadSession(continueToken, base) {
22
+ if (!continueToken) {
23
+ return { ...base, sessionHistory: [] };
24
+ }
25
+ const notes = await this._loadNotes(continueToken, this._ctx);
26
+ const sessionHistory = notes.map((content) => ({
27
+ nodeId: '',
28
+ stepId: '',
29
+ content,
30
+ }));
31
+ return { ...base, sessionHistory };
32
+ }
33
+ }
34
+ exports.DefaultContextLoader = DefaultContextLoader;
@@ -1,4 +1,5 @@
1
- import type { ReadFileState, AbortRegistry } from './workflow-runner.js';
1
+ import type { ReadFileState } from './workflow-runner.js';
2
+ import type { ActiveSessionSet } from './active-sessions.js';
2
3
  import type { DaemonEventEmitter } from './daemon-events.js';
3
4
  export interface FileStateTracker {
4
5
  recordRead(filePath: string, content: string, timestamp: number, isPartialView: boolean): void;
@@ -22,6 +23,6 @@ export interface SessionScope {
22
23
  readonly emitter: DaemonEventEmitter | undefined;
23
24
  readonly sessionId: string;
24
25
  readonly workflowId: string;
25
- readonly abortRegistry: AbortRegistry | undefined;
26
+ readonly activeSessionSet: ActiveSessionSet | undefined;
26
27
  readonly maxIssueSummaries: number;
27
28
  }
@@ -0,0 +1,38 @@
1
+ import type { Result } from '../../runtime/result.js';
2
+ export declare const DAEMON_SESSIONS_DIR: string;
3
+ export declare const BASH_TIMEOUT_MS: number;
4
+ export declare const READ_SIZE_CAP_BYTES: number;
5
+ export declare const GLOB_ALWAYS_EXCLUDE: string[];
6
+ export declare function withWorkrailSession(sid: string | null | undefined): {
7
+ workrailSessionId?: string;
8
+ };
9
+ export interface PersistTokensError {
10
+ readonly code: string;
11
+ readonly message: string;
12
+ }
13
+ export declare function persistTokens(sessionId: string, continueToken: string, checkpointToken: string | null, worktreePath?: string, recoveryContext?: {
14
+ readonly workflowId: string;
15
+ readonly goal: string;
16
+ readonly workspacePath: string;
17
+ }): Promise<Result<void, PersistTokensError>>;
18
+ export declare function findActualString(fileContent: string, oldString: string): string | null;
19
+ export interface IssueRecord {
20
+ sessionId: string;
21
+ kind: 'tool_failure' | 'blocked' | 'unexpected_behavior' | 'needs_human' | 'self_correction';
22
+ severity: 'info' | 'warn' | 'error' | 'fatal';
23
+ summary: string;
24
+ context?: string;
25
+ toolName?: string;
26
+ command?: string;
27
+ suggestedFix?: string;
28
+ continueToken?: string;
29
+ }
30
+ export declare function appendIssueAsync(issuesDir: string, sessionId: string, record: IssueRecord): Promise<void>;
31
+ export interface SignalRecord {
32
+ readonly signalId: string;
33
+ readonly sessionId: string;
34
+ readonly workrailSessionId?: string;
35
+ readonly signalKind: string;
36
+ readonly payload: Readonly<Record<string, unknown>>;
37
+ }
38
+ export declare function appendSignalAsync(signalsDir: string, sessionId: string, record: SignalRecord): Promise<void>;
@@ -0,0 +1,101 @@
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.GLOB_ALWAYS_EXCLUDE = exports.READ_SIZE_CAP_BYTES = exports.BASH_TIMEOUT_MS = exports.DAEMON_SESSIONS_DIR = void 0;
37
+ exports.withWorkrailSession = withWorkrailSession;
38
+ exports.persistTokens = persistTokens;
39
+ exports.findActualString = findActualString;
40
+ exports.appendIssueAsync = appendIssueAsync;
41
+ exports.appendSignalAsync = appendSignalAsync;
42
+ const fs = __importStar(require("node:fs/promises"));
43
+ const path = __importStar(require("node:path"));
44
+ const os = __importStar(require("node:os"));
45
+ const result_js_1 = require("../../runtime/result.js");
46
+ exports.DAEMON_SESSIONS_DIR = path.join(os.homedir(), '.workrail', 'daemon-sessions');
47
+ exports.BASH_TIMEOUT_MS = 5 * 60 * 1000;
48
+ exports.READ_SIZE_CAP_BYTES = 256 * 1024;
49
+ exports.GLOB_ALWAYS_EXCLUDE = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'];
50
+ function withWorkrailSession(sid) {
51
+ return sid != null ? { workrailSessionId: sid } : {};
52
+ }
53
+ async function persistTokens(sessionId, continueToken, checkpointToken, worktreePath, recoveryContext) {
54
+ try {
55
+ await fs.mkdir(exports.DAEMON_SESSIONS_DIR, { recursive: true });
56
+ const sessionPath = path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`);
57
+ const state = JSON.stringify({
58
+ continueToken,
59
+ checkpointToken,
60
+ ts: Date.now(),
61
+ ...(worktreePath !== undefined ? { worktreePath } : {}),
62
+ ...(recoveryContext !== undefined ? {
63
+ workflowId: recoveryContext.workflowId,
64
+ goal: recoveryContext.goal,
65
+ workspacePath: recoveryContext.workspacePath,
66
+ } : {}),
67
+ }, null, 2);
68
+ const tmp = `${sessionPath}.tmp`;
69
+ await fs.writeFile(tmp, state, 'utf8');
70
+ await fs.rename(tmp, sessionPath);
71
+ return (0, result_js_1.ok)(undefined);
72
+ }
73
+ catch (e) {
74
+ const nodeErr = e;
75
+ return (0, result_js_1.err)({ code: nodeErr.code ?? 'UNKNOWN', message: nodeErr.message ?? String(e) });
76
+ }
77
+ }
78
+ function findActualString(fileContent, oldString) {
79
+ if (fileContent.includes(oldString))
80
+ return oldString;
81
+ const normalized = oldString
82
+ .replace(/[‘’]/g, "'")
83
+ .replace(/[“”]/g, '"')
84
+ .replace(/–/g, '-')
85
+ .replace(/—/g, '--');
86
+ if (fileContent.includes(normalized))
87
+ return normalized;
88
+ return null;
89
+ }
90
+ async function appendIssueAsync(issuesDir, sessionId, record) {
91
+ await fs.mkdir(issuesDir, { recursive: true });
92
+ const filePath = path.join(issuesDir, `${sessionId}.jsonl`);
93
+ const line = JSON.stringify({ ...record, ts: Date.now() }) + '\n';
94
+ await fs.appendFile(filePath, line, 'utf8');
95
+ }
96
+ async function appendSignalAsync(signalsDir, sessionId, record) {
97
+ await fs.mkdir(signalsDir, { recursive: true });
98
+ const filePath = path.join(signalsDir, `${sessionId}.jsonl`);
99
+ const line = JSON.stringify({ ...record, ts: Date.now() }) + '\n';
100
+ await fs.appendFile(filePath, line, 'utf8');
101
+ }
@@ -0,0 +1,3 @@
1
+ import type { AgentTool } from '../agent-loop.js';
2
+ import type { DaemonEventEmitter } from '../daemon-events.js';
3
+ export declare function makeBashTool(workspacePath: string, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeBashTool = makeBashTool;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_util_1 = require("node:util");
6
+ const _shared_js_1 = require("./_shared.js");
7
+ const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
8
+ function makeBashTool(workspacePath, schemas, sessionId, emitter, workrailSessionId) {
9
+ return {
10
+ name: 'Bash',
11
+ description: 'Execute a shell command. Throws on failure (non-zero exit with stderr, or exit code 2+). ' +
12
+ 'Exit code 1 with empty stderr is treated as "no match found" (standard grep semantics) and ' +
13
+ 'returns empty output without throwing. ' +
14
+ `Maximum execution time: ${_shared_js_1.BASH_TIMEOUT_MS / 1000}s.`,
15
+ inputSchema: schemas['BashParams'],
16
+ label: 'Bash',
17
+ execute: async (_toolCallId, params) => {
18
+ if (typeof params.command !== 'string' || !params.command)
19
+ throw new Error('Bash: command must be a non-empty string');
20
+ console.log(`[WorkflowRunner] Tool: bash "${String(params.command).slice(0, 80)}"`);
21
+ if (sessionId)
22
+ emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Bash', summary: String(params.command).slice(0, 80), ...(0, _shared_js_1.withWorkrailSession)(workrailSessionId) });
23
+ const cwd = params.cwd ?? workspacePath;
24
+ try {
25
+ const { stdout, stderr } = await execAsync(params.command, {
26
+ cwd,
27
+ timeout: _shared_js_1.BASH_TIMEOUT_MS,
28
+ shell: '/bin/bash',
29
+ });
30
+ const output = [stdout, stderr].filter(Boolean).join('\n');
31
+ return {
32
+ content: [{ type: 'text', text: output || '(no output)' }],
33
+ details: { stdout, stderr },
34
+ };
35
+ }
36
+ catch (err) {
37
+ const e = err;
38
+ const stdout = String(e.stdout ?? '');
39
+ const stderr = String(e.stderr ?? '');
40
+ const rawCode = e.code;
41
+ const signal = e.signal;
42
+ if (rawCode === 1 && !stderr.trim()) {
43
+ return {
44
+ content: [{ type: 'text', text: stdout || '(no output)' }],
45
+ details: { stdout, stderr },
46
+ };
47
+ }
48
+ const exitInfo = rawCode != null
49
+ ? `exit ${String(rawCode)}`
50
+ : signal
51
+ ? `signal ${String(signal)}`
52
+ : 'exit unknown';
53
+ throw new Error(`Command failed: ${params.command} (${exitInfo})\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`);
54
+ }
55
+ },
56
+ };
57
+ }
@@ -0,0 +1,6 @@
1
+ import type { AgentTool } from '../agent-loop.js';
2
+ import type { V2ToolContext } from '../../mcp/types.js';
3
+ import type { DaemonEventEmitter } from '../daemon-events.js';
4
+ import { executeContinueWorkflow } from '../../mcp/handlers/v2-execution/index.js';
5
+ export declare function makeContinueWorkflowTool(sessionId: string, ctx: V2ToolContext, onAdvance: (nextStepText: string, continueToken: string) => void, onComplete: (notes: string | undefined, artifacts?: readonly unknown[]) => void, schemas: Record<string, any>, _executeContinueWorkflowFn?: typeof executeContinueWorkflow, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
6
+ export declare function makeCompleteStepTool(sessionId: string, ctx: V2ToolContext, getCurrentToken: () => string, onAdvance: (nextStepText: string, continueToken: string) => void, onComplete: (notes: string | undefined, artifacts?: readonly unknown[]) => void, onTokenUpdate: (t: string) => void, schemas: Record<string, any>, _executeContinueWorkflowFn?: typeof executeContinueWorkflow, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeContinueWorkflowTool = makeContinueWorkflowTool;
4
+ exports.makeCompleteStepTool = makeCompleteStepTool;
5
+ const index_js_1 = require("../../mcp/handlers/v2-execution/index.js");
6
+ const _shared_js_1 = require("./_shared.js");
7
+ function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, _executeContinueWorkflowFn = index_js_1.executeContinueWorkflow, emitter, workrailSessionId) {
8
+ return {
9
+ name: 'continue_workflow',
10
+ description: '[DEPRECATED in daemon sessions -- use complete_step instead] ' +
11
+ 'Advance the WorkRail workflow to the next step. Call this after completing all work ' +
12
+ 'required by the current step. Include your notes in notesMarkdown. ' +
13
+ 'When the step requires an assessment gate, include wr.assessment objects in artifacts.',
14
+ inputSchema: schemas['ContinueWorkflowParams'],
15
+ label: 'Continue Workflow',
16
+ execute: async (_toolCallId, params) => {
17
+ console.log(`[WorkflowRunner] Tool: continue_workflow sessionId=${sessionId}`);
18
+ emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'continue_workflow', summary: params.intent ?? 'advance', ...(0, _shared_js_1.withWorkrailSession)(workrailSessionId) });
19
+ const result = await _executeContinueWorkflowFn({
20
+ continueToken: params.continueToken,
21
+ intent: (params.intent ?? 'advance'),
22
+ output: (params.notesMarkdown || params.artifacts?.length)
23
+ ? {
24
+ ...(params.notesMarkdown ? { notesMarkdown: params.notesMarkdown } : {}),
25
+ ...(params.artifacts ? { artifacts: params.artifacts } : {}),
26
+ }
27
+ : undefined,
28
+ context: params.context,
29
+ }, ctx);
30
+ if (result.isErr()) {
31
+ throw new Error(`continue_workflow failed: ${result.error.kind} -- ${JSON.stringify(result.error)}`);
32
+ }
33
+ const out = result.value.response;
34
+ const continueToken = out.continueToken ?? '';
35
+ const checkpointToken = out.checkpointToken ?? null;
36
+ const persistToken = (out.kind === 'blocked' ? out.nextCall?.params.continueToken : undefined) ?? continueToken;
37
+ if (persistToken) {
38
+ const persistResult = await (0, _shared_js_1.persistTokens)(sessionId, persistToken, checkpointToken);
39
+ if (persistResult.kind === 'err') {
40
+ console.warn(`[WorkflowRunner] persistTokens failed (continue_workflow): ${persistResult.error.code} -- ${persistResult.error.message}`);
41
+ }
42
+ }
43
+ if (out.kind === 'blocked') {
44
+ const retryToken = out.nextCall?.params.continueToken ?? continueToken;
45
+ const lines = ['## Step blocked -- action required\n'];
46
+ for (const blocker of out.blockers.blockers) {
47
+ lines.push(blocker.message);
48
+ if (blocker.suggestedFix) {
49
+ lines.push(`\nWhat to do: ${blocker.suggestedFix}`);
50
+ }
51
+ lines.push('');
52
+ }
53
+ if (out.validation) {
54
+ if (out.validation.issues.length > 0) {
55
+ lines.push('**Issues:**');
56
+ for (const issue of out.validation.issues)
57
+ lines.push(`- ${issue}`);
58
+ lines.push('');
59
+ }
60
+ if (out.validation.suggestions.length > 0) {
61
+ lines.push('**Suggestions:**');
62
+ for (const s of out.validation.suggestions)
63
+ lines.push(`- ${s}`);
64
+ lines.push('');
65
+ }
66
+ }
67
+ if (out.assessmentFollowup) {
68
+ lines.push(`**Follow-up required:** ${out.assessmentFollowup.title}`);
69
+ lines.push(out.assessmentFollowup.guidance);
70
+ lines.push('');
71
+ }
72
+ if (out.retryable) {
73
+ lines.push(`Retry the same step with corrected output.\n\ncontinueToken: ${retryToken}`);
74
+ }
75
+ else {
76
+ lines.push(`You cannot proceed without resolving this. Inform the user and wait for their response, then call continue_workflow.\n\ncontinueToken: ${retryToken}`);
77
+ }
78
+ const feedback = lines.join('\n');
79
+ return {
80
+ content: [{ type: 'text', text: feedback }],
81
+ details: out,
82
+ };
83
+ }
84
+ if (out.isComplete) {
85
+ onComplete(params.notesMarkdown, Array.isArray(params.artifacts) ? params.artifacts : undefined);
86
+ return {
87
+ content: [{ type: 'text', text: 'Workflow complete. All steps have been executed.' }],
88
+ details: out,
89
+ };
90
+ }
91
+ const pending = out.pending;
92
+ const stepText = pending
93
+ ? `## Next step: ${pending.title}\n\n${pending.prompt}\n\ncontinueToken: ${continueToken}`
94
+ : `Step advanced. continueToken: ${continueToken}`;
95
+ onAdvance(stepText, continueToken);
96
+ return {
97
+ content: [{ type: 'text', text: stepText }],
98
+ details: out,
99
+ };
100
+ },
101
+ };
102
+ }
103
+ function makeCompleteStepTool(sessionId, ctx, getCurrentToken, onAdvance, onComplete, onTokenUpdate, schemas, _executeContinueWorkflowFn = index_js_1.executeContinueWorkflow, emitter, workrailSessionId) {
104
+ return {
105
+ name: 'complete_step',
106
+ description: 'Mark the current WorkRail workflow step as complete and advance to the next one. ' +
107
+ 'Call this after completing all work required by the current step. ' +
108
+ 'Include your substantive notes (min 50 characters) describing what you did. ' +
109
+ 'The daemon manages the session token internally -- you do not need a continueToken. ' +
110
+ 'When the step requires an assessment gate, include wr.assessment objects in artifacts.',
111
+ inputSchema: schemas['CompleteStepParams'],
112
+ label: 'Complete Step',
113
+ execute: async (_toolCallId, params) => {
114
+ console.log(`[WorkflowRunner] Tool: complete_step sessionId=${sessionId}`);
115
+ emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'complete_step', summary: 'advance', ...(0, _shared_js_1.withWorkrailSession)(workrailSessionId) });
116
+ const notes = params.notes;
117
+ if (!notes || notes.length < 50) {
118
+ throw new Error(`complete_step: notes is required and must be at least 50 characters. ` +
119
+ `Provide substantive notes describing what you did, what you produced, and any notable decisions. ` +
120
+ `Current length: ${notes?.length ?? 0} characters.`);
121
+ }
122
+ const continueToken = getCurrentToken();
123
+ const result = await _executeContinueWorkflowFn({
124
+ continueToken,
125
+ intent: 'advance',
126
+ output: (notes || params.artifacts?.length)
127
+ ? {
128
+ notesMarkdown: notes,
129
+ ...(params.artifacts?.length ? { artifacts: params.artifacts } : {}),
130
+ }
131
+ : undefined,
132
+ context: params.context,
133
+ }, ctx);
134
+ if (result.isErr()) {
135
+ throw new Error(`complete_step failed: ${result.error.kind} -- ${JSON.stringify(result.error)}`);
136
+ }
137
+ const out = result.value.response;
138
+ const newContinueToken = out.continueToken ?? '';
139
+ const checkpointToken = out.checkpointToken ?? null;
140
+ const persistToken = (out.kind === 'blocked' ? out.nextCall?.params.continueToken : undefined) ?? newContinueToken;
141
+ if (persistToken) {
142
+ const persistResult = await (0, _shared_js_1.persistTokens)(sessionId, persistToken, checkpointToken);
143
+ if (persistResult.kind === 'err') {
144
+ console.warn(`[WorkflowRunner] persistTokens failed (complete_step): ${persistResult.error.code} -- ${persistResult.error.message}`);
145
+ }
146
+ }
147
+ if (out.kind === 'blocked') {
148
+ const retryToken = out.nextCall?.params.continueToken ?? newContinueToken;
149
+ onTokenUpdate(retryToken);
150
+ const lines = ['## Step blocked -- action required\n'];
151
+ for (const blocker of out.blockers.blockers) {
152
+ lines.push(blocker.message);
153
+ if (blocker.suggestedFix) {
154
+ lines.push(`\nWhat to do: ${blocker.suggestedFix}`);
155
+ }
156
+ lines.push('');
157
+ }
158
+ if (out.validation) {
159
+ if (out.validation.issues.length > 0) {
160
+ lines.push('**Issues:**');
161
+ for (const issue of out.validation.issues)
162
+ lines.push(`- ${issue}`);
163
+ lines.push('');
164
+ }
165
+ if (out.validation.suggestions.length > 0) {
166
+ lines.push('**Suggestions:**');
167
+ for (const s of out.validation.suggestions)
168
+ lines.push(`- ${s}`);
169
+ lines.push('');
170
+ }
171
+ }
172
+ if (out.assessmentFollowup) {
173
+ lines.push(`**Follow-up required:** ${out.assessmentFollowup.title}`);
174
+ lines.push(out.assessmentFollowup.guidance);
175
+ lines.push('');
176
+ }
177
+ if (out.retryable) {
178
+ lines.push(`Retry the same step: call complete_step again with corrected notes.`);
179
+ }
180
+ else {
181
+ lines.push(`You cannot proceed without resolving this. Inform the user and wait for their response, then call complete_step.`);
182
+ }
183
+ const feedback = lines.join('\n');
184
+ return {
185
+ content: [{ type: 'text', text: feedback }],
186
+ details: out,
187
+ };
188
+ }
189
+ if (out.isComplete) {
190
+ onComplete(notes, Array.isArray(params.artifacts) ? params.artifacts : undefined);
191
+ return {
192
+ content: [{ type: 'text', text: JSON.stringify({ status: 'complete' }) }],
193
+ details: out,
194
+ };
195
+ }
196
+ const pending = out.pending;
197
+ const nextStepTitle = pending?.title ?? 'Next step';
198
+ const stepText = pending
199
+ ? `${JSON.stringify({ status: 'advanced', nextStep: pending.title })}\n\n## ${pending.title}\n\n${pending.prompt}`
200
+ : JSON.stringify({ status: 'advanced', nextStep: nextStepTitle });
201
+ onAdvance(stepText, newContinueToken);
202
+ return {
203
+ content: [{ type: 'text', text: stepText }],
204
+ details: out,
205
+ };
206
+ },
207
+ };
208
+ }
@@ -0,0 +1,6 @@
1
+ import type { AgentTool } from '../agent-loop.js';
2
+ import type { DaemonEventEmitter } from '../daemon-events.js';
3
+ import type { ReadFileState } from '../workflow-runner.js';
4
+ export declare function makeReadTool(readFileState: Map<string, ReadFileState>, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
5
+ export declare function makeWriteTool(readFileState: Map<string, ReadFileState>, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;
6
+ export declare function makeEditTool(workspacePath: string, readFileState: Map<string, ReadFileState>, schemas: Record<string, any>, sessionId?: string, emitter?: DaemonEventEmitter, workrailSessionId?: string | null): AgentTool;