@exaudeus/workrail 3.25.0 → 3.26.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.
Files changed (59) hide show
  1. package/dist/cli/commands/index.d.ts +5 -0
  2. package/dist/cli/commands/index.js +12 -1
  3. package/dist/cli/commands/worktrain-await.d.ts +35 -0
  4. package/dist/cli/commands/worktrain-await.js +207 -0
  5. package/dist/cli/commands/worktrain-inbox.d.ts +23 -0
  6. package/dist/cli/commands/worktrain-inbox.js +82 -0
  7. package/dist/cli/commands/worktrain-init.d.ts +23 -0
  8. package/dist/cli/commands/worktrain-init.js +338 -0
  9. package/dist/cli/commands/worktrain-spawn.d.ts +28 -0
  10. package/dist/cli/commands/worktrain-spawn.js +106 -0
  11. package/dist/cli/commands/worktrain-tell.d.ts +25 -0
  12. package/dist/cli/commands/worktrain-tell.js +32 -0
  13. package/dist/cli-worktrain.d.ts +2 -0
  14. package/dist/cli-worktrain.js +169 -0
  15. package/dist/cli.js +13 -3
  16. package/dist/config/config-file.d.ts +2 -0
  17. package/dist/config/config-file.js +55 -0
  18. package/dist/daemon/agent-loop.d.ts +90 -0
  19. package/dist/daemon/agent-loop.js +214 -0
  20. package/dist/daemon/pi-mono-loader.d.ts +0 -5
  21. package/dist/daemon/pi-mono-loader.js +0 -64
  22. package/dist/daemon/soul-template.d.ts +2 -0
  23. package/dist/daemon/soul-template.js +22 -0
  24. package/dist/daemon/workflow-runner.d.ts +24 -2
  25. package/dist/daemon/workflow-runner.js +235 -119
  26. package/dist/manifest.json +147 -51
  27. package/dist/mcp/output-schemas.d.ts +154 -154
  28. package/dist/mcp/transports/bridge-entry.js +20 -2
  29. package/dist/mcp/transports/bridge-events.d.ts +34 -0
  30. package/dist/mcp/transports/bridge-events.js +24 -0
  31. package/dist/mcp/transports/fatal-exit.d.ts +5 -0
  32. package/dist/mcp/transports/fatal-exit.js +82 -0
  33. package/dist/mcp/transports/http-entry.js +3 -0
  34. package/dist/mcp/transports/stdio-entry.js +3 -7
  35. package/dist/mcp/v2/tools.d.ts +7 -7
  36. package/dist/trigger/delivery-action.d.ts +37 -0
  37. package/dist/trigger/delivery-action.js +204 -0
  38. package/dist/trigger/delivery-client.d.ts +11 -0
  39. package/dist/trigger/delivery-client.js +27 -0
  40. package/dist/trigger/trigger-listener.d.ts +2 -0
  41. package/dist/trigger/trigger-listener.js +12 -2
  42. package/dist/trigger/trigger-router.d.ts +8 -2
  43. package/dist/trigger/trigger-router.js +164 -6
  44. package/dist/trigger/trigger-store.d.ts +11 -3
  45. package/dist/trigger/trigger-store.js +254 -13
  46. package/dist/trigger/types.d.ts +24 -0
  47. package/dist/trigger/types.js +4 -0
  48. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +22 -22
  49. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +114 -114
  50. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +454 -454
  51. package/dist/v2/durable-core/schemas/session/blockers.d.ts +14 -14
  52. package/dist/v2/durable-core/schemas/session/events.d.ts +93 -93
  53. package/dist/v2/durable-core/schemas/session/gaps.d.ts +2 -2
  54. package/dist/v2/durable-core/schemas/session/validation-event.d.ts +4 -4
  55. package/dist/v2/usecases/console-routes.js +33 -3
  56. package/package.json +6 -4
  57. package/spec/workflow-tags.json +1 -0
  58. package/workflows/classify-task-workflow.json +68 -0
  59. package/workflows/coding-task-workflow-agentic.lean.v2.json +43 -13
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const promises_1 = require("readline/promises");
9
+ const process_1 = require("process");
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const child_process_1 = require("child_process");
14
+ const util_1 = require("util");
15
+ const crypto_1 = require("crypto");
16
+ const interpret_result_js_1 = require("./cli/interpret-result.js");
17
+ const index_js_1 = require("./cli/commands/index.js");
18
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
19
+ const program = new commander_1.Command();
20
+ program
21
+ .name('worktrain')
22
+ .description('WorkTrain daemon management')
23
+ .version('0.0.3');
24
+ program
25
+ .command('init')
26
+ .description('Guided setup for WorkTrain daemon: credentials, workspace, triggers.yml, daemon-soul.md, smoke test')
27
+ .option('-y, --yes', 'Skip interactive prompts and use safe defaults (for CI / non-TTY use)')
28
+ .action(async (options) => {
29
+ if (!options.yes && !process_1.stdin.isTTY) {
30
+ console.warn('Warning: stdin is not a TTY. Interactive prompts may not work as expected.\n' +
31
+ 'Run with --yes to use safe defaults without prompting.');
32
+ }
33
+ const rl = (0, promises_1.createInterface)({ input: process_1.stdin, output: process_1.stdout, terminal: true });
34
+ try {
35
+ const result = await (0, index_js_1.executeWorktrainInitCommand)({
36
+ prompt: async (question, defaultValue) => {
37
+ if (options.yes) {
38
+ return defaultValue ?? '';
39
+ }
40
+ const answer = await rl.question(question);
41
+ return answer.trim() || (defaultValue ?? '');
42
+ },
43
+ mkdir: (p, opts) => fs_1.default.promises.mkdir(p, opts),
44
+ readFile: (p) => fs_1.default.promises.readFile(p, 'utf-8'),
45
+ writeFile: (p, content) => fs_1.default.promises.writeFile(p, content, 'utf-8'),
46
+ exists: async (p) => {
47
+ try {
48
+ await fs_1.default.promises.access(p);
49
+ return true;
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ },
55
+ homedir: os_1.default.homedir,
56
+ cwd: process.cwd,
57
+ joinPath: path_1.default.join,
58
+ runSmoke: async () => {
59
+ try {
60
+ const { stdout } = await execFileAsync('workrail', ['list'], {
61
+ timeout: 10000,
62
+ });
63
+ return { ok: true, output: stdout.trim() };
64
+ }
65
+ catch (err) {
66
+ const message = err instanceof Error
67
+ ? err.message
68
+ : String(err);
69
+ return { ok: false, output: message };
70
+ }
71
+ },
72
+ print: (line) => console.log(line),
73
+ env: process_1.env,
74
+ }, { yes: options.yes });
75
+ (0, interpret_result_js_1.interpretCliResultWithoutDI)(result);
76
+ }
77
+ finally {
78
+ rl.close();
79
+ }
80
+ });
81
+ program
82
+ .command('tell <message>')
83
+ .description('Queue an async message for the WorkTrain daemon (~/.workrail/message-queue.jsonl)')
84
+ .option('-w, --workspace <name>', 'Workspace hint for the daemon (optional)')
85
+ .addOption(new commander_1.Option('-p, --priority <level>', 'Message priority: high, normal, or low')
86
+ .choices(['high', 'normal', 'low'])
87
+ .default('normal'))
88
+ .action(async (message, options) => {
89
+ const result = await (0, index_js_1.executeWorktrainTellCommand)(message, {
90
+ appendFile: (p, content) => fs_1.default.promises.appendFile(p, content, 'utf-8'),
91
+ mkdir: (p, opts) => fs_1.default.promises.mkdir(p, opts),
92
+ homedir: os_1.default.homedir,
93
+ joinPath: path_1.default.join,
94
+ print: (line) => console.log(line),
95
+ now: () => new Date().toISOString(),
96
+ generateId: () => (0, crypto_1.randomUUID)(),
97
+ }, {
98
+ workspace: options.workspace,
99
+ priority: (options.priority ?? 'normal'),
100
+ });
101
+ (0, interpret_result_js_1.interpretCliResultWithoutDI)(result);
102
+ });
103
+ program
104
+ .command('inbox')
105
+ .description('Read unread messages from the WorkTrain daemon (~/.workrail/outbox.jsonl)')
106
+ .option('-w, --watch', 'Watch for new messages in real time (not yet implemented)')
107
+ .action(async (options) => {
108
+ const result = await (0, index_js_1.executeWorktrainInboxCommand)({
109
+ readFile: (p) => fs_1.default.promises.readFile(p, 'utf-8'),
110
+ writeFile: (p, content) => fs_1.default.promises.writeFile(p, content, 'utf-8'),
111
+ mkdir: (p, opts) => fs_1.default.promises.mkdir(p, opts),
112
+ homedir: os_1.default.homedir,
113
+ joinPath: path_1.default.join,
114
+ print: (line) => console.log(line),
115
+ }, { watch: options.watch });
116
+ (0, interpret_result_js_1.interpretCliResultWithoutDI)(result);
117
+ });
118
+ program
119
+ .command('spawn')
120
+ .description('Start a workflow session non-interactively. Prints session handle to stdout.')
121
+ .requiredOption('-w, --workflow <id>', 'Workflow ID to run')
122
+ .requiredOption('-g, --goal <text>', 'One-sentence goal for the workflow session')
123
+ .requiredOption('-W, --workspace <path>', 'Absolute path to the workspace directory')
124
+ .option('-p, --port <n>', 'Console HTTP server port (default: auto-discover from lock file, then 3456)', parseInt)
125
+ .action(async (options) => {
126
+ const result = await (0, index_js_1.executeWorktrainSpawnCommand)({
127
+ fetch: (url, opts) => globalThis.fetch(url, opts),
128
+ readFile: (p) => fs_1.default.promises.readFile(p, 'utf-8'),
129
+ stdout: (line) => process.stdout.write(line + '\n'),
130
+ stderr: (line) => process.stderr.write(line + '\n'),
131
+ homedir: os_1.default.homedir,
132
+ joinPath: path_1.default.join,
133
+ pathIsAbsolute: path_1.default.isAbsolute,
134
+ statPath: (p) => fs_1.default.promises.stat(p),
135
+ }, {
136
+ workflow: options.workflow,
137
+ goal: options.goal,
138
+ workspace: options.workspace,
139
+ port: options.port,
140
+ });
141
+ (0, interpret_result_js_1.interpretCliResultWithoutDI)(result);
142
+ });
143
+ program
144
+ .command('await')
145
+ .description('Block until workflow sessions complete. Prints JSON results to stdout.')
146
+ .requiredOption('-s, --sessions <handles>', 'Comma-separated list of session handles to wait for')
147
+ .option('-m, --mode <mode>', "Wait mode: 'all' (default) or 'any'", 'all')
148
+ .option('-t, --timeout <duration>', 'Timeout (e.g. "30m", "1h", "90s"). Default: 30m', '30m')
149
+ .option('-p, --port <n>', 'Console HTTP server port (default: auto-discover from lock file, then 3456)', parseInt)
150
+ .action(async (options) => {
151
+ const mode = options.mode === 'any' ? 'any' : 'all';
152
+ const result = await (0, index_js_1.executeWorktrainAwaitCommand)({
153
+ fetch: (url) => globalThis.fetch(url),
154
+ readFile: (p) => fs_1.default.promises.readFile(p, 'utf-8'),
155
+ stdout: (line) => process.stdout.write(line + '\n'),
156
+ stderr: (line) => process.stderr.write(line + '\n'),
157
+ homedir: os_1.default.homedir,
158
+ joinPath: path_1.default.join,
159
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
160
+ now: () => Date.now(),
161
+ }, {
162
+ sessions: options.sessions,
163
+ mode,
164
+ timeout: options.timeout,
165
+ port: options.port,
166
+ });
167
+ (0, interpret_result_js_1.interpretCliResultWithoutDI)(result);
168
+ });
169
+ program.parse();
package/dist/cli.js CHANGED
@@ -49,6 +49,7 @@ const validation_js_1 = require("./application/validation.js");
49
49
  const validate_workflow_file_js_1 = require("./application/use-cases/validate-workflow-file.js");
50
50
  const workflow_compiler_js_1 = require("./application/services/workflow-compiler.js");
51
51
  const v1_to_v2_shim_js_1 = require("./v2/read-only/v1-to-v2-shim.js");
52
+ const config_file_js_1 = require("./config/config-file.js");
52
53
  const interpret_result_js_1 = require("./cli/interpret-result.js");
53
54
  const index_js_1 = require("./cli/commands/index.js");
54
55
  const program = new commander_1.Command();
@@ -186,7 +187,7 @@ program
186
187
  program
187
188
  .command('daemon')
188
189
  .description('Start the autonomous WorkRail daemon (trigger webhook server on port 3200)')
189
- .option('-w, --workspace <path>', 'Path to workspace containing triggers.yml', process.cwd())
190
+ .option('-w, --workspace <path>', 'Path to workspace containing triggers.yml')
190
191
  .action(async (options) => {
191
192
  const { startTriggerListener } = await Promise.resolve().then(() => __importStar(require('./trigger/trigger-listener.js')));
192
193
  await (0, container_js_1.initializeContainer)({ runtimeMode: { kind: 'cli' } });
@@ -199,6 +200,15 @@ program
199
200
  process.exit(1);
200
201
  }
201
202
  const ctx = v2Guard.ctx;
203
+ let workspacePath;
204
+ if (options.workspace) {
205
+ workspacePath = options.workspace;
206
+ }
207
+ else {
208
+ const configResult = (0, config_file_js_1.loadWorkrailConfigFile)();
209
+ const configWorkspace = configResult.kind === 'ok' ? configResult.value['WORKRAIL_DEFAULT_WORKSPACE'] : undefined;
210
+ workspacePath = configWorkspace?.trim() || process.cwd();
211
+ }
202
212
  const usesBedrock = !!process.env['AWS_PROFILE'] || !!process.env['AWS_ACCESS_KEY_ID'];
203
213
  const apiKey = process.env['ANTHROPIC_API_KEY'];
204
214
  if (!usesBedrock && !apiKey) {
@@ -206,7 +216,7 @@ program
206
216
  process.exit(1);
207
217
  }
208
218
  const handle = await startTriggerListener(ctx, {
209
- workspacePath: options.workspace,
219
+ workspacePath,
210
220
  apiKey: apiKey,
211
221
  env: process.env,
212
222
  });
@@ -219,7 +229,7 @@ program
219
229
  process.exit(1);
220
230
  }
221
231
  console.log(`WorkRail daemon running on port ${handle.port}`);
222
- console.log(`Workspace: ${options.workspace}`);
232
+ console.log(`Workspace: ${workspacePath}`);
223
233
  console.log('Waiting for webhook triggers...');
224
234
  const shutdown = async () => {
225
235
  console.log('\nShutting down daemon...');
@@ -1,4 +1,5 @@
1
1
  import type { Result } from '../runtime/result.js';
2
+ import type { WorkspaceConfig } from '../trigger/types.js';
2
3
  export type ConfigFileError = {
3
4
  readonly _tag: 'ConfigFileError';
4
5
  readonly message: string;
@@ -6,3 +7,4 @@ export type ConfigFileError = {
6
7
  };
7
8
  export declare function ensureWorkrailConfigFile(): void;
8
9
  export declare function loadWorkrailConfigFile(): Result<Record<string, string>, ConfigFileError>;
10
+ export declare function loadWorkspacesFromConfigFile(): Result<Record<string, WorkspaceConfig>, never>;
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ensureWorkrailConfigFile = ensureWorkrailConfigFile;
37
37
  exports.loadWorkrailConfigFile = loadWorkrailConfigFile;
38
+ exports.loadWorkspacesFromConfigFile = loadWorkspacesFromConfigFile;
38
39
  const os = __importStar(require("os"));
39
40
  const path = __importStar(require("path"));
40
41
  const fs = __importStar(require("fs"));
@@ -65,6 +66,8 @@ const ALLOWED_CONFIG_FILE_KEYS = new Set([
65
66
  'WORKRAIL_DATA_DIR',
66
67
  'WORKRAIL_CACHE_DIR',
67
68
  'WORKRAIL_JSON_RESPONSES',
69
+ 'WORKRAIL_DEFAULT_WORKSPACE',
70
+ 'maxConcurrentSessions',
68
71
  ]);
69
72
  const ConfigFileSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.string());
70
73
  const CONFIG_FILE_TEMPLATE = `{
@@ -139,3 +142,55 @@ function loadWorkrailConfigFile() {
139
142
  }
140
143
  return (0, result_js_1.ok)(validated);
141
144
  }
145
+ const WorkspaceConfigEntrySchema = zod_1.z.object({
146
+ path: zod_1.z.string().min(1),
147
+ soulFile: zod_1.z.string().min(1).optional(),
148
+ });
149
+ function loadWorkspacesFromConfigFile() {
150
+ if (process.env['VITEST'])
151
+ return (0, result_js_1.ok)({});
152
+ const configPath = path.join(os.homedir(), '.workrail', 'config.json');
153
+ let rawContent;
154
+ try {
155
+ rawContent = fs.readFileSync(configPath, 'utf-8');
156
+ }
157
+ catch {
158
+ return (0, result_js_1.ok)({});
159
+ }
160
+ let parsed;
161
+ try {
162
+ parsed = JSON.parse(rawContent);
163
+ }
164
+ catch {
165
+ return (0, result_js_1.ok)({});
166
+ }
167
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
168
+ return (0, result_js_1.ok)({});
169
+ }
170
+ const workspacesRaw = parsed['workspaces'];
171
+ if (workspacesRaw === undefined || workspacesRaw === null) {
172
+ return (0, result_js_1.ok)({});
173
+ }
174
+ if (typeof workspacesRaw !== 'object' || Array.isArray(workspacesRaw)) {
175
+ console.warn('[WorkRail] config file: "workspaces" must be an object (map of name -> { path, soulFile? }). Ignoring.');
176
+ return (0, result_js_1.ok)({});
177
+ }
178
+ const result = {};
179
+ for (const [name, entry] of Object.entries(workspacesRaw)) {
180
+ const parseResult = WorkspaceConfigEntrySchema.safeParse(entry);
181
+ if (!parseResult.success) {
182
+ console.warn(`[WorkRail] config file: workspace "${name}" has invalid shape -- skipped. ` +
183
+ `Expected { path: string; soulFile?: string }. ` +
184
+ `Issues: ${parseResult.error.issues.map((i) => i.message).join(', ')}`);
185
+ continue;
186
+ }
187
+ const data = parseResult.data;
188
+ if (data.soulFile?.startsWith('~/')) {
189
+ result[name] = { ...data, soulFile: path.join(os.homedir(), data.soulFile.slice(2)) };
190
+ }
191
+ else {
192
+ result[name] = data;
193
+ }
194
+ }
195
+ return (0, result_js_1.ok)(result);
196
+ }
@@ -0,0 +1,90 @@
1
+ import type Anthropic from '@anthropic-ai/sdk';
2
+ export interface AgentClientInterface {
3
+ messages: {
4
+ create(params: Anthropic.MessageCreateParamsNonStreaming, options?: {
5
+ signal?: AbortSignal;
6
+ }): Promise<Anthropic.Message>;
7
+ };
8
+ }
9
+ export interface AgentToolResult<T> {
10
+ readonly content: ReadonlyArray<{
11
+ readonly type: 'text';
12
+ readonly text: string;
13
+ }>;
14
+ readonly details: T;
15
+ }
16
+ export interface AgentTool {
17
+ readonly name: string;
18
+ readonly description: string;
19
+ readonly inputSchema: Record<string, unknown>;
20
+ readonly label: string;
21
+ execute(toolCallId: string, params: Record<string, unknown>): Promise<AgentToolResult<unknown>>;
22
+ }
23
+ export type AgentEvent = {
24
+ readonly type: 'turn_end';
25
+ readonly toolResults: ReadonlyArray<AgentToolCallResult>;
26
+ } | {
27
+ readonly type: 'agent_end';
28
+ };
29
+ export interface AgentToolCallResult {
30
+ readonly toolCallId: string;
31
+ readonly toolName: string;
32
+ readonly result: AgentToolResult<unknown> | null;
33
+ readonly isError: boolean;
34
+ }
35
+ export type AgentInternalMessage = AgentInternalUserMessage | AgentInternalAssistantMessage | AgentInternalToolResultMessage;
36
+ export interface AgentInternalUserMessage {
37
+ readonly role: 'user';
38
+ readonly content: string;
39
+ readonly timestamp: number;
40
+ }
41
+ export interface AgentInternalAssistantMessage {
42
+ readonly role: 'assistant';
43
+ readonly stopReason: 'tool_use' | 'end_turn' | 'error';
44
+ readonly errorMessage?: string;
45
+ readonly content: ReadonlyArray<Anthropic.ContentBlock>;
46
+ }
47
+ export interface AgentInternalToolResultMessage {
48
+ readonly role: 'user';
49
+ readonly content: ReadonlyArray<Anthropic.ToolResultBlockParam>;
50
+ }
51
+ export interface AgentLoopOptions {
52
+ readonly systemPrompt: string;
53
+ readonly tools: readonly AgentTool[];
54
+ readonly client: AgentClientInterface;
55
+ readonly modelId: string;
56
+ readonly maxTokens?: number;
57
+ readonly toolExecution?: 'sequential';
58
+ }
59
+ export declare class AgentLoop {
60
+ private readonly _options;
61
+ private readonly _listeners;
62
+ private readonly _steerQueue;
63
+ private _messages;
64
+ private _abortController;
65
+ private _isRunning;
66
+ private _aborted;
67
+ constructor(options: AgentLoopOptions);
68
+ subscribe(listener: (event: AgentEvent) => Promise<void> | void): () => void;
69
+ steer(message: {
70
+ role: 'user';
71
+ content: string;
72
+ timestamp: number;
73
+ }): void;
74
+ abort(): void;
75
+ get state(): {
76
+ readonly messages: ReadonlyArray<AgentInternalMessage>;
77
+ };
78
+ prompt(message: {
79
+ role: 'user';
80
+ content: string;
81
+ timestamp: number;
82
+ }): Promise<void>;
83
+ private _runLoop;
84
+ private _executeTools;
85
+ private _drainSteerQueue;
86
+ private _emitEvent;
87
+ private _appendErrorMessage;
88
+ private _buildApiMessages;
89
+ private _mapStopReason;
90
+ }
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentLoop = void 0;
4
+ class AgentLoop {
5
+ constructor(options) {
6
+ this._listeners = [];
7
+ this._steerQueue = [];
8
+ this._messages = [];
9
+ this._abortController = new AbortController();
10
+ this._isRunning = false;
11
+ this._aborted = false;
12
+ this._options = options;
13
+ }
14
+ subscribe(listener) {
15
+ this._listeners.push(listener);
16
+ return () => {
17
+ const idx = this._listeners.indexOf(listener);
18
+ if (idx !== -1)
19
+ this._listeners.splice(idx, 1);
20
+ };
21
+ }
22
+ steer(message) {
23
+ this._steerQueue.push({ role: 'user', content: message.content, timestamp: message.timestamp });
24
+ }
25
+ abort() {
26
+ this._aborted = true;
27
+ this._abortController.abort();
28
+ }
29
+ get state() {
30
+ return { messages: this._messages };
31
+ }
32
+ async prompt(message) {
33
+ if (!this._aborted) {
34
+ this._abortController = new AbortController();
35
+ }
36
+ this._isRunning = true;
37
+ this._messages.push({ role: 'user', content: message.content, timestamp: message.timestamp });
38
+ try {
39
+ await this._runLoop();
40
+ }
41
+ finally {
42
+ this._isRunning = false;
43
+ }
44
+ }
45
+ async _runLoop() {
46
+ const { client, modelId, systemPrompt, tools, maxTokens = 8192 } = this._options;
47
+ while (true) {
48
+ if (this._aborted || this._abortController.signal.aborted) {
49
+ this._appendErrorMessage('aborted');
50
+ await this._emitEvent({ type: 'agent_end' });
51
+ return;
52
+ }
53
+ const apiMessages = this._buildApiMessages();
54
+ const apiTools = tools.map((t) => ({
55
+ name: t.name,
56
+ description: t.description,
57
+ input_schema: t.inputSchema,
58
+ }));
59
+ let response;
60
+ try {
61
+ response = await client.messages.create({
62
+ model: modelId,
63
+ system: systemPrompt,
64
+ messages: apiMessages,
65
+ tools: apiTools,
66
+ max_tokens: maxTokens,
67
+ }, { signal: this._abortController.signal });
68
+ }
69
+ catch (err) {
70
+ const isAbort = this._abortController.signal.aborted ||
71
+ (err instanceof Error && err.name === 'AbortError');
72
+ const message = err instanceof Error ? err.message : String(err);
73
+ this._appendErrorMessage(isAbort ? 'aborted' : message);
74
+ await this._emitEvent({ type: 'agent_end' });
75
+ return;
76
+ }
77
+ const stopReason = this._mapStopReason(response.stop_reason);
78
+ const assistantMsg = {
79
+ role: 'assistant',
80
+ stopReason,
81
+ content: response.content,
82
+ };
83
+ this._messages.push(assistantMsg);
84
+ const toolUseBlocks = response.content.filter((block) => block.type === 'tool_use');
85
+ if (stopReason === 'tool_use' || toolUseBlocks.length > 0) {
86
+ const toolResults = await this._executeTools(toolUseBlocks);
87
+ const toolResultBlocks = toolResults.map((r) => ({
88
+ type: 'tool_result',
89
+ tool_use_id: r.toolCallId,
90
+ content: r.result?.content.map((c) => ({ type: 'text', text: c.text })) ?? [
91
+ { type: 'text', text: r.isError ? `Tool error: ${r.toolName}` : '(no output)' },
92
+ ],
93
+ is_error: r.isError,
94
+ }));
95
+ this._messages.push({ role: 'user', content: toolResultBlocks });
96
+ await this._emitEvent({ type: 'turn_end', toolResults });
97
+ const steered = this._drainSteerQueue();
98
+ if (steered > 0) {
99
+ continue;
100
+ }
101
+ continue;
102
+ }
103
+ if (stopReason === 'end_turn') {
104
+ await this._emitEvent({ type: 'turn_end', toolResults: [] });
105
+ const steered = this._drainSteerQueue();
106
+ if (steered > 0) {
107
+ continue;
108
+ }
109
+ await this._emitEvent({ type: 'agent_end' });
110
+ return;
111
+ }
112
+ const errorMsg = assistantMsg.errorMessage ?? `Unexpected stop_reason: ${response.stop_reason ?? 'unknown'}`;
113
+ this._appendErrorMessage(errorMsg);
114
+ await this._emitEvent({ type: 'agent_end' });
115
+ return;
116
+ }
117
+ }
118
+ async _executeTools(toolUseBlocks) {
119
+ const results = [];
120
+ for (const block of toolUseBlocks) {
121
+ if (this._abortController.signal.aborted) {
122
+ results.push({
123
+ toolCallId: block.id,
124
+ toolName: block.name,
125
+ result: null,
126
+ isError: true,
127
+ });
128
+ continue;
129
+ }
130
+ const tool = this._options.tools.find((t) => t.name === block.name);
131
+ if (!tool) {
132
+ results.push({
133
+ toolCallId: block.id,
134
+ toolName: block.name,
135
+ result: {
136
+ content: [{ type: 'text', text: `Unknown tool: ${block.name}` }],
137
+ details: null,
138
+ },
139
+ isError: true,
140
+ });
141
+ continue;
142
+ }
143
+ const params = (block.input ?? {});
144
+ const result = await tool.execute(block.id, params);
145
+ results.push({
146
+ toolCallId: block.id,
147
+ toolName: block.name,
148
+ result,
149
+ isError: false,
150
+ });
151
+ }
152
+ return results;
153
+ }
154
+ _drainSteerQueue() {
155
+ let count = 0;
156
+ while (this._steerQueue.length > 0) {
157
+ const msg = this._steerQueue.shift();
158
+ this._messages.push(msg);
159
+ count++;
160
+ }
161
+ return count;
162
+ }
163
+ async _emitEvent(event) {
164
+ for (const listener of this._listeners) {
165
+ await listener(event);
166
+ }
167
+ }
168
+ _appendErrorMessage(errorMessage) {
169
+ this._messages.push({
170
+ role: 'assistant',
171
+ stopReason: 'error',
172
+ errorMessage,
173
+ content: [],
174
+ });
175
+ }
176
+ _buildApiMessages() {
177
+ const result = [];
178
+ for (const msg of this._messages) {
179
+ if (msg.role === 'assistant') {
180
+ const assistantMsg = msg;
181
+ result.push({
182
+ role: 'assistant',
183
+ content: assistantMsg.content,
184
+ });
185
+ }
186
+ else if (msg.role === 'user') {
187
+ const userMsg = msg;
188
+ if (typeof userMsg.content === 'string') {
189
+ result.push({ role: 'user', content: userMsg.content });
190
+ }
191
+ else {
192
+ result.push({
193
+ role: 'user',
194
+ content: userMsg.content,
195
+ });
196
+ }
197
+ }
198
+ }
199
+ return result;
200
+ }
201
+ _mapStopReason(stopReason) {
202
+ switch (stopReason) {
203
+ case 'tool_use':
204
+ return 'tool_use';
205
+ case 'end_turn':
206
+ return 'end_turn';
207
+ case 'max_tokens':
208
+ return 'end_turn';
209
+ default:
210
+ return 'error';
211
+ }
212
+ }
213
+ }
214
+ exports.AgentLoop = AgentLoop;
@@ -1,5 +0,0 @@
1
- export type { Agent, AgentTool, AgentToolResult, AgentEvent, AgentLoopConfig } from '@mariozechner/pi-agent-core';
2
- export type { Model, TSchema } from '@mariozechner/pi-ai';
3
- type AnyModule = Record<string, any>;
4
- export declare function loadPiAi(): Promise<AnyModule>;
5
- export declare function loadPiAgentCore(): Promise<AnyModule>;
@@ -1,65 +1 @@
1
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.loadPiAi = loadPiAi;
37
- exports.loadPiAgentCore = loadPiAgentCore;
38
- const esmImport = new Function('specifier', 'return import(specifier)');
39
- let _piAi = null;
40
- let _piAgentCore = null;
41
- let _bedrockRegistered = false;
42
- async function loadPiAi() {
43
- if (!_piAi) {
44
- _piAi = await esmImport('@mariozechner/pi-ai');
45
- if (!_bedrockRegistered) {
46
- try {
47
- const path = await Promise.resolve().then(() => __importStar(require('node:path')));
48
- const bedrockPath = path.resolve(__dirname, '..', '..', 'node_modules', '@mariozechner', 'pi-ai', 'dist', 'bedrock-provider.js');
49
- const bedrockMod = await esmImport(`file://${bedrockPath}`).catch(() => null);
50
- if (bedrockMod?.bedrockProviderModule && _piAi.setBedrockProviderModule) {
51
- _piAi.setBedrockProviderModule(bedrockMod.bedrockProviderModule);
52
- _bedrockRegistered = true;
53
- }
54
- }
55
- catch {
56
- }
57
- }
58
- }
59
- return _piAi;
60
- }
61
- async function loadPiAgentCore() {
62
- if (!_piAgentCore)
63
- _piAgentCore = await esmImport('@mariozechner/pi-agent-core');
64
- return _piAgentCore;
65
- }
@@ -0,0 +1,2 @@
1
+ export declare const DAEMON_SOUL_DEFAULT = "- Write code that follows the patterns already established in the codebase\n- Never skip tests. Run existing tests before and after changes\n- Prefer small, focused changes over large rewrites\n- If a step asks you to write code, write actual code -- do not write pseudocode or placeholders\n- Commit your work when you complete a logical unit";
2
+ export declare const DAEMON_SOUL_TEMPLATE = "# WorkRail Daemon Soul\n#\n# This file is injected into every WorkRail Auto daemon session system prompt under\n# \"## Agent Rules and Philosophy\". Edit it to customize the agent's behavior for\n# your environment: coding conventions, commit style, tool preferences, etc.\n#\n# Changes take effect on the next daemon session -- no restart required.\n#\n# The defaults below reflect general best practices. Override them freely.\n\n- Write code that follows the patterns already established in the codebase\n- Never skip tests. Run existing tests before and after changes\n- Prefer small, focused changes over large rewrites\n- If a step asks you to write code, write actual code -- do not write pseudocode or placeholders\n- Commit your work when you complete a logical unit\n";