@exaudeus/workrail 3.25.0 → 3.26.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.
- package/dist/cli/commands/index.d.ts +5 -0
- package/dist/cli/commands/index.js +12 -1
- package/dist/cli/commands/worktrain-await.d.ts +35 -0
- package/dist/cli/commands/worktrain-await.js +207 -0
- package/dist/cli/commands/worktrain-inbox.d.ts +23 -0
- package/dist/cli/commands/worktrain-inbox.js +82 -0
- package/dist/cli/commands/worktrain-init.d.ts +23 -0
- package/dist/cli/commands/worktrain-init.js +338 -0
- package/dist/cli/commands/worktrain-spawn.d.ts +28 -0
- package/dist/cli/commands/worktrain-spawn.js +106 -0
- package/dist/cli/commands/worktrain-tell.d.ts +25 -0
- package/dist/cli/commands/worktrain-tell.js +32 -0
- package/dist/cli-worktrain.d.ts +2 -0
- package/dist/cli-worktrain.js +169 -0
- package/dist/cli.js +13 -3
- package/dist/config/config-file.d.ts +2 -0
- package/dist/config/config-file.js +55 -0
- package/dist/daemon/agent-loop.d.ts +90 -0
- package/dist/daemon/agent-loop.js +214 -0
- package/dist/daemon/pi-mono-loader.d.ts +0 -5
- package/dist/daemon/pi-mono-loader.js +0 -64
- package/dist/daemon/soul-template.d.ts +2 -0
- package/dist/daemon/soul-template.js +22 -0
- package/dist/daemon/workflow-runner.d.ts +24 -2
- package/dist/daemon/workflow-runner.js +244 -120
- package/dist/manifest.json +147 -51
- package/dist/mcp/output-schemas.d.ts +154 -154
- package/dist/mcp/transports/bridge-entry.js +20 -2
- package/dist/mcp/transports/bridge-events.d.ts +34 -0
- package/dist/mcp/transports/bridge-events.js +24 -0
- package/dist/mcp/transports/fatal-exit.d.ts +5 -0
- package/dist/mcp/transports/fatal-exit.js +82 -0
- package/dist/mcp/transports/http-entry.js +3 -0
- package/dist/mcp/transports/stdio-entry.js +3 -7
- package/dist/mcp/v2/tools.d.ts +7 -7
- package/dist/trigger/delivery-action.d.ts +37 -0
- package/dist/trigger/delivery-action.js +204 -0
- package/dist/trigger/delivery-client.d.ts +11 -0
- package/dist/trigger/delivery-client.js +27 -0
- package/dist/trigger/trigger-listener.d.ts +2 -0
- package/dist/trigger/trigger-listener.js +12 -2
- package/dist/trigger/trigger-router.d.ts +8 -2
- package/dist/trigger/trigger-router.js +164 -6
- package/dist/trigger/trigger-store.d.ts +11 -3
- package/dist/trigger/trigger-store.js +254 -13
- package/dist/trigger/types.d.ts +24 -0
- package/dist/trigger/types.js +4 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +22 -22
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +114 -114
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +454 -454
- package/dist/v2/durable-core/schemas/session/blockers.d.ts +14 -14
- package/dist/v2/durable-core/schemas/session/events.d.ts +93 -93
- package/dist/v2/durable-core/schemas/session/gaps.d.ts +2 -2
- package/dist/v2/durable-core/schemas/session/validation-event.d.ts +4 -4
- package/dist/v2/usecases/console-routes.js +33 -3
- package/package.json +6 -4
- package/spec/workflow-tags.json +1 -0
- package/workflows/classify-task-workflow.json +68 -0
- package/workflows/coding-task-workflow-agentic.lean.v2.json +43 -13
|
@@ -6,3 +6,8 @@ export { executeStartCommand, type StartCommandDeps, type RpcServer } from './st
|
|
|
6
6
|
export { executeCleanupCommand, type CleanupCommandDeps, type CleanupCommandOptions } from './cleanup.js';
|
|
7
7
|
export { executeVersionCommand, type VersionCommandDeps } from './version.js';
|
|
8
8
|
export { executeMigrateCommand, migrateWorkflow, migrateWorkflowFile, detectWorkflowVersion, type MigrationResult, type FileMigrationResult, type MigrateCommandOptions, type MigrateFileDeps, type MigrateFileOptions, } from './migrate.js';
|
|
9
|
+
export { executeWorktrainInitCommand, type WorktrainInitCommandDeps, type WorktrainInitCommandOpts, } from './worktrain-init.js';
|
|
10
|
+
export { executeWorktrainTellCommand, type Priority, type QueuedMessage, type WorktrainTellCommandDeps, type WorktrainTellCommandOpts, } from './worktrain-tell.js';
|
|
11
|
+
export { executeWorktrainInboxCommand, type OutboxMessage, type InboxCursor, type WorktrainInboxCommandDeps, type WorktrainInboxCommandOpts, } from './worktrain-inbox.js';
|
|
12
|
+
export { executeWorktrainSpawnCommand, type WorktrainSpawnCommandDeps, type WorktrainSpawnCommandOpts, } from './worktrain-spawn.js';
|
|
13
|
+
export { executeWorktrainAwaitCommand, parseDurationMs, type WorktrainAwaitCommandDeps, type WorktrainAwaitCommandOpts, type SessionOutcome, type SessionResult, type AwaitResult, } from './worktrain-await.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.detectWorkflowVersion = exports.migrateWorkflowFile = exports.migrateWorkflow = exports.executeMigrateCommand = exports.executeVersionCommand = exports.executeCleanupCommand = exports.executeStartCommand = exports.executeValidateCommand = exports.executeListCommand = exports.getWorkflowSources = exports.executeSourcesCommand = exports.executeInitConfigCommand = exports.executeInitCommand = void 0;
|
|
3
|
+
exports.parseDurationMs = exports.executeWorktrainAwaitCommand = exports.executeWorktrainSpawnCommand = exports.executeWorktrainInboxCommand = exports.executeWorktrainTellCommand = exports.executeWorktrainInitCommand = exports.detectWorkflowVersion = exports.migrateWorkflowFile = exports.migrateWorkflow = exports.executeMigrateCommand = exports.executeVersionCommand = exports.executeCleanupCommand = exports.executeStartCommand = exports.executeValidateCommand = exports.executeListCommand = exports.getWorkflowSources = exports.executeSourcesCommand = exports.executeInitConfigCommand = exports.executeInitCommand = void 0;
|
|
4
4
|
var init_js_1 = require("./init.js");
|
|
5
5
|
Object.defineProperty(exports, "executeInitCommand", { enumerable: true, get: function () { return init_js_1.executeInitCommand; } });
|
|
6
6
|
Object.defineProperty(exports, "executeInitConfigCommand", { enumerable: true, get: function () { return init_js_1.executeInitConfigCommand; } });
|
|
@@ -22,3 +22,14 @@ Object.defineProperty(exports, "executeMigrateCommand", { enumerable: true, get:
|
|
|
22
22
|
Object.defineProperty(exports, "migrateWorkflow", { enumerable: true, get: function () { return migrate_js_1.migrateWorkflow; } });
|
|
23
23
|
Object.defineProperty(exports, "migrateWorkflowFile", { enumerable: true, get: function () { return migrate_js_1.migrateWorkflowFile; } });
|
|
24
24
|
Object.defineProperty(exports, "detectWorkflowVersion", { enumerable: true, get: function () { return migrate_js_1.detectWorkflowVersion; } });
|
|
25
|
+
var worktrain_init_js_1 = require("./worktrain-init.js");
|
|
26
|
+
Object.defineProperty(exports, "executeWorktrainInitCommand", { enumerable: true, get: function () { return worktrain_init_js_1.executeWorktrainInitCommand; } });
|
|
27
|
+
var worktrain_tell_js_1 = require("./worktrain-tell.js");
|
|
28
|
+
Object.defineProperty(exports, "executeWorktrainTellCommand", { enumerable: true, get: function () { return worktrain_tell_js_1.executeWorktrainTellCommand; } });
|
|
29
|
+
var worktrain_inbox_js_1 = require("./worktrain-inbox.js");
|
|
30
|
+
Object.defineProperty(exports, "executeWorktrainInboxCommand", { enumerable: true, get: function () { return worktrain_inbox_js_1.executeWorktrainInboxCommand; } });
|
|
31
|
+
var worktrain_spawn_js_1 = require("./worktrain-spawn.js");
|
|
32
|
+
Object.defineProperty(exports, "executeWorktrainSpawnCommand", { enumerable: true, get: function () { return worktrain_spawn_js_1.executeWorktrainSpawnCommand; } });
|
|
33
|
+
var worktrain_await_js_1 = require("./worktrain-await.js");
|
|
34
|
+
Object.defineProperty(exports, "executeWorktrainAwaitCommand", { enumerable: true, get: function () { return worktrain_await_js_1.executeWorktrainAwaitCommand; } });
|
|
35
|
+
Object.defineProperty(exports, "parseDurationMs", { enumerable: true, get: function () { return worktrain_await_js_1.parseDurationMs; } });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CliResult } from '../types/cli-result.js';
|
|
2
|
+
export interface WorktrainAwaitCommandDeps {
|
|
3
|
+
readonly fetch: (url: string) => Promise<{
|
|
4
|
+
readonly ok: boolean;
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly json: () => Promise<unknown>;
|
|
7
|
+
}>;
|
|
8
|
+
readonly readFile: (path: string) => Promise<string>;
|
|
9
|
+
readonly stdout: (line: string) => void;
|
|
10
|
+
readonly stderr: (line: string) => void;
|
|
11
|
+
readonly homedir: () => string;
|
|
12
|
+
readonly joinPath: (...paths: string[]) => string;
|
|
13
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
14
|
+
readonly now: () => number;
|
|
15
|
+
}
|
|
16
|
+
export interface WorktrainAwaitCommandOpts {
|
|
17
|
+
readonly sessions: string;
|
|
18
|
+
readonly mode?: 'all' | 'any';
|
|
19
|
+
readonly timeout?: string;
|
|
20
|
+
readonly port?: number;
|
|
21
|
+
readonly pollInterval?: number;
|
|
22
|
+
}
|
|
23
|
+
export type SessionOutcome = 'success' | 'failed' | 'timeout' | 'not_found' | 'not_awaited';
|
|
24
|
+
export interface SessionResult {
|
|
25
|
+
readonly handle: string;
|
|
26
|
+
readonly outcome: SessionOutcome;
|
|
27
|
+
readonly status: string | null;
|
|
28
|
+
readonly durationMs: number;
|
|
29
|
+
}
|
|
30
|
+
export interface AwaitResult {
|
|
31
|
+
readonly results: readonly SessionResult[];
|
|
32
|
+
readonly allSucceeded: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function parseDurationMs(raw: string): number | null;
|
|
35
|
+
export declare function executeWorktrainAwaitCommand(deps: WorktrainAwaitCommandDeps, opts: WorktrainAwaitCommandOpts): Promise<CliResult>;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseDurationMs = parseDurationMs;
|
|
4
|
+
exports.executeWorktrainAwaitCommand = executeWorktrainAwaitCommand;
|
|
5
|
+
const cli_result_js_1 = require("../types/cli-result.js");
|
|
6
|
+
const DEFAULT_CONSOLE_PORT = 3456;
|
|
7
|
+
const LOCK_FILE_NAME = 'dashboard.lock';
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
9
|
+
const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
10
|
+
function parseDurationMs(raw) {
|
|
11
|
+
const trimmed = raw.trim();
|
|
12
|
+
if (!trimmed)
|
|
13
|
+
return null;
|
|
14
|
+
if (/^\d+$/.test(trimmed)) {
|
|
15
|
+
const n = parseInt(trimmed, 10);
|
|
16
|
+
return Number.isFinite(n) && n > 0 ? n * 1000 : null;
|
|
17
|
+
}
|
|
18
|
+
const match = /^(\d+)(s|m|h)$/i.exec(trimmed);
|
|
19
|
+
if (!match)
|
|
20
|
+
return null;
|
|
21
|
+
const n = parseInt(match[1] ?? '0', 10);
|
|
22
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
23
|
+
return null;
|
|
24
|
+
const unit = (match[2] ?? '').toLowerCase();
|
|
25
|
+
if (unit === 's')
|
|
26
|
+
return n * 1000;
|
|
27
|
+
if (unit === 'm')
|
|
28
|
+
return n * 60 * 1000;
|
|
29
|
+
if (unit === 'h')
|
|
30
|
+
return n * 60 * 60 * 1000;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
async function discoverConsolePort(deps, portOverride) {
|
|
34
|
+
if (portOverride !== undefined && portOverride > 0) {
|
|
35
|
+
return portOverride;
|
|
36
|
+
}
|
|
37
|
+
const lockPath = deps.joinPath(deps.homedir(), '.workrail', LOCK_FILE_NAME);
|
|
38
|
+
try {
|
|
39
|
+
const raw = await deps.readFile(lockPath);
|
|
40
|
+
const parsed = JSON.parse(raw);
|
|
41
|
+
if (typeof parsed.port === 'number' && parsed.port > 0) {
|
|
42
|
+
return parsed.port;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
}
|
|
47
|
+
return DEFAULT_CONSOLE_PORT;
|
|
48
|
+
}
|
|
49
|
+
const TERMINAL_STATUSES = new Set(['complete', 'complete_with_gaps', 'blocked', 'dormant']);
|
|
50
|
+
const SUCCESS_STATUSES = new Set(['complete', 'complete_with_gaps']);
|
|
51
|
+
function statusToOutcome(status) {
|
|
52
|
+
if (!TERMINAL_STATUSES.has(status))
|
|
53
|
+
return null;
|
|
54
|
+
if (SUCCESS_STATUSES.has(status))
|
|
55
|
+
return 'success';
|
|
56
|
+
if (status === 'blocked')
|
|
57
|
+
return 'failed';
|
|
58
|
+
return 'timeout';
|
|
59
|
+
}
|
|
60
|
+
async function pollSession(sessionHandle, port, deps) {
|
|
61
|
+
const url = `http://127.0.0.1:${port}/api/v2/sessions/${encodeURIComponent(sessionHandle)}`;
|
|
62
|
+
const response = await deps.fetch(url);
|
|
63
|
+
if (response.status === 404) {
|
|
64
|
+
return { outcome: 'not_found', status: null };
|
|
65
|
+
}
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error(`Unexpected HTTP ${response.status} polling session ${sessionHandle}`);
|
|
68
|
+
}
|
|
69
|
+
const body = await response.json();
|
|
70
|
+
if (!body['success']) {
|
|
71
|
+
return { outcome: 'not_found', status: null };
|
|
72
|
+
}
|
|
73
|
+
const data = body['data'];
|
|
74
|
+
if (!data)
|
|
75
|
+
return null;
|
|
76
|
+
const runs = data['runs'];
|
|
77
|
+
if (Array.isArray(runs) && runs.length > 0) {
|
|
78
|
+
const firstRun = runs[0];
|
|
79
|
+
const runStatus = typeof firstRun['status'] === 'string' ? firstRun['status'] : null;
|
|
80
|
+
if (runStatus !== null) {
|
|
81
|
+
const outcome = statusToOutcome(runStatus);
|
|
82
|
+
if (outcome !== null) {
|
|
83
|
+
return { outcome, status: runStatus };
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const summaryStatus = typeof data['status'] === 'string' ? data['status'] : null;
|
|
89
|
+
if (summaryStatus !== null) {
|
|
90
|
+
const outcome = statusToOutcome(summaryStatus);
|
|
91
|
+
if (outcome !== null) {
|
|
92
|
+
return { outcome, status: summaryStatus };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
async function executeWorktrainAwaitCommand(deps, opts) {
|
|
98
|
+
const rawSessions = opts.sessions.trim();
|
|
99
|
+
if (!rawSessions) {
|
|
100
|
+
return (0, cli_result_js_1.misuse)('--sessions is required and must not be empty.');
|
|
101
|
+
}
|
|
102
|
+
const handles = rawSessions
|
|
103
|
+
.split(',')
|
|
104
|
+
.map((h) => h.trim())
|
|
105
|
+
.filter((h) => h.length > 0);
|
|
106
|
+
if (handles.length === 0) {
|
|
107
|
+
return (0, cli_result_js_1.misuse)('--sessions must contain at least one session handle.');
|
|
108
|
+
}
|
|
109
|
+
const mode = opts.mode ?? 'all';
|
|
110
|
+
if (mode !== 'all' && mode !== 'any') {
|
|
111
|
+
return (0, cli_result_js_1.misuse)(`--mode must be 'all' or 'any', got: ${mode}`);
|
|
112
|
+
}
|
|
113
|
+
let timeoutMs = DEFAULT_TIMEOUT_MS;
|
|
114
|
+
if (opts.timeout !== undefined) {
|
|
115
|
+
const parsed = parseDurationMs(opts.timeout);
|
|
116
|
+
if (parsed === null) {
|
|
117
|
+
return (0, cli_result_js_1.misuse)(`--timeout must be a duration like '30m', '1h', '90s', got: ${opts.timeout}`);
|
|
118
|
+
}
|
|
119
|
+
timeoutMs = parsed;
|
|
120
|
+
}
|
|
121
|
+
const pollInterval = opts.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
|
|
122
|
+
const port = await discoverConsolePort(deps, opts.port);
|
|
123
|
+
deps.stderr(`Awaiting ${handles.length} session(s) on port ${port} (mode: ${mode}, timeout: ${timeoutMs / 1000}s)...`);
|
|
124
|
+
const startMs = deps.now();
|
|
125
|
+
const pending = new Set(handles);
|
|
126
|
+
const results = new Map();
|
|
127
|
+
while (pending.size > 0) {
|
|
128
|
+
const elapsed = deps.now() - startMs;
|
|
129
|
+
if (elapsed >= timeoutMs) {
|
|
130
|
+
for (const handle of pending) {
|
|
131
|
+
results.set(handle, {
|
|
132
|
+
handle,
|
|
133
|
+
outcome: 'timeout',
|
|
134
|
+
status: null,
|
|
135
|
+
durationMs: deps.now() - startMs,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
pending.clear();
|
|
139
|
+
deps.stderr(`Timeout reached after ${Math.round(elapsed / 1000)}s.`);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
for (const handle of [...pending]) {
|
|
143
|
+
let pollResult;
|
|
144
|
+
try {
|
|
145
|
+
pollResult = await pollSession(handle, port, deps);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
149
|
+
deps.stderr(`Warning: error polling session ${handle}: ${message}`);
|
|
150
|
+
results.set(handle, {
|
|
151
|
+
handle,
|
|
152
|
+
outcome: 'failed',
|
|
153
|
+
status: null,
|
|
154
|
+
durationMs: deps.now() - startMs,
|
|
155
|
+
});
|
|
156
|
+
pending.delete(handle);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (pollResult !== null) {
|
|
160
|
+
results.set(handle, {
|
|
161
|
+
handle,
|
|
162
|
+
outcome: pollResult.outcome,
|
|
163
|
+
status: pollResult.status,
|
|
164
|
+
durationMs: deps.now() - startMs,
|
|
165
|
+
});
|
|
166
|
+
pending.delete(handle);
|
|
167
|
+
deps.stderr(`Session ${handle}: ${pollResult.outcome} (status: ${pollResult.status ?? 'n/a'})`);
|
|
168
|
+
if (mode === 'any' && pollResult.outcome === 'success') {
|
|
169
|
+
for (const remaining of pending) {
|
|
170
|
+
results.set(remaining, {
|
|
171
|
+
handle: remaining,
|
|
172
|
+
outcome: 'not_awaited',
|
|
173
|
+
status: null,
|
|
174
|
+
durationMs: deps.now() - startMs,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
pending.clear();
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (pending.size > 0) {
|
|
183
|
+
deps.stderr(`${pending.size} session(s) still running... (${Math.round((deps.now() - startMs) / 1000)}s elapsed)`);
|
|
184
|
+
await deps.sleep(pollInterval);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const resultArray = handles.map((handle) => {
|
|
188
|
+
return (results.get(handle) ?? {
|
|
189
|
+
handle,
|
|
190
|
+
outcome: 'not_found',
|
|
191
|
+
status: null,
|
|
192
|
+
durationMs: deps.now() - startMs,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
const allSucceeded = mode === 'all'
|
|
196
|
+
? resultArray.every((r) => r.outcome === 'success')
|
|
197
|
+
: resultArray.some((r) => r.outcome === 'success');
|
|
198
|
+
const awaitResult = {
|
|
199
|
+
results: resultArray,
|
|
200
|
+
allSucceeded,
|
|
201
|
+
};
|
|
202
|
+
deps.stdout(JSON.stringify(awaitResult, null, 2));
|
|
203
|
+
if (!allSucceeded) {
|
|
204
|
+
return (0, cli_result_js_1.failure)('One or more sessions did not succeed.', { exitCode: { kind: 'general_error' } });
|
|
205
|
+
}
|
|
206
|
+
return { kind: 'success' };
|
|
207
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CliResult } from '../types/cli-result.js';
|
|
2
|
+
export interface OutboxMessage {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly message: string;
|
|
5
|
+
readonly timestamp: string;
|
|
6
|
+
}
|
|
7
|
+
export interface InboxCursor {
|
|
8
|
+
readonly lastReadCount: number;
|
|
9
|
+
}
|
|
10
|
+
export interface WorktrainInboxCommandDeps {
|
|
11
|
+
readonly readFile: (path: string) => Promise<string>;
|
|
12
|
+
readonly writeFile: (path: string, content: string) => Promise<void>;
|
|
13
|
+
readonly mkdir: (path: string, options: {
|
|
14
|
+
recursive: boolean;
|
|
15
|
+
}) => Promise<string | undefined>;
|
|
16
|
+
readonly homedir: () => string;
|
|
17
|
+
readonly joinPath: (...paths: string[]) => string;
|
|
18
|
+
readonly print: (line: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface WorktrainInboxCommandOpts {
|
|
21
|
+
readonly watch?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function executeWorktrainInboxCommand(deps: WorktrainInboxCommandDeps, opts?: WorktrainInboxCommandOpts): Promise<CliResult>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeWorktrainInboxCommand = executeWorktrainInboxCommand;
|
|
4
|
+
const cli_result_js_1 = require("../types/cli-result.js");
|
|
5
|
+
async function executeWorktrainInboxCommand(deps, opts = {}) {
|
|
6
|
+
if (opts.watch) {
|
|
7
|
+
deps.print('Watch mode is not yet implemented (requires the daemon coordinator loop).');
|
|
8
|
+
deps.print('Run `worktrain inbox` without --watch to read queued messages now.');
|
|
9
|
+
return (0, cli_result_js_1.success)({ message: 'Watch mode not yet implemented.' });
|
|
10
|
+
}
|
|
11
|
+
const workrailDir = deps.joinPath(deps.homedir(), '.workrail');
|
|
12
|
+
const outboxPath = deps.joinPath(workrailDir, 'outbox.jsonl');
|
|
13
|
+
const cursorPath = deps.joinPath(workrailDir, 'inbox-cursor.json');
|
|
14
|
+
let outboxContent;
|
|
15
|
+
try {
|
|
16
|
+
outboxContent = await deps.readFile(outboxPath);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (isEnoent(err)) {
|
|
20
|
+
deps.print('No messages.');
|
|
21
|
+
return (0, cli_result_js_1.success)({ message: 'No messages.' });
|
|
22
|
+
}
|
|
23
|
+
return (0, cli_result_js_1.failure)(`Failed to read outbox: ${err instanceof Error ? err.message : String(err)}`);
|
|
24
|
+
}
|
|
25
|
+
const allLines = outboxContent.split('\n').filter((line) => line.trim() !== '');
|
|
26
|
+
const parsed = [];
|
|
27
|
+
let malformedCount = 0;
|
|
28
|
+
for (const line of allLines) {
|
|
29
|
+
try {
|
|
30
|
+
const msg = JSON.parse(line);
|
|
31
|
+
parsed.push(msg);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
malformedCount++;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const totalLines = parsed.length;
|
|
38
|
+
let lastReadCount = 0;
|
|
39
|
+
try {
|
|
40
|
+
const cursorContent = await deps.readFile(cursorPath);
|
|
41
|
+
const cursor = JSON.parse(cursorContent);
|
|
42
|
+
if (typeof cursor.lastReadCount === 'number' && cursor.lastReadCount >= 0) {
|
|
43
|
+
lastReadCount = cursor.lastReadCount;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
lastReadCount = 0;
|
|
48
|
+
}
|
|
49
|
+
if (lastReadCount > totalLines) {
|
|
50
|
+
lastReadCount = 0;
|
|
51
|
+
}
|
|
52
|
+
const unread = parsed.slice(lastReadCount);
|
|
53
|
+
const newCount = unread.length;
|
|
54
|
+
if (malformedCount > 0) {
|
|
55
|
+
deps.print(`Warning: skipped ${malformedCount} malformed line(s) in outbox.jsonl.`);
|
|
56
|
+
}
|
|
57
|
+
if (newCount === 0) {
|
|
58
|
+
deps.print('No new messages.');
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
for (const msg of unread) {
|
|
62
|
+
deps.print(`[${msg.timestamp}] ${msg.message}`);
|
|
63
|
+
}
|
|
64
|
+
deps.print('');
|
|
65
|
+
deps.print(`${newCount} new message(s).`);
|
|
66
|
+
}
|
|
67
|
+
const newCursor = { lastReadCount: totalLines };
|
|
68
|
+
try {
|
|
69
|
+
await deps.mkdir(workrailDir, { recursive: true });
|
|
70
|
+
await deps.writeFile(cursorPath, JSON.stringify(newCursor, null, 2) + '\n');
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
deps.print(`Warning: could not update read cursor: ${err instanceof Error ? err.message : String(err)}`);
|
|
74
|
+
}
|
|
75
|
+
return (0, cli_result_js_1.success)({ message: `${newCount} new message(s).` });
|
|
76
|
+
}
|
|
77
|
+
function isEnoent(err) {
|
|
78
|
+
return (err !== null &&
|
|
79
|
+
typeof err === 'object' &&
|
|
80
|
+
'code' in err &&
|
|
81
|
+
err.code === 'ENOENT');
|
|
82
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CliResult } from '../types/cli-result.js';
|
|
2
|
+
export interface WorktrainInitCommandDeps {
|
|
3
|
+
readonly prompt: (question: string, defaultValue?: string) => Promise<string>;
|
|
4
|
+
readonly mkdir: (path: string, options: {
|
|
5
|
+
recursive: boolean;
|
|
6
|
+
}) => Promise<string | undefined>;
|
|
7
|
+
readonly readFile: (path: string) => Promise<string>;
|
|
8
|
+
readonly writeFile: (path: string, content: string) => Promise<void>;
|
|
9
|
+
readonly exists: (path: string) => Promise<boolean>;
|
|
10
|
+
readonly homedir: () => string;
|
|
11
|
+
readonly cwd: () => string;
|
|
12
|
+
readonly joinPath: (...paths: string[]) => string;
|
|
13
|
+
readonly runSmoke: () => Promise<{
|
|
14
|
+
readonly ok: boolean;
|
|
15
|
+
readonly output: string;
|
|
16
|
+
}>;
|
|
17
|
+
readonly print: (line: string) => void;
|
|
18
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
19
|
+
}
|
|
20
|
+
export interface WorktrainInitCommandOpts {
|
|
21
|
+
readonly yes?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function executeWorktrainInitCommand(deps: WorktrainInitCommandDeps, opts?: WorktrainInitCommandOpts): Promise<CliResult>;
|