@exaudeus/workrail 3.32.0 → 3.34.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 +488 -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-C1JXnwZS.js} +1 -1
- package/dist/{console → console-ui}/index.html +1 -1
- package/dist/daemon/agent-loop.d.ts +27 -0
- package/dist/daemon/agent-loop.js +39 -1
- package/dist/daemon/daemon-events.d.ts +63 -1
- package/dist/daemon/workflow-runner.d.ts +3 -2
- package/dist/daemon/workflow-runner.js +285 -46
- package/dist/infrastructure/session/HttpServer.js +133 -34
- package/dist/manifest.json +136 -104
- package/dist/mcp/handlers/v2-error-mapping.d.ts +3 -0
- package/dist/mcp/handlers/v2-error-mapping.js +2 -0
- package/dist/mcp/handlers/v2-execution/advance.js +25 -0
- package/dist/mcp/handlers/v2-execution/continue-advance.js +7 -0
- package/dist/mcp/output-schemas.d.ts +30 -30
- package/dist/mcp/transports/fatal-exit.js +4 -0
- package/dist/mcp/transports/http-entry.js +0 -5
- package/dist/mcp/transports/stdio-entry.js +24 -12
- package/dist/mcp/v2/tools.d.ts +4 -4
- package/dist/mcp-server.d.ts +0 -2
- package/dist/mcp-server.js +1 -42
- 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/domain/observation-builder.d.ts +3 -0
- package/dist/v2/durable-core/domain/observation-builder.js +2 -2
- package/dist/v2/durable-core/domain/prompt-renderer.d.ts +2 -1
- package/dist/v2/durable-core/domain/prompt-renderer.js +10 -0
- 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 +185 -10
- package/dist/v2/usecases/console-types.d.ts +8 -0
- package/docs/design/bridge-removal-pr-a-candidates.md +115 -0
- package/docs/design/bridge-removal-pr-a-design-review.md +79 -0
- package/docs/design/bridge-removal-pr-a-implementation-plan.md +203 -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/discovery/design-candidates.md +180 -0
- package/docs/discovery/design-review-findings.md +110 -0
- package/docs/discovery/wr-discovery-goal-reframing.md +303 -0
- package/docs/ideas/backlog.md +627 -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 +59 -8
- package/dist/mcp/transports/bridge-entry.d.ts +0 -102
- package/dist/mcp/transports/bridge-entry.js +0 -454
- package/dist/mcp/transports/bridge-events.d.ts +0 -51
- package/dist/mcp/transports/bridge-events.js +0 -24
- package/dist/mcp/transports/primary-tombstone.d.ts +0 -21
- package/dist/mcp/transports/primary-tombstone.js +0 -51
- /package/dist/{console → console-ui}/assets/index-8dh0Psu-.css +0 -0
package/dist/cli-worktrain.js
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
38
|
};
|
|
@@ -166,4 +199,459 @@ program
|
|
|
166
199
|
});
|
|
167
200
|
(0, interpret_result_js_1.interpretCliResultWithoutDI)(result);
|
|
168
201
|
});
|
|
202
|
+
program
|
|
203
|
+
.command('console')
|
|
204
|
+
.description('Start the WorkRail console UI (reads session files directly, no daemon required)')
|
|
205
|
+
.option('-p, --port <n>', 'Port to bind the console server (default: 3456)', parseInt)
|
|
206
|
+
.option('-w, --workspace <path>', 'Workspace path (reserved for future scoped view)')
|
|
207
|
+
.action(async (options) => {
|
|
208
|
+
const { startStandaloneConsole } = await Promise.resolve().then(() => __importStar(require('./console/standalone-console.js')));
|
|
209
|
+
const result = await startStandaloneConsole({
|
|
210
|
+
port: options.port,
|
|
211
|
+
});
|
|
212
|
+
if (result.kind === 'port_conflict') {
|
|
213
|
+
process.stderr.write(`[Console] Port ${result.port} is already in use. ` +
|
|
214
|
+
`Use --port to choose a different port, or stop the process holding port ${result.port}.\n`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
if (result.kind === 'io_error') {
|
|
218
|
+
process.stderr.write(`[Console] Failed to start: ${result.message}\n`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
const line = '='.repeat(60);
|
|
222
|
+
process.stdout.write(`\n${line}\n`);
|
|
223
|
+
process.stdout.write(`WorkRail Console\n`);
|
|
224
|
+
process.stdout.write(`${line}\n`);
|
|
225
|
+
process.stdout.write(`Console: http://localhost:${result.port}/console\n`);
|
|
226
|
+
process.stdout.write(`Sessions: ${path_1.default.join(os_1.default.homedir(), '.workrail', 'data', 'sessions')}\n`);
|
|
227
|
+
process.stdout.write(`${line}\n\n`);
|
|
228
|
+
process.stdout.write(`Press Ctrl+C to stop.\n`);
|
|
229
|
+
const shutdown = async () => {
|
|
230
|
+
process.stdout.write('\n[Console] Shutting down...\n');
|
|
231
|
+
await result.stop();
|
|
232
|
+
process.exit(0);
|
|
233
|
+
};
|
|
234
|
+
process.on('SIGINT', () => { void shutdown(); });
|
|
235
|
+
process.on('SIGTERM', () => { void shutdown(); });
|
|
236
|
+
});
|
|
237
|
+
program
|
|
238
|
+
.command('daemon')
|
|
239
|
+
.description('Start the WorkTrain daemon, or manage it as a macOS launchd service')
|
|
240
|
+
.option('--install', 'Create the launchd plist and start the daemon service')
|
|
241
|
+
.option('--uninstall', 'Stop the daemon service and remove the launchd plist')
|
|
242
|
+
.option('--status', 'Show the current status of the daemon service')
|
|
243
|
+
.action(async (options) => {
|
|
244
|
+
const { execFile: execFileRaw } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
245
|
+
const execFilePromise = (0, util_1.promisify)(execFileRaw);
|
|
246
|
+
const result = await (0, index_js_1.executeWorktrainDaemonCommand)({
|
|
247
|
+
env: process_1.env,
|
|
248
|
+
platform: process.platform,
|
|
249
|
+
worktrainBinPath: process.argv[1],
|
|
250
|
+
nodeBinPath: process.execPath,
|
|
251
|
+
homedir: os_1.default.homedir,
|
|
252
|
+
joinPath: path_1.default.join,
|
|
253
|
+
mkdir: (p, opts) => fs_1.default.promises.mkdir(p, opts),
|
|
254
|
+
writeFile: (p, content) => fs_1.default.promises.writeFile(p, content, 'utf-8'),
|
|
255
|
+
chmod: (p, mode) => fs_1.default.promises.chmod(p, mode),
|
|
256
|
+
readFile: (p) => fs_1.default.promises.readFile(p, 'utf-8'),
|
|
257
|
+
removeFile: (p) => fs_1.default.promises.unlink(p),
|
|
258
|
+
exists: async (p) => {
|
|
259
|
+
try {
|
|
260
|
+
await fs_1.default.promises.access(p);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
exec: async (command, args) => {
|
|
268
|
+
try {
|
|
269
|
+
const { stdout, stderr } = await execFilePromise(command, args, { encoding: 'utf-8' });
|
|
270
|
+
return { stdout: stdout ?? '', stderr: stderr ?? '', exitCode: 0 };
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
const e = err;
|
|
274
|
+
return {
|
|
275
|
+
stdout: e.stdout ?? '',
|
|
276
|
+
stderr: e.stderr ?? '',
|
|
277
|
+
exitCode: typeof e.code === 'number' ? e.code : 1,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
print: (line) => console.log(line),
|
|
282
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
283
|
+
startDaemon: async () => {
|
|
284
|
+
const { startTriggerListener } = await Promise.resolve().then(() => __importStar(require('./trigger/trigger-listener.js')));
|
|
285
|
+
const { startDaemonConsole } = await Promise.resolve().then(() => __importStar(require('./trigger/daemon-console.js')));
|
|
286
|
+
const { DaemonEventEmitter } = await Promise.resolve().then(() => __importStar(require('./daemon/daemon-events.js')));
|
|
287
|
+
const { initializeContainer, container } = await Promise.resolve().then(() => __importStar(require('./di/container.js')));
|
|
288
|
+
const { DI } = await Promise.resolve().then(() => __importStar(require('./di/tokens.js')));
|
|
289
|
+
await initializeContainer({ runtimeMode: { kind: 'cli' } });
|
|
290
|
+
const { createToolContext } = await Promise.resolve().then(() => __importStar(require('./mcp/server.js')));
|
|
291
|
+
const { requireV2Context } = await Promise.resolve().then(() => __importStar(require('./mcp/types.js')));
|
|
292
|
+
const rawCtx = await createToolContext();
|
|
293
|
+
const v2Guard = requireV2Context(rawCtx);
|
|
294
|
+
if (!v2Guard.ok) {
|
|
295
|
+
console.error('v2 engine not available -- ensure WorkRail is fully initialized');
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
const ctx = v2Guard.ctx;
|
|
299
|
+
const { loadWorkrailConfigFile } = await Promise.resolve().then(() => __importStar(require('./config/config-file.js')));
|
|
300
|
+
const configResult = loadWorkrailConfigFile();
|
|
301
|
+
const configWorkspace = configResult.kind === 'ok' ? configResult.value['WORKRAIL_DEFAULT_WORKSPACE'] : undefined;
|
|
302
|
+
const workspacePath = configWorkspace?.trim() || process.cwd();
|
|
303
|
+
const usesBedrock = !!process.env['AWS_PROFILE'] || !!process.env['AWS_ACCESS_KEY_ID'];
|
|
304
|
+
const apiKey = process.env['ANTHROPIC_API_KEY'];
|
|
305
|
+
if (!usesBedrock && !apiKey) {
|
|
306
|
+
console.error('No LLM credentials found. Set AWS_PROFILE (Bedrock) or ANTHROPIC_API_KEY.');
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
const emitter = new DaemonEventEmitter();
|
|
310
|
+
const handle = await startTriggerListener(ctx, {
|
|
311
|
+
workspacePath,
|
|
312
|
+
apiKey: apiKey,
|
|
313
|
+
env: process.env,
|
|
314
|
+
emitter,
|
|
315
|
+
});
|
|
316
|
+
if (handle === null) {
|
|
317
|
+
console.error('Daemon is disabled. Set WORKRAIL_TRIGGERS_ENABLED=true to enable.');
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
if ('_kind' in handle) {
|
|
321
|
+
console.error('Failed to start daemon:', handle.error);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
console.log(`WorkRail daemon running on port ${handle.port}`);
|
|
325
|
+
console.log(`Workspace: ${workspacePath}`);
|
|
326
|
+
console.log('Waiting for webhook triggers...');
|
|
327
|
+
const pkg = require('../package.json');
|
|
328
|
+
const workflowService = container.resolve(DI.Services.Workflow);
|
|
329
|
+
const consoleResult = await startDaemonConsole(ctx, {
|
|
330
|
+
triggerRouter: handle.router,
|
|
331
|
+
serverVersion: pkg.version,
|
|
332
|
+
workflowService,
|
|
333
|
+
});
|
|
334
|
+
let consoleHandle = null;
|
|
335
|
+
if (consoleResult.kind === 'ok') {
|
|
336
|
+
consoleHandle = consoleResult.value;
|
|
337
|
+
}
|
|
338
|
+
else if (consoleResult.error.kind === 'port_conflict') {
|
|
339
|
+
console.warn(`[DaemonConsole] Port ${consoleResult.error.port} is already held. ` +
|
|
340
|
+
`The daemon is running but the console is unavailable.`);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
console.warn(`[DaemonConsole] Could not start console: ${consoleResult.error.message}`);
|
|
344
|
+
}
|
|
345
|
+
await new Promise((resolve) => {
|
|
346
|
+
const shutdown = async () => {
|
|
347
|
+
console.log('\nShutting down daemon...');
|
|
348
|
+
if (consoleHandle) {
|
|
349
|
+
await consoleHandle.stop();
|
|
350
|
+
}
|
|
351
|
+
await handle.stop();
|
|
352
|
+
resolve();
|
|
353
|
+
};
|
|
354
|
+
process.once('SIGINT', () => void shutdown());
|
|
355
|
+
process.once('SIGTERM', () => void shutdown());
|
|
356
|
+
});
|
|
357
|
+
},
|
|
358
|
+
}, {
|
|
359
|
+
install: options.install,
|
|
360
|
+
uninstall: options.uninstall,
|
|
361
|
+
status: options.status,
|
|
362
|
+
});
|
|
363
|
+
(0, interpret_result_js_1.interpretCliResultWithoutDI)(result);
|
|
364
|
+
});
|
|
365
|
+
function formatDaemonEventLine(raw) {
|
|
366
|
+
let obj;
|
|
367
|
+
try {
|
|
368
|
+
obj = JSON.parse(raw);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
const ts = typeof obj['ts'] === 'number'
|
|
374
|
+
? new Date(obj['ts']).toISOString().replace('T', ' ').slice(0, 23)
|
|
375
|
+
: '?';
|
|
376
|
+
const kind = typeof obj['kind'] === 'string' ? obj['kind'] : 'unknown';
|
|
377
|
+
const sessionId = typeof obj['sessionId'] === 'string' ? obj['sessionId'].slice(0, 8) : null;
|
|
378
|
+
const prefix = sessionId ? `[${ts}] [${sessionId}] ${kind}` : `[${ts}] ${kind}`;
|
|
379
|
+
switch (kind) {
|
|
380
|
+
case 'agent_stuck':
|
|
381
|
+
return `${prefix} *** STUCK: ${obj['reason'] ?? '?'} -- ${String(obj['detail'] ?? '').slice(0, 100)}`;
|
|
382
|
+
case 'llm_turn_started':
|
|
383
|
+
return `${prefix} msgs=${obj['messageCount'] ?? '?'}`;
|
|
384
|
+
case 'llm_turn_completed':
|
|
385
|
+
return `${prefix} stop=${obj['stopReason'] ?? '?'} in=${obj['inputTokens'] ?? '?'} out=${obj['outputTokens'] ?? '?'} tools=[${Array.isArray(obj['toolNamesRequested']) ? obj['toolNamesRequested'].join(',') : ''}]`;
|
|
386
|
+
case 'tool_call_started':
|
|
387
|
+
return `${prefix} tool=${obj['toolName'] ?? '?'} args=${String(obj['argsSummary'] ?? '').slice(0, 80)}`;
|
|
388
|
+
case 'tool_call_completed':
|
|
389
|
+
return `${prefix} tool=${obj['toolName'] ?? '?'} ${obj['durationMs'] ?? '?'}ms result=${String(obj['resultSummary'] ?? '').slice(0, 60)}`;
|
|
390
|
+
case 'tool_call_failed':
|
|
391
|
+
return `${prefix} tool=${obj['toolName'] ?? '?'} ${obj['durationMs'] ?? '?'}ms err=${String(obj['errorMessage'] ?? '').slice(0, 80)}`;
|
|
392
|
+
case 'tool_called':
|
|
393
|
+
return `${prefix} tool=${obj['toolName'] ?? '?'} ${obj['summary'] ? String(obj['summary']).slice(0, 80) : ''}`;
|
|
394
|
+
case 'tool_error':
|
|
395
|
+
return `${prefix} tool=${obj['toolName'] ?? '?'} err=${String(obj['error'] ?? '').slice(0, 80)}`;
|
|
396
|
+
case 'session_started':
|
|
397
|
+
return `${prefix} workflow=${obj['workflowId'] ?? '?'} workspace=${obj['workspacePath'] ?? '?'}`;
|
|
398
|
+
case 'session_completed': {
|
|
399
|
+
const outcome = obj['outcome'];
|
|
400
|
+
const detail = obj['detail'] ? ` (${obj['detail']})` : '';
|
|
401
|
+
if (outcome === 'success') {
|
|
402
|
+
return `${prefix} workflow=${obj['workflowId'] ?? '?'} -- session complete${detail}`;
|
|
403
|
+
}
|
|
404
|
+
else if (outcome === 'error') {
|
|
405
|
+
return `${prefix} workflow=${obj['workflowId'] ?? '?'} -- session FAILED${detail}`;
|
|
406
|
+
}
|
|
407
|
+
else if (outcome === 'timeout') {
|
|
408
|
+
return `${prefix} workflow=${obj['workflowId'] ?? '?'} -- session TIMEOUT${detail}`;
|
|
409
|
+
}
|
|
410
|
+
return `${prefix} workflow=${obj['workflowId'] ?? '?'} outcome=${outcome ?? '?'}${detail}`;
|
|
411
|
+
}
|
|
412
|
+
case 'step_advanced':
|
|
413
|
+
return `${prefix} -> step advanced`;
|
|
414
|
+
case 'issue_reported': {
|
|
415
|
+
const severity = obj['severity'];
|
|
416
|
+
const summary = String(obj['summary'] ?? '').slice(0, 100);
|
|
417
|
+
if (severity === 'fatal') {
|
|
418
|
+
return `${prefix} FATAL: ${summary}`;
|
|
419
|
+
}
|
|
420
|
+
else if (severity === 'error') {
|
|
421
|
+
return `${prefix} ERROR: ${summary}`;
|
|
422
|
+
}
|
|
423
|
+
return `${prefix} severity=${severity ?? '?'} ${summary}`;
|
|
424
|
+
}
|
|
425
|
+
default:
|
|
426
|
+
return `${prefix} ${JSON.stringify(obj).slice(0, 120)}`;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
program
|
|
430
|
+
.command('logs')
|
|
431
|
+
.description('Read and display the WorkRail daemon event log. Use --follow to stream new events in real time.')
|
|
432
|
+
.option('--follow', 'Continuously poll the log file for new events (like tail -f)')
|
|
433
|
+
.option('--session <id>', 'Filter events by sessionId (UUID prefix) or workrailSessionId (sess_xxx prefix)')
|
|
434
|
+
.action(async (options) => {
|
|
435
|
+
const eventsDir = path_1.default.join(os_1.default.homedir(), '.workrail', 'events', 'daemon');
|
|
436
|
+
function todayFilePath() {
|
|
437
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
438
|
+
return path_1.default.join(eventsDir, `${date}.jsonl`);
|
|
439
|
+
}
|
|
440
|
+
function readNewLines(filePath, fromOffset) {
|
|
441
|
+
let stat;
|
|
442
|
+
try {
|
|
443
|
+
stat = fs_1.default.statSync(filePath);
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
if (stat.size <= fromOffset) {
|
|
449
|
+
return { lines: [], newOffset: fromOffset };
|
|
450
|
+
}
|
|
451
|
+
const fd = fs_1.default.openSync(filePath, 'r');
|
|
452
|
+
try {
|
|
453
|
+
const len = stat.size - fromOffset;
|
|
454
|
+
const buf = Buffer.alloc(len);
|
|
455
|
+
fs_1.default.readSync(fd, buf, 0, len, fromOffset);
|
|
456
|
+
const text = buf.toString('utf8');
|
|
457
|
+
const lines = text.split('\n').filter((l) => l.trim().length > 0);
|
|
458
|
+
return { lines, newOffset: stat.size };
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
fs_1.default.closeSync(fd);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function printLines(lines) {
|
|
465
|
+
for (const line of lines) {
|
|
466
|
+
if (options.session) {
|
|
467
|
+
try {
|
|
468
|
+
const obj = JSON.parse(line);
|
|
469
|
+
const sid = typeof obj['sessionId'] === 'string' ? obj['sessionId'] : '';
|
|
470
|
+
const wrid = typeof obj['workrailSessionId'] === 'string' ? obj['workrailSessionId'] : '';
|
|
471
|
+
const matchesSession = sid.startsWith(options.session) || sid === options.session ||
|
|
472
|
+
wrid.startsWith(options.session) || wrid === options.session;
|
|
473
|
+
if (!matchesSession)
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const formatted = formatDaemonEventLine(line);
|
|
481
|
+
if (formatted !== null) {
|
|
482
|
+
process.stdout.write(formatted + '\n');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const filePath = todayFilePath();
|
|
487
|
+
if (!options.follow) {
|
|
488
|
+
const result = readNewLines(filePath, 0);
|
|
489
|
+
if (result === null) {
|
|
490
|
+
process.stdout.write(`No events yet. Is the daemon running? (Expected: ${filePath})\n`);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
printLines(result.lines);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
process.once('SIGINT', () => process.exit(0));
|
|
497
|
+
let currentFilePath = filePath;
|
|
498
|
+
let offset = 0;
|
|
499
|
+
const initial = readNewLines(currentFilePath, 0);
|
|
500
|
+
if (initial !== null) {
|
|
501
|
+
printLines(initial.lines);
|
|
502
|
+
offset = initial.newOffset;
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
process.stdout.write(`Waiting for events... (${currentFilePath})\n`);
|
|
506
|
+
}
|
|
507
|
+
while (true) {
|
|
508
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
509
|
+
const newFilePath = todayFilePath();
|
|
510
|
+
if (newFilePath !== currentFilePath) {
|
|
511
|
+
currentFilePath = newFilePath;
|
|
512
|
+
offset = 0;
|
|
513
|
+
}
|
|
514
|
+
const result = readNewLines(currentFilePath, offset);
|
|
515
|
+
if (result !== null && result.lines.length > 0) {
|
|
516
|
+
printLines(result.lines);
|
|
517
|
+
offset = result.newOffset;
|
|
518
|
+
}
|
|
519
|
+
else if (result !== null) {
|
|
520
|
+
offset = result.newOffset;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
program
|
|
525
|
+
.command('status <sessionId>')
|
|
526
|
+
.description('Print a health summary for a daemon session. Accepts sessionId (UUID prefix) or workrailSessionId (sess_xxx).')
|
|
527
|
+
.action(async (sessionId) => {
|
|
528
|
+
const eventsDir = path_1.default.join(os_1.default.homedir(), '.workrail', 'events', 'daemon');
|
|
529
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
530
|
+
const filePath = path_1.default.join(eventsDir, `${date}.jsonl`);
|
|
531
|
+
let raw;
|
|
532
|
+
try {
|
|
533
|
+
raw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
process.stdout.write(`No events today. Is the daemon running? (Expected: ${filePath})\n`);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
let workflowId = null;
|
|
540
|
+
let firstTs = null;
|
|
541
|
+
let lastTs = null;
|
|
542
|
+
let llmTurns = 0;
|
|
543
|
+
let stepAdvances = 0;
|
|
544
|
+
let totalToolCalls = 0;
|
|
545
|
+
let failedToolCalls = 0;
|
|
546
|
+
let fatalIssues = 0;
|
|
547
|
+
let errorIssues = 0;
|
|
548
|
+
let warnIssues = 0;
|
|
549
|
+
let sessionOutcome = null;
|
|
550
|
+
let lastToolName = null;
|
|
551
|
+
let lastToolArgs = null;
|
|
552
|
+
let stuckCount = 0;
|
|
553
|
+
let isLive = true;
|
|
554
|
+
for (const line of raw.split('\n')) {
|
|
555
|
+
if (!line.trim())
|
|
556
|
+
continue;
|
|
557
|
+
let obj;
|
|
558
|
+
try {
|
|
559
|
+
obj = JSON.parse(line);
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const sid = typeof obj['sessionId'] === 'string' ? obj['sessionId'] : '';
|
|
565
|
+
const wrid = typeof obj['workrailSessionId'] === 'string' ? obj['workrailSessionId'] : '';
|
|
566
|
+
const matches = sid.startsWith(sessionId) || sid === sessionId ||
|
|
567
|
+
wrid.startsWith(sessionId) || wrid === sessionId;
|
|
568
|
+
if (!matches)
|
|
569
|
+
continue;
|
|
570
|
+
const ts = typeof obj['ts'] === 'number' ? obj['ts'] : null;
|
|
571
|
+
if (ts !== null) {
|
|
572
|
+
if (firstTs === null || ts < firstTs)
|
|
573
|
+
firstTs = ts;
|
|
574
|
+
if (lastTs === null || ts > lastTs)
|
|
575
|
+
lastTs = ts;
|
|
576
|
+
}
|
|
577
|
+
const kind = typeof obj['kind'] === 'string' ? obj['kind'] : '';
|
|
578
|
+
switch (kind) {
|
|
579
|
+
case 'session_started':
|
|
580
|
+
workflowId = typeof obj['workflowId'] === 'string' ? obj['workflowId'] : null;
|
|
581
|
+
break;
|
|
582
|
+
case 'llm_turn_completed':
|
|
583
|
+
llmTurns++;
|
|
584
|
+
break;
|
|
585
|
+
case 'step_advanced':
|
|
586
|
+
stepAdvances++;
|
|
587
|
+
break;
|
|
588
|
+
case 'tool_call_started':
|
|
589
|
+
totalToolCalls++;
|
|
590
|
+
lastToolName = typeof obj['toolName'] === 'string' ? obj['toolName'] : null;
|
|
591
|
+
lastToolArgs = typeof obj['argsSummary'] === 'string' ? String(obj['argsSummary']).slice(0, 60) : null;
|
|
592
|
+
break;
|
|
593
|
+
case 'tool_call_failed':
|
|
594
|
+
failedToolCalls++;
|
|
595
|
+
break;
|
|
596
|
+
case 'issue_reported': {
|
|
597
|
+
const severity = obj['severity'];
|
|
598
|
+
if (severity === 'fatal')
|
|
599
|
+
fatalIssues++;
|
|
600
|
+
else if (severity === 'error')
|
|
601
|
+
errorIssues++;
|
|
602
|
+
else if (severity === 'warn')
|
|
603
|
+
warnIssues++;
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
case 'agent_stuck':
|
|
607
|
+
stuckCount++;
|
|
608
|
+
break;
|
|
609
|
+
case 'session_completed':
|
|
610
|
+
sessionOutcome = typeof obj['outcome'] === 'string' ? obj['outcome'] : null;
|
|
611
|
+
isLive = false;
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (firstTs === null) {
|
|
616
|
+
process.stdout.write(`No events found for session: ${sessionId}\n`);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const durationMs = (lastTs ?? firstTs) - firstTs;
|
|
620
|
+
const durationSec = Math.floor(durationMs / 1000);
|
|
621
|
+
const durationMin = Math.floor(durationSec / 60);
|
|
622
|
+
const durationRemSec = durationSec % 60;
|
|
623
|
+
const durationStr = durationMin > 0
|
|
624
|
+
? `${durationMin}m ${durationRemSec}s`
|
|
625
|
+
: `${durationSec}s`;
|
|
626
|
+
const avgTurnSec = llmTurns > 0 ? (durationMs / llmTurns / 1000).toFixed(1) : '?';
|
|
627
|
+
const failRate = totalToolCalls > 0 ? ((failedToolCalls / totalToolCalls) * 100).toFixed(1) : '0';
|
|
628
|
+
const sessionStatus = sessionOutcome !== null
|
|
629
|
+
? sessionOutcome.toUpperCase()
|
|
630
|
+
: (isLive ? 'RUNNING' : 'UNKNOWN');
|
|
631
|
+
const issueStr = (fatalIssues + errorIssues + warnIssues) > 0
|
|
632
|
+
? `${fatalIssues + errorIssues + warnIssues} (${fatalIssues} fatal, ${errorIssues} error, ${warnIssues} warn)`
|
|
633
|
+
: '0';
|
|
634
|
+
const lastActivityStr = lastTs !== null
|
|
635
|
+
? `${lastToolName ?? 'unknown'} ${lastToolArgs ? `"${lastToolArgs}"` : ''} ${Math.round((Date.now() - lastTs) / 1000)}s ago`
|
|
636
|
+
: 'unknown';
|
|
637
|
+
process.stdout.write(`\nSession: ${sessionId} [${sessionStatus}]\n`);
|
|
638
|
+
if (workflowId)
|
|
639
|
+
process.stdout.write(`Workflow: ${workflowId}\n`);
|
|
640
|
+
process.stdout.write(`Duration: ${durationStr}\n`);
|
|
641
|
+
process.stdout.write(`LLM turns: ${llmTurns}${llmTurns > 0 ? ` (avg ${avgTurnSec}s each)` : ''}\n`);
|
|
642
|
+
process.stdout.write(`Step advances: ${stepAdvances}\n`);
|
|
643
|
+
process.stdout.write(`Tool calls: ${totalToolCalls} (${failedToolCalls} failed, ${failRate}% failure rate)\n`);
|
|
644
|
+
process.stdout.write(`Issues reported: ${issueStr}\n`);
|
|
645
|
+
process.stdout.write(`Last activity: ${lastActivityStr}\n`);
|
|
646
|
+
if (stuckCount > 0) {
|
|
647
|
+
process.stdout.write(`*** WARNING: ${stuckCount} stuck signal(s) detected\n`);
|
|
648
|
+
}
|
|
649
|
+
if (fatalIssues > 0) {
|
|
650
|
+
process.stdout.write(`*** WARNING: ${fatalIssues} FATAL issue(s) reported\n`);
|
|
651
|
+
}
|
|
652
|
+
if (llmTurns >= 10 && stepAdvances === 0) {
|
|
653
|
+
process.stdout.write(`*** WARNING: ${llmTurns} turns with 0 step advances (possible stuck)\n`);
|
|
654
|
+
}
|
|
655
|
+
process.stdout.write('\n');
|
|
656
|
+
});
|
|
169
657
|
program.parse();
|
package/dist/cli.js
CHANGED
|
@@ -190,7 +190,6 @@ program
|
|
|
190
190
|
.option('-w, --workspace <path>', 'Path to workspace containing triggers.yml')
|
|
191
191
|
.action(async (options) => {
|
|
192
192
|
const { startTriggerListener } = await Promise.resolve().then(() => __importStar(require('./trigger/trigger-listener.js')));
|
|
193
|
-
const { startDaemonConsole } = await Promise.resolve().then(() => __importStar(require('./trigger/daemon-console.js')));
|
|
194
193
|
const { DaemonEventEmitter } = await Promise.resolve().then(() => __importStar(require('./daemon/daemon-events.js')));
|
|
195
194
|
await (0, container_js_1.initializeContainer)({ runtimeMode: { kind: 'cli' } });
|
|
196
195
|
const { createToolContext } = await Promise.resolve().then(() => __importStar(require('./mcp/server.js')));
|
|
@@ -235,29 +234,9 @@ program
|
|
|
235
234
|
console.log(`WorkRail daemon running on port ${handle.port}`);
|
|
236
235
|
console.log(`Workspace: ${workspacePath}`);
|
|
237
236
|
console.log('Waiting for webhook triggers...');
|
|
238
|
-
|
|
239
|
-
const consoleResult = await startDaemonConsole(ctx, {
|
|
240
|
-
triggerRouter: handle.router,
|
|
241
|
-
serverVersion: pkg.version,
|
|
242
|
-
workflowService: rawCtx.workflowService,
|
|
243
|
-
});
|
|
244
|
-
let consoleHandle = null;
|
|
245
|
-
if (consoleResult.kind === 'ok') {
|
|
246
|
-
consoleHandle = consoleResult.value;
|
|
247
|
-
}
|
|
248
|
-
else if (consoleResult.error.kind === 'port_conflict') {
|
|
249
|
-
console.warn(`[DaemonConsole] Port ${consoleResult.error.port} is already held (likely by an MCP server). ` +
|
|
250
|
-
`The daemon is running but the console is unavailable. ` +
|
|
251
|
-
`Restart the MCP server while the daemon is running to enable the daemon console.`);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
console.warn(`[DaemonConsole] Could not start console: ${consoleResult.error.message}`);
|
|
255
|
-
}
|
|
237
|
+
console.log('Run "worktrain console" in a separate terminal to start the console UI.');
|
|
256
238
|
const shutdown = async () => {
|
|
257
239
|
console.log('\nShutting down daemon...');
|
|
258
|
-
if (consoleHandle) {
|
|
259
|
-
await consoleHandle.stop();
|
|
260
|
-
}
|
|
261
240
|
await handle.stop();
|
|
262
241
|
process.exit(0);
|
|
263
242
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface StandaloneConsoleHandle {
|
|
2
|
+
readonly port: number;
|
|
3
|
+
stop(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export type StandaloneConsoleError = {
|
|
6
|
+
readonly kind: 'port_conflict';
|
|
7
|
+
readonly port: number;
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: 'io_error';
|
|
10
|
+
readonly message: string;
|
|
11
|
+
};
|
|
12
|
+
export type StandaloneConsoleResult = ({
|
|
13
|
+
readonly kind: 'ok';
|
|
14
|
+
} & StandaloneConsoleHandle) | ({
|
|
15
|
+
readonly kind: 'port_conflict';
|
|
16
|
+
} & {
|
|
17
|
+
readonly port: number;
|
|
18
|
+
}) | ({
|
|
19
|
+
readonly kind: 'io_error';
|
|
20
|
+
} & {
|
|
21
|
+
readonly message: string;
|
|
22
|
+
});
|
|
23
|
+
export interface StartStandaloneConsoleOptions {
|
|
24
|
+
readonly port?: number;
|
|
25
|
+
readonly dataDir?: string;
|
|
26
|
+
readonly lockFilePath?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function startStandaloneConsole(options?: StartStandaloneConsoleOptions): Promise<StandaloneConsoleResult>;
|