@exaudeus/workrail 3.33.0 → 3.34.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 (39) hide show
  1. package/dist/cli-worktrain.js +167 -8
  2. package/dist/console-ui/assets/{index-BuJFLLfY.js → index-BVU9OSOb.js} +1 -1
  3. package/dist/console-ui/index.html +1 -1
  4. package/dist/daemon/agent-loop.d.ts +1 -0
  5. package/dist/daemon/agent-loop.js +1 -1
  6. package/dist/daemon/daemon-events.d.ts +17 -1
  7. package/dist/daemon/workflow-runner.d.ts +1 -1
  8. package/dist/daemon/workflow-runner.js +96 -21
  9. package/dist/manifest.json +45 -69
  10. package/dist/mcp/handlers/v2-error-mapping.d.ts +3 -0
  11. package/dist/mcp/handlers/v2-error-mapping.js +2 -0
  12. package/dist/mcp/handlers/v2-execution/advance.js +25 -0
  13. package/dist/mcp/handlers/v2-execution/continue-advance.js +7 -0
  14. package/dist/mcp/transports/http-entry.js +0 -7
  15. package/dist/mcp/transports/stdio-entry.js +0 -8
  16. package/dist/mcp-server.d.ts +0 -2
  17. package/dist/mcp-server.js +1 -42
  18. package/dist/trigger/polled-event-store.js +8 -6
  19. package/dist/v2/durable-core/domain/observation-builder.d.ts +3 -0
  20. package/dist/v2/durable-core/domain/observation-builder.js +2 -2
  21. package/dist/v2/durable-core/domain/prompt-renderer.d.ts +2 -1
  22. package/dist/v2/durable-core/domain/prompt-renderer.js +10 -0
  23. package/dist/v2/usecases/console-service.js +65 -14
  24. package/dist/v2/usecases/console-types.d.ts +1 -0
  25. package/docs/design/bridge-removal-pr-a-candidates.md +115 -0
  26. package/docs/design/bridge-removal-pr-a-design-review.md +79 -0
  27. package/docs/design/bridge-removal-pr-a-implementation-plan.md +203 -0
  28. package/docs/discovery/design-candidates.md +180 -0
  29. package/docs/discovery/design-review-findings.md +110 -0
  30. package/docs/discovery/wr-discovery-goal-reframing.md +303 -0
  31. package/docs/ideas/backlog.md +361 -0
  32. package/package.json +1 -1
  33. package/workflows/wr.discovery.json +58 -7
  34. package/dist/mcp/transports/bridge-entry.d.ts +0 -102
  35. package/dist/mcp/transports/bridge-entry.js +0 -454
  36. package/dist/mcp/transports/bridge-events.d.ts +0 -55
  37. package/dist/mcp/transports/bridge-events.js +0 -24
  38. package/dist/mcp/transports/primary-tombstone.d.ts +0 -21
  39. package/dist/mcp/transports/primary-tombstone.js +0 -51
@@ -1,454 +0,0 @@
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.DEFAULT_BRIDGE_CONFIG = void 0;
37
- exports.detectHealthyPrimary = detectHealthyPrimary;
38
- exports.spawnLockPath = spawnLockPath;
39
- exports.acquireSpawnLock = acquireSpawnLock;
40
- exports.releaseSpawnLock = releaseSpawnLock;
41
- exports.spawnPrimary = spawnPrimary;
42
- exports.reconnectWithBackoff = reconnectWithBackoff;
43
- exports.handleReconnectOutcome = handleReconnectOutcome;
44
- exports.startBridgeServer = startBridgeServer;
45
- const fatal_exit_js_1 = require("./fatal-exit.js");
46
- const bridge_events_js_1 = require("./bridge-events.js");
47
- const primary_tombstone_js_1 = require("./primary-tombstone.js");
48
- const fs_1 = require("fs");
49
- const os_1 = require("os");
50
- const path_1 = require("path");
51
- exports.DEFAULT_BRIDGE_CONFIG = {
52
- reconnectBaseDelayMs: 250,
53
- reconnectMaxAttempts: 8,
54
- forwardTimeoutMs: 30000,
55
- maxRespawnAttempts: 3,
56
- spawnLockStaleMs: 30000,
57
- waitForPrimaryPollMs: 5000,
58
- };
59
- async function detectHealthyPrimary(port, opts = {}) {
60
- const retries = opts.retries ?? 3;
61
- const baseDelayMs = opts.baseDelayMs ?? 200;
62
- const fetchFn = opts.fetch ?? globalThis.fetch;
63
- for (let attempt = 0; attempt < retries; attempt++) {
64
- try {
65
- const response = await fetchFn(`http://localhost:${port}/workrail-health`, {
66
- method: 'GET',
67
- signal: AbortSignal.timeout(500),
68
- });
69
- if (response.ok) {
70
- const body = (await response.json().catch(() => null));
71
- if (body?.service === 'workrail') {
72
- const pid = typeof body.pid === 'number' ? body.pid : 0;
73
- return { port, pid };
74
- }
75
- }
76
- }
77
- catch {
78
- }
79
- if (attempt < retries - 1) {
80
- await sleep(baseDelayMs * (attempt + 1));
81
- }
82
- }
83
- return null;
84
- }
85
- function spawnLockPath(port) {
86
- return (0, path_1.join)((0, os_1.homedir)(), '.workrail', `spawn-coordinator-${port}.lock`);
87
- }
88
- function acquireSpawnLock(port, staleMs, deps = {}) {
89
- const lockPath = spawnLockPath(port);
90
- const writeFn = deps.writeFileSync ?? fs_1.writeFileSync;
91
- const statFn = deps.statSync ?? require('fs').statSync;
92
- const unlinkFn = deps.unlinkSync ?? require('fs').unlinkSync;
93
- try {
94
- (0, fs_1.mkdirSync)((0, path_1.join)((0, os_1.homedir)(), '.workrail'), { recursive: true });
95
- }
96
- catch {
97
- return { kind: 'skipped', reason: 'mkdirSync failed' };
98
- }
99
- try {
100
- writeFn(lockPath, String(process.pid), { flag: 'wx' });
101
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'spawn_lock_acquired', port });
102
- return { kind: 'acquired' };
103
- }
104
- catch (err) {
105
- const code = err.code;
106
- if (code === 'EEXIST') {
107
- try {
108
- const stat = statFn(lockPath);
109
- const ageMs = Date.now() - stat.mtimeMs;
110
- if (ageMs > staleMs) {
111
- try {
112
- unlinkFn(lockPath);
113
- }
114
- catch {
115
- }
116
- try {
117
- writeFn(lockPath, String(process.pid), { flag: 'wx' });
118
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'spawn_lock_acquired', port });
119
- return { kind: 'acquired' };
120
- }
121
- catch {
122
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'spawn_lock_skipped', reason: 'lost race after stale reclaim' });
123
- return { kind: 'skipped', reason: 'lost race after stale reclaim' };
124
- }
125
- }
126
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'spawn_lock_skipped', reason: 'lock held by another bridge' });
127
- return { kind: 'skipped', reason: 'lock held by another bridge' };
128
- }
129
- catch {
130
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'spawn_lock_skipped', reason: 'lock contested' });
131
- return { kind: 'skipped', reason: 'lock contested' };
132
- }
133
- }
134
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'spawn_lock_skipped', reason: `unexpected error: ${code ?? String(err)}` });
135
- return { kind: 'skipped', reason: `unexpected error: ${code ?? String(err)}` };
136
- }
137
- }
138
- function releaseSpawnLock(port, deps = {}) {
139
- const unlinkFn = deps.unlinkSync ?? require('fs').unlinkSync;
140
- try {
141
- unlinkFn(spawnLockPath(port));
142
- }
143
- catch {
144
- }
145
- }
146
- async function spawnPrimary(port, deps) {
147
- await sleep(Math.random() * 2000);
148
- const primaryDetected = await detectHealthyPrimary(port, { retries: 3, baseDelayMs: 500, fetch: deps.fetch });
149
- if (primaryDetected != null) {
150
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'spawn_skipped', reason: 'primary already up after jitter' });
151
- console.error('[Bridge] Primary already available after jitter — skipping spawn');
152
- return;
153
- }
154
- const scriptPath = process.argv[1];
155
- if (scriptPath == null) {
156
- console.error('[Bridge] Cannot spawn primary: process.argv[1] is undefined');
157
- return;
158
- }
159
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'spawn_primary', port });
160
- console.error('[Bridge] Spawning new WorkRail primary process');
161
- try {
162
- const child = deps.spawn(process.execPath, [scriptPath], {
163
- env: {
164
- ...process.env,
165
- WORKRAIL_TRANSPORT: 'http',
166
- WORKRAIL_HTTP_PORT: String(port),
167
- },
168
- detached: true,
169
- stdio: 'ignore',
170
- });
171
- child.unref();
172
- }
173
- catch (err) {
174
- console.error('[Bridge] Failed to spawn primary:', err);
175
- }
176
- }
177
- async function reconnectWithBackoff(deps) {
178
- const { detect, config, signal } = deps;
179
- const { reconnectBaseDelayMs, reconnectMaxAttempts } = config;
180
- for (let attempt = 0; attempt < reconnectMaxAttempts; attempt++) {
181
- if (signal.aborted)
182
- return { kind: 'aborted' };
183
- const succeeded = await detect(attempt);
184
- if (succeeded)
185
- return { kind: 'reconnected' };
186
- if (attempt < reconnectMaxAttempts - 1) {
187
- const delay = reconnectBaseDelayMs * Math.pow(2, attempt);
188
- await sleep(delay);
189
- if (signal.aborted)
190
- return { kind: 'aborted' };
191
- }
192
- }
193
- return { kind: 'exhausted' };
194
- }
195
- async function handleReconnectOutcome(outcome, reconnectingState, deps) {
196
- switch (outcome.kind) {
197
- case 'reconnected':
198
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'reconnected', attempt: reconnectingState.attempt });
199
- console.error('[Bridge] Reconnected to primary');
200
- return;
201
- case 'aborted':
202
- return;
203
- case 'exhausted':
204
- if (reconnectingState.respawnBudget > 0) {
205
- await deps.triggerSpawn();
206
- deps.setConnectionState({
207
- kind: 'reconnecting',
208
- attempt: 0,
209
- maxAttempts: deps.config.reconnectMaxAttempts,
210
- respawnBudget: reconnectingState.respawnBudget - 1,
211
- });
212
- deps.startReconnectLoop();
213
- }
214
- else {
215
- (0, bridge_events_js_1.logBridgeEvent)({
216
- kind: 'budget_exhausted',
217
- budgetUsed: reconnectingState.maxAttempts,
218
- respawnBudget: reconnectingState.respawnBudget,
219
- });
220
- console.error('[Bridge] Spawn budget exhausted — entering slow-poll mode (5s interval)');
221
- deps.setConnectionState({ kind: 'waiting_for_primary' });
222
- deps.startWaitLoop();
223
- }
224
- return;
225
- }
226
- }
227
- async function startBridgeServer(primaryPort, config = exports.DEFAULT_BRIDGE_CONFIG, deps = {}) {
228
- (0, fatal_exit_js_1.registerFatalHandlers)('bridge');
229
- (0, fatal_exit_js_1.logStartup)('bridge', { primaryPort });
230
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'started', primaryPort, ppid: process.ppid });
231
- const { StdioServerTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/stdio.js')));
232
- const { StreamableHTTPClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/streamableHttp.js')));
233
- const { spawn: nodeSpawn } = await Promise.resolve().then(() => __importStar(require('child_process')));
234
- const spawnFn = deps.spawn ?? ((command, args, opts) => nodeSpawn(command, args, opts));
235
- const shutdownController = new AbortController();
236
- const { signal: shutdownSignal } = shutdownController;
237
- const stdioTransport = new StdioServerTransport();
238
- let connectionState = { kind: 'connecting' };
239
- const setConnectionState = (next) => {
240
- connectionState = next;
241
- };
242
- const performShutdown = (reason) => {
243
- if (shutdownSignal.aborted)
244
- return;
245
- shutdownController.abort();
246
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'shutdown', reason });
247
- process.stderr.write(`[Bridge] Shutdown pid=${process.pid} reason="${reason}" ts=${new Date().toISOString()}\n`);
248
- const state = connectionState;
249
- void (state.kind === 'connected' ? state.transport.close() : Promise.resolve()).finally(() => process.exit(0));
250
- };
251
- const buildConnectedTransport = async () => {
252
- const t = new StreamableHTTPClientTransport(new URL(`http://localhost:${primaryPort}/mcp`));
253
- t.onerror = (err) => console.error('[Bridge] HTTP transport error:', err);
254
- t.onmessage = (msg) => {
255
- void stdioTransport.send(msg).catch((err) => {
256
- console.error('[Bridge] Forward to IDE failed:', err);
257
- });
258
- };
259
- t.onclose = () => {
260
- if (shutdownSignal.aborted)
261
- return;
262
- const current = connectionState;
263
- if (current.kind === 'connecting' ||
264
- current.kind === 'reconnecting' ||
265
- current.kind === 'waiting_for_primary')
266
- return;
267
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'disconnected' });
268
- console.error('[Bridge] Primary connection lost — reconnecting');
269
- const connectedPrimaryPid = deps.originalPrimaryPid ?? 0;
270
- if (connectedPrimaryPid > 0) {
271
- const tombstone = (0, primary_tombstone_js_1.readTombstone)();
272
- if (tombstone?.pid === connectedPrimaryPid) {
273
- console.error('[Bridge] Tombstone detected — primary died cleanly, entering slow-poll mode');
274
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'waiting_for_primary', port: primaryPort });
275
- setConnectionState({ kind: 'waiting_for_primary' });
276
- startWaitLoop();
277
- return;
278
- }
279
- }
280
- setConnectionState({
281
- kind: 'reconnecting',
282
- attempt: 0,
283
- maxAttempts: config.reconnectMaxAttempts,
284
- respawnBudget: config.maxRespawnAttempts,
285
- });
286
- startReconnectLoop();
287
- };
288
- try {
289
- await t.start();
290
- const transport = { send: (msg) => t.send(msg), close: () => t.close() };
291
- setConnectionState({ kind: 'connected', transport });
292
- return transport;
293
- }
294
- catch {
295
- return null;
296
- }
297
- };
298
- const startReconnectLoop = () => {
299
- const stateAtStart = connectionState;
300
- if (stateAtStart.kind !== 'reconnecting')
301
- return;
302
- void reconnectWithBackoff({
303
- signal: shutdownSignal,
304
- config,
305
- detect: async (attempt) => {
306
- const connectedPrimaryPid = deps.originalPrimaryPid ?? 0;
307
- if (connectedPrimaryPid > 0) {
308
- const tombstone = (0, primary_tombstone_js_1.readTombstone)();
309
- if (tombstone?.pid === connectedPrimaryPid) {
310
- console.error('[Bridge] Tombstone detected during reconnect — entering slow-poll mode');
311
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'waiting_for_primary', port: primaryPort });
312
- setConnectionState({ kind: 'waiting_for_primary' });
313
- startWaitLoop();
314
- return true;
315
- }
316
- }
317
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'reconnect_attempt', attempt: attempt + 1, maxAttempts: config.reconnectMaxAttempts });
318
- console.error(`[Bridge] Reconnect attempt ${attempt + 1}/${config.reconnectMaxAttempts}`);
319
- const detected = await detectHealthyPrimary(primaryPort, { retries: 1, fetch: deps.fetch });
320
- if (detected == null)
321
- return false;
322
- const transport = await buildConnectedTransport();
323
- return transport != null;
324
- },
325
- })
326
- .then((outcome) => {
327
- const stateAtOutcome = connectionState;
328
- if (stateAtOutcome.kind !== 'reconnecting')
329
- return;
330
- return handleReconnectOutcome(outcome, stateAtOutcome, {
331
- setConnectionState,
332
- performShutdown,
333
- startReconnectLoop,
334
- startWaitLoop,
335
- triggerSpawn: async () => {
336
- const lockResult = acquireSpawnLock(primaryPort, config.spawnLockStaleMs);
337
- if (lockResult.kind === 'skipped') {
338
- console.error(`[Bridge] Spawn skipped (coordinator lock): ${lockResult.reason}`);
339
- return;
340
- }
341
- try {
342
- await spawnPrimary(primaryPort, { spawn: spawnFn, fetch: deps.fetch });
343
- }
344
- finally {
345
- releaseSpawnLock(primaryPort);
346
- }
347
- },
348
- config,
349
- });
350
- })
351
- .catch((err) => {
352
- const errObj = err instanceof Error ? err : new Error(String(err));
353
- (0, bridge_events_js_1.logBridgeEvent)({
354
- kind: 'reconnect_loop_error',
355
- message: errObj.message,
356
- stack: errObj.stack ?? null,
357
- });
358
- console.error('[Bridge] Unexpected error in reconnect loop:', err);
359
- });
360
- };
361
- const startWaitLoop = () => {
362
- const stateAtStart = connectionState;
363
- if (stateAtStart.kind !== 'waiting_for_primary')
364
- return;
365
- void (async () => {
366
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'waiting_for_primary', port: primaryPort });
367
- while (!shutdownSignal.aborted) {
368
- await sleep(config.waitForPrimaryPollMs);
369
- if (shutdownSignal.aborted)
370
- return;
371
- const detected = await detectHealthyPrimary(primaryPort, { retries: 1, fetch: deps.fetch });
372
- if (detected != null) {
373
- console.error('[Bridge] Primary found after waiting — resuming normal operation');
374
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'primary_found_after_wait', port: primaryPort });
375
- setConnectionState({
376
- kind: 'reconnecting',
377
- attempt: 0,
378
- maxAttempts: config.reconnectMaxAttempts,
379
- respawnBudget: config.maxRespawnAttempts,
380
- });
381
- startReconnectLoop();
382
- return;
383
- }
384
- }
385
- })().catch((err) => {
386
- const errObj = err instanceof Error ? err : new Error(String(err));
387
- (0, bridge_events_js_1.logBridgeEvent)({
388
- kind: 'reconnect_loop_error',
389
- message: `wait loop error: ${errObj.message}`,
390
- stack: errObj.stack ?? null,
391
- });
392
- console.error('[Bridge] Unexpected error in wait loop:', err);
393
- });
394
- };
395
- stdioTransport.onmessage = (msg) => {
396
- const state = connectionState;
397
- switch (state.kind) {
398
- case 'connected': {
399
- const timer = setTimeout(() => {
400
- console.error('[Bridge] Warning: no response from primary after', config.forwardTimeoutMs, 'ms');
401
- }, config.forwardTimeoutMs);
402
- void state.transport
403
- .send(msg)
404
- .catch((err) => console.error('[Bridge] Forward to primary failed:', err))
405
- .finally(() => clearTimeout(timer));
406
- return;
407
- }
408
- case 'connecting':
409
- case 'reconnecting':
410
- case 'waiting_for_primary':
411
- sendUnavailableError(msg, (m) => stdioTransport.send(m));
412
- return;
413
- case 'closed':
414
- return;
415
- }
416
- };
417
- stdioTransport.onerror = (err) => console.error('[Bridge] Stdio error:', err);
418
- const initialTransport = await buildConnectedTransport();
419
- if (initialTransport == null) {
420
- throw new Error(`[Bridge] Failed to connect to primary on port ${primaryPort}`);
421
- }
422
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'connected', primaryPort });
423
- console.error('[Bridge] Connected to primary');
424
- process.stdout.on('error', (err) => {
425
- const code = err.code;
426
- const reason = code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED'
427
- ? 'stdout pipe broken (client disconnected)'
428
- : `stdout error: ${String(err)}`;
429
- performShutdown(reason);
430
- });
431
- await stdioTransport.start();
432
- console.error('[Bridge] WorkRail MCP bridge running on stdio');
433
- process.stdin.once('end', () => performShutdown('stdin closed'));
434
- process.once('SIGINT', () => performShutdown('SIGINT'));
435
- process.once('SIGTERM', () => performShutdown('SIGTERM'));
436
- process.once('SIGHUP', () => performShutdown('SIGHUP'));
437
- }
438
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
439
- function sendUnavailableError(msg, send) {
440
- if (!('id' in msg) || msg.id == null)
441
- return;
442
- void send({
443
- jsonrpc: '2.0',
444
- id: msg.id,
445
- error: {
446
- code: -32603,
447
- message: 'WorkRail primary server is temporarily unavailable — reconnecting. ' +
448
- 'Wait a few seconds and retry your tool call. ' +
449
- 'If this persists, tell the user: ' +
450
- '"WorkRail disconnected. Check the terminal running workrail for the ' +
451
- 'error message, then run /mcp in Claude to reconnect."',
452
- },
453
- }).catch(() => undefined);
454
- }
@@ -1,55 +0,0 @@
1
- export type BridgeEvent = {
2
- readonly kind: 'primary_started';
3
- readonly transport: 'stdio' | 'http';
4
- readonly port?: number;
5
- } | {
6
- readonly kind: 'started';
7
- readonly primaryPort: number;
8
- readonly ppid: number;
9
- } | {
10
- readonly kind: 'connected';
11
- readonly primaryPort: number;
12
- } | {
13
- readonly kind: 'disconnected';
14
- } | {
15
- readonly kind: 'reconnect_attempt';
16
- readonly attempt: number;
17
- readonly maxAttempts: number;
18
- } | {
19
- readonly kind: 'reconnected';
20
- readonly attempt: number;
21
- } | {
22
- readonly kind: 'spawn_primary';
23
- readonly port: number;
24
- } | {
25
- readonly kind: 'spawn_skipped';
26
- readonly reason: string;
27
- } | {
28
- readonly kind: 'spawn_lock_acquired';
29
- readonly port: number;
30
- } | {
31
- readonly kind: 'spawn_lock_skipped';
32
- readonly reason: string;
33
- } | {
34
- readonly kind: 'budget_exhausted';
35
- readonly budgetUsed: number;
36
- readonly respawnBudget: number;
37
- } | {
38
- readonly kind: 'waiting_for_primary';
39
- readonly port: number;
40
- } | {
41
- readonly kind: 'primary_found_after_wait';
42
- readonly port: number;
43
- } | {
44
- readonly kind: 'reconnect_loop_error';
45
- readonly message: string;
46
- readonly stack: string | null;
47
- } | {
48
- readonly kind: 'shutdown';
49
- readonly reason: string;
50
- } | {
51
- readonly kind: 'orphaned';
52
- readonly expectedPid: number;
53
- readonly actualPid: number;
54
- };
55
- export declare function logBridgeEvent(event: BridgeEvent): void;
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.logBridgeEvent = logBridgeEvent;
4
- const fs_1 = require("fs");
5
- const os_1 = require("os");
6
- const path_1 = require("path");
7
- const BRIDGE_LOG_PATH = (0, path_1.join)((0, os_1.homedir)(), '.workrail', 'bridge.log');
8
- const BRIDGE_LOG_MAX_BYTES = 512 * 1024;
9
- function logBridgeEvent(event) {
10
- try {
11
- (0, fs_1.mkdirSync)((0, path_1.join)((0, os_1.homedir)(), '.workrail'), { recursive: true });
12
- try {
13
- const { statSync } = require('fs');
14
- if (statSync(BRIDGE_LOG_PATH).size > BRIDGE_LOG_MAX_BYTES) {
15
- const { writeFileSync } = require('fs');
16
- writeFileSync(BRIDGE_LOG_PATH, '');
17
- }
18
- }
19
- catch { }
20
- const entry = { ts: new Date().toISOString(), pid: process.pid, ...event };
21
- (0, fs_1.appendFileSync)(BRIDGE_LOG_PATH, JSON.stringify(entry) + '\n');
22
- }
23
- catch { }
24
- }
@@ -1,21 +0,0 @@
1
- export interface PrimaryTombstone {
2
- readonly pid: number;
3
- readonly port: number;
4
- readonly diedAt: string;
5
- }
6
- export type WriteSyncLike = (path: string, content: string) => void;
7
- export type ReadSyncLike = (path: string, encoding: 'utf-8') => string;
8
- export type UnlinkSyncLike = (path: string) => void;
9
- export type MkdirSyncLike = (path: string, opts: {
10
- recursive: true;
11
- }) => void;
12
- export interface TombstoneDeps {
13
- readonly writeSync?: WriteSyncLike;
14
- readonly readSync?: ReadSyncLike;
15
- readonly unlinkSync?: UnlinkSyncLike;
16
- readonly mkdirSync?: MkdirSyncLike;
17
- }
18
- export declare function tombstonePath(): string;
19
- export declare function writeTombstone(port: number, pid: number, deps?: TombstoneDeps): void;
20
- export declare function readTombstone(deps?: TombstoneDeps): PrimaryTombstone | null;
21
- export declare function clearTombstone(deps?: TombstoneDeps): void;
@@ -1,51 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.tombstonePath = tombstonePath;
4
- exports.writeTombstone = writeTombstone;
5
- exports.readTombstone = readTombstone;
6
- exports.clearTombstone = clearTombstone;
7
- const fs_1 = require("fs");
8
- const os_1 = require("os");
9
- const path_1 = require("path");
10
- function tombstonePath() {
11
- return (0, path_1.join)((0, os_1.homedir)(), '.workrail', 'primary.tombstone');
12
- }
13
- function writeTombstone(port, pid, deps = {}) {
14
- try {
15
- const mkdirFn = deps.mkdirSync ?? fs_1.mkdirSync;
16
- mkdirFn((0, path_1.join)((0, os_1.homedir)(), '.workrail'), { recursive: true });
17
- const writeFn = deps.writeSync ?? fs_1.writeFileSync;
18
- const tombstone = {
19
- pid,
20
- port,
21
- diedAt: new Date().toISOString(),
22
- };
23
- writeFn(tombstonePath(), JSON.stringify(tombstone, null, 2));
24
- }
25
- catch {
26
- }
27
- }
28
- function readTombstone(deps = {}) {
29
- try {
30
- const readFn = deps.readSync ?? fs_1.readFileSync;
31
- const content = readFn(tombstonePath(), 'utf-8');
32
- const parsed = JSON.parse(content);
33
- if (typeof parsed.pid === 'number' &&
34
- typeof parsed.port === 'number' &&
35
- typeof parsed.diedAt === 'string') {
36
- return parsed;
37
- }
38
- return null;
39
- }
40
- catch {
41
- return null;
42
- }
43
- }
44
- function clearTombstone(deps = {}) {
45
- try {
46
- const unlinkFn = deps.unlinkSync ?? fs_1.unlinkSync;
47
- unlinkFn(tombstonePath());
48
- }
49
- catch {
50
- }
51
- }