@exaudeus/workrail 3.15.0 → 3.17.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/application/services/workflow-service.d.ts +2 -0
- package/dist/application/services/workflow-service.js +3 -0
- package/dist/application/use-cases/raw-workflow-file-scanner.js +10 -13
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +2 -1
- package/dist/cli/commands/init.d.ts +10 -0
- package/dist/cli/commands/init.js +72 -0
- package/dist/cli.js +13 -1
- package/dist/config/config-file.d.ts +8 -0
- package/dist/config/config-file.js +141 -0
- package/dist/config/feature-flags.js +8 -0
- package/dist/console/assets/index-BZNM03t1.css +1 -0
- package/dist/console/assets/index-BwJelCXK.js +28 -0
- package/dist/console/index.html +2 -2
- package/dist/di/container.d.ts +1 -0
- package/dist/di/container.js +24 -7
- package/dist/infrastructure/session/HttpServer.d.ts +3 -4
- package/dist/infrastructure/session/HttpServer.js +58 -106
- package/dist/infrastructure/storage/caching-workflow-storage.d.ts +2 -0
- package/dist/infrastructure/storage/caching-workflow-storage.js +15 -6
- package/dist/infrastructure/storage/file-workflow-storage.js +3 -4
- package/dist/infrastructure/storage/schema-validating-workflow-storage.js +9 -8
- package/dist/manifest.json +303 -247
- package/dist/mcp/assert-output.d.ts +37 -0
- package/dist/mcp/assert-output.js +53 -0
- package/dist/mcp/boundary-coercion.d.ts +1 -0
- package/dist/mcp/boundary-coercion.js +44 -0
- package/dist/mcp/dev-mode.d.ts +2 -0
- package/dist/mcp/dev-mode.js +16 -0
- package/dist/mcp/handler-factory.d.ts +1 -1
- package/dist/mcp/handler-factory.js +20 -16
- package/dist/mcp/handlers/session.js +8 -9
- package/dist/mcp/handlers/shared/request-workflow-reader.d.ts +1 -0
- package/dist/mcp/handlers/shared/request-workflow-reader.js +90 -20
- package/dist/mcp/handlers/v2-advance-core/event-builders.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/event-builders.js +6 -6
- package/dist/mcp/handlers/v2-advance-core/index.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/index.js +4 -3
- package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/input-validation.js +32 -9
- package/dist/mcp/handlers/v2-advance-core/outcome-blocked.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +1 -1
- package/dist/mcp/handlers/v2-advance-core/outcome-success.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/outcome-success.js +1 -1
- package/dist/mcp/handlers/v2-checkpoint.d.ts +1 -1
- package/dist/mcp/handlers/v2-checkpoint.js +5 -6
- package/dist/mcp/handlers/v2-execution/advance.d.ts +4 -2
- package/dist/mcp/handlers/v2-execution/advance.js +5 -7
- package/dist/mcp/handlers/v2-execution/continue-advance.d.ts +1 -0
- package/dist/mcp/handlers/v2-execution/continue-advance.js +59 -27
- package/dist/mcp/handlers/v2-execution/continue-rehydrate.d.ts +2 -1
- package/dist/mcp/handlers/v2-execution/continue-rehydrate.js +11 -10
- package/dist/mcp/handlers/v2-execution/index.js +2 -0
- package/dist/mcp/handlers/v2-execution/replay.d.ts +8 -4
- package/dist/mcp/handlers/v2-execution/replay.js +50 -30
- package/dist/mcp/handlers/v2-execution/start.d.ts +2 -3
- package/dist/mcp/handlers/v2-execution/start.js +58 -30
- package/dist/mcp/handlers/v2-execution/workflow-object-cache.d.ts +5 -0
- package/dist/mcp/handlers/v2-execution/workflow-object-cache.js +19 -0
- package/dist/mcp/handlers/v2-execution-helpers.d.ts +1 -0
- package/dist/mcp/handlers/v2-execution-helpers.js +23 -7
- package/dist/mcp/handlers/v2-resume.d.ts +1 -1
- package/dist/mcp/handlers/v2-resume.js +3 -4
- package/dist/mcp/handlers/v2-state-conversion.js +5 -1
- package/dist/mcp/handlers/v2-workflow.d.ts +80 -0
- package/dist/mcp/handlers/v2-workflow.js +40 -23
- package/dist/mcp/handlers/workflow.d.ts +2 -5
- package/dist/mcp/handlers/workflow.js +15 -12
- package/dist/mcp/output-schemas.d.ts +25 -27
- package/dist/mcp/output-schemas.js +7 -7
- package/dist/mcp/server.js +23 -4
- package/dist/mcp/tool-call-timing.d.ts +24 -0
- package/dist/mcp/tool-call-timing.js +85 -0
- package/dist/mcp/transports/http-entry.js +3 -2
- package/dist/mcp/transports/http-listener.d.ts +1 -0
- package/dist/mcp/transports/http-listener.js +25 -0
- package/dist/mcp/transports/shutdown-hooks.d.ts +4 -1
- package/dist/mcp/transports/shutdown-hooks.js +3 -2
- package/dist/mcp/transports/stdio-entry.js +6 -28
- package/dist/mcp/v2-response-formatter.d.ts +1 -1
- package/dist/mcp/v2-response-formatter.js +2 -5
- package/dist/mcp/validation/schema-introspection.d.ts +1 -0
- package/dist/mcp/validation/schema-introspection.js +15 -5
- package/dist/mcp/validation/suggestion-generator.js +2 -2
- package/dist/runtime/adapters/node-process-signals.d.ts +1 -0
- package/dist/runtime/adapters/node-process-signals.js +5 -0
- package/dist/runtime/adapters/noop-process-signals.d.ts +1 -0
- package/dist/runtime/adapters/noop-process-signals.js +2 -0
- package/dist/runtime/ports/process-signals.d.ts +1 -0
- package/dist/types/workflow-definition.d.ts +5 -1
- package/dist/types/workflow-definition.js +2 -0
- package/dist/types/workflow.d.ts +3 -0
- package/dist/types/workflow.js +35 -26
- package/dist/v2/durable-core/domain/context-template-resolver.js +2 -2
- package/dist/v2/durable-core/domain/function-definition-expander.js +2 -17
- package/dist/v2/durable-core/domain/prompt-renderer.d.ts +2 -0
- package/dist/v2/durable-core/domain/prompt-renderer.js +22 -18
- package/dist/v2/durable-core/domain/recap-recovery.js +23 -16
- package/dist/v2/durable-core/domain/retrieval-contract.js +13 -7
- package/dist/v2/durable-core/schemas/compiled-workflow/index.js +4 -3
- package/dist/v2/durable-core/session-index.d.ts +22 -0
- package/dist/v2/durable-core/session-index.js +58 -0
- package/dist/v2/durable-core/sorted-event-log.d.ts +6 -0
- package/dist/v2/durable-core/sorted-event-log.js +15 -0
- package/dist/v2/infra/local/fs/index.js +8 -8
- package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +2 -0
- package/dist/v2/infra/local/pinned-workflow-store/index.js +49 -0
- package/dist/v2/infra/local/remembered-roots-store/index.d.ts +3 -1
- package/dist/v2/infra/local/remembered-roots-store/index.js +6 -3
- package/dist/v2/infra/local/session-store/index.d.ts +1 -1
- package/dist/v2/infra/local/session-store/index.js +71 -61
- package/dist/v2/infra/local/session-summary-provider/index.js +9 -4
- package/dist/v2/infra/local/snapshot-store/index.js +2 -1
- package/dist/v2/infra/local/workspace-anchor/index.js +4 -2
- package/dist/v2/ports/pinned-workflow-store.port.d.ts +2 -0
- package/dist/v2/ports/session-event-log-store.port.d.ts +1 -1
- package/dist/v2/projections/assessment-consequences.d.ts +2 -1
- package/dist/v2/projections/assessment-consequences.js +0 -5
- package/dist/v2/projections/assessments.d.ts +2 -1
- package/dist/v2/projections/assessments.js +2 -4
- package/dist/v2/projections/gaps.d.ts +2 -1
- package/dist/v2/projections/gaps.js +0 -5
- package/dist/v2/projections/preferences.d.ts +2 -1
- package/dist/v2/projections/preferences.js +0 -5
- package/dist/v2/projections/run-context.d.ts +2 -2
- package/dist/v2/projections/run-context.js +0 -5
- package/dist/v2/projections/run-dag.js +7 -1
- package/dist/v2/projections/run-execution-trace.d.ts +8 -0
- package/dist/v2/projections/run-execution-trace.js +124 -0
- package/dist/v2/projections/run-status-signals.d.ts +2 -2
- package/dist/v2/usecases/console-routes.d.ts +3 -1
- package/dist/v2/usecases/console-routes.js +124 -25
- package/dist/v2/usecases/console-service.d.ts +1 -0
- package/dist/v2/usecases/console-service.js +83 -25
- package/dist/v2/usecases/console-types.d.ts +53 -0
- package/dist/v2/usecases/worktree-service.js +32 -1
- package/package.json +6 -5
- package/spec/workflow.schema.json +18 -0
- package/workflows/adaptive-ticket-creation.json +23 -16
- package/workflows/architecture-scalability-audit.json +29 -22
- package/workflows/bug-investigation.agentic.v2.json +7 -0
- package/workflows/coding-task-workflow-agentic.json +7 -0
- package/workflows/coding-task-workflow-agentic.lean.v2.json +16 -8
- package/workflows/coding-task-workflow-agentic.v2.json +7 -0
- package/workflows/cross-platform-code-conversion.v2.json +7 -0
- package/workflows/document-creation-workflow.json +15 -8
- package/workflows/documentation-update-workflow.json +15 -8
- package/workflows/intelligent-test-case-generation.json +7 -0
- package/workflows/learner-centered-course-workflow.json +9 -2
- package/workflows/mr-review-workflow.agentic.v2.json +7 -0
- package/workflows/personal-learning-materials-creation-branched.json +15 -8
- package/workflows/presentation-creation.json +12 -5
- package/workflows/production-readiness-audit.json +7 -0
- package/workflows/relocation-workflow-us.json +39 -32
- package/workflows/scoped-documentation-workflow.json +33 -26
- package/workflows/ui-ux-design-workflow.json +7 -0
- package/workflows/workflow-diagnose-environment.json +6 -0
- package/workflows/workflow-for-workflows.json +7 -0
- package/workflows/workflow-for-workflows.v2.json +23 -11
- package/workflows/wr.discovery.json +8 -1
- package/dist/console/assets/index-BZYIjrzJ.js +0 -28
- package/dist/console/assets/index-OLCKbDdm.css +0 -1
- package/dist/mcp/handlers/v2-resolve-refs-envelope.d.ts +0 -5
- package/dist/mcp/handlers/v2-resolve-refs-envelope.js +0 -17
package/dist/console/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>WorkRail Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/console/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/console/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/console/assets/index-BwJelCXK.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/console/assets/index-BZNM03t1.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/dist/di/container.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { container } from 'tsyringe';
|
|
|
3
3
|
import type { RuntimeMode } from '../runtime/runtime-mode.js';
|
|
4
4
|
export interface ContainerInitOptions {
|
|
5
5
|
readonly runtimeMode?: RuntimeMode;
|
|
6
|
+
readonly env?: Record<string, string | undefined>;
|
|
6
7
|
}
|
|
7
8
|
export declare function initializeContainer(options?: ContainerInitOptions): Promise<void>;
|
|
8
9
|
export declare function startAsyncServices(): Promise<void>;
|
package/dist/di/container.js
CHANGED
|
@@ -50,14 +50,28 @@ const in_memory_shutdown_events_js_1 = require("../runtime/adapters/in-memory-sh
|
|
|
50
50
|
const node_process_terminator_js_1 = require("../runtime/adapters/node-process-terminator.js");
|
|
51
51
|
const throwing_process_terminator_js_1 = require("../runtime/adapters/throwing-process-terminator.js");
|
|
52
52
|
const app_config_js_1 = require("../config/app-config.js");
|
|
53
|
+
const config_file_js_1 = require("../config/config-file.js");
|
|
53
54
|
const formatter_js_1 = require("../errors/formatter.js");
|
|
54
55
|
let initialized = false;
|
|
55
56
|
let asyncInitialized = false;
|
|
56
57
|
let initializationPromise = null;
|
|
57
58
|
let isInitializing = false;
|
|
58
|
-
|
|
59
|
+
let mergedEnv = process.env;
|
|
60
|
+
async function registerConfig(env) {
|
|
61
|
+
(0, config_file_js_1.ensureWorkrailConfigFile)();
|
|
62
|
+
if (env !== undefined) {
|
|
63
|
+
mergedEnv = env;
|
|
64
|
+
}
|
|
65
|
+
else if (process.env['VITEST']) {
|
|
66
|
+
mergedEnv = { ...process.env };
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const configFileResult = (0, config_file_js_1.loadWorkrailConfigFile)();
|
|
70
|
+
const configFileValues = configFileResult.kind === 'ok' ? configFileResult.value : {};
|
|
71
|
+
mergedEnv = { ...configFileValues, ...process.env };
|
|
72
|
+
}
|
|
59
73
|
if (!tsyringe_1.container.isRegistered(tokens_js_1.DI.Config.App)) {
|
|
60
|
-
const configResult = (0, app_config_js_1.loadConfig)({ env:
|
|
74
|
+
const configResult = (0, app_config_js_1.loadConfig)({ env: mergedEnv, projectPath: process.cwd() });
|
|
61
75
|
if (configResult.kind === 'err') {
|
|
62
76
|
console.error((0, formatter_js_1.formatAppError)(configResult.error));
|
|
63
77
|
process.exit(1);
|
|
@@ -71,9 +85,10 @@ async function registerConfig() {
|
|
|
71
85
|
tsyringe_1.container.register(tokens_js_1.DI.Config.BrowserBehavior, { useValue: config.dashboard.browserBehavior });
|
|
72
86
|
}
|
|
73
87
|
if (!tsyringe_1.container.isRegistered(tokens_js_1.DI.Infra.FeatureFlags)) {
|
|
74
|
-
const {
|
|
88
|
+
const { CustomEnvFeatureFlagProvider } = await Promise.resolve().then(() => __importStar(require('../config/feature-flags.js')));
|
|
89
|
+
const captured = mergedEnv;
|
|
75
90
|
tsyringe_1.container.register(tokens_js_1.DI.Infra.FeatureFlags, {
|
|
76
|
-
useFactory: (0, tsyringe_1.instanceCachingFactory)((
|
|
91
|
+
useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new CustomEnvFeatureFlagProvider(captured))
|
|
77
92
|
});
|
|
78
93
|
}
|
|
79
94
|
}
|
|
@@ -186,7 +201,7 @@ async function registerV2Services() {
|
|
|
186
201
|
const { NodeTimeClockV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/time-clock/index.js')));
|
|
187
202
|
const { IdFactoryV2 } = await Promise.resolve().then(() => __importStar(require('../v2/infra/local/id-factory/index.js')));
|
|
188
203
|
tsyringe_1.container.register(tokens_js_1.DI.V2.DataDir, {
|
|
189
|
-
useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new LocalDataDirV2(
|
|
204
|
+
useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new LocalDataDirV2(mergedEnv)),
|
|
190
205
|
});
|
|
191
206
|
tsyringe_1.container.register(tokens_js_1.DI.V2.FileSystem, {
|
|
192
207
|
useFactory: (0, tsyringe_1.instanceCachingFactory)(() => new NodeFileSystemV2()),
|
|
@@ -249,7 +264,8 @@ async function registerV2Services() {
|
|
|
249
264
|
useFactory: (0, tsyringe_1.instanceCachingFactory)((c) => {
|
|
250
265
|
const dataDir = c.resolve(tokens_js_1.DI.V2.DataDir);
|
|
251
266
|
const fs = c.resolve(tokens_js_1.DI.V2.FileSystem);
|
|
252
|
-
|
|
267
|
+
const clock = c.resolve(tokens_js_1.DI.V2.TimeClock);
|
|
268
|
+
return new LocalRememberedRootsStoreV2(dataDir, fs, clock);
|
|
253
269
|
}),
|
|
254
270
|
});
|
|
255
271
|
tsyringe_1.container.register(tokens_js_1.DI.V2.ManagedSourceStore, {
|
|
@@ -321,7 +337,7 @@ async function initializeContainer(options = {}) {
|
|
|
321
337
|
isInitializing = true;
|
|
322
338
|
try {
|
|
323
339
|
registerRuntime(options);
|
|
324
|
-
await registerConfig();
|
|
340
|
+
await registerConfig(options.env);
|
|
325
341
|
await registerStorageChain();
|
|
326
342
|
await registerV2Services();
|
|
327
343
|
await registerServices();
|
|
@@ -366,6 +382,7 @@ function resetContainer() {
|
|
|
366
382
|
asyncInitialized = false;
|
|
367
383
|
initializationPromise = null;
|
|
368
384
|
isInitializing = false;
|
|
385
|
+
mergedEnv = process.env;
|
|
369
386
|
}
|
|
370
387
|
function isInitialized() {
|
|
371
388
|
return initialized;
|
|
@@ -42,18 +42,17 @@ export declare class HttpServer {
|
|
|
42
42
|
private tryBecomePrimary;
|
|
43
43
|
private shouldReclaimLock;
|
|
44
44
|
private reclaimStaleLock;
|
|
45
|
-
private checkHealth;
|
|
46
45
|
private setupPrimaryCleanup;
|
|
47
46
|
private startAsPrimary;
|
|
48
47
|
private startLegacyMode;
|
|
49
48
|
private printBanner;
|
|
50
49
|
openDashboard(sessionId?: string): Promise<string>;
|
|
51
50
|
stop(): Promise<void>;
|
|
52
|
-
mountRoutes(installer: (app: Application) => void): void;
|
|
51
|
+
mountRoutes(installer: (app: Application) => (() => void) | void): void;
|
|
52
|
+
private readonly _routeDisposers;
|
|
53
53
|
finalize(): void;
|
|
54
54
|
getBaseUrl(): string;
|
|
55
55
|
getPort(): number;
|
|
56
|
-
private quickCleanup;
|
|
57
|
-
private getWorkrailPorts;
|
|
58
56
|
fullCleanup(): Promise<number>;
|
|
57
|
+
private getWorkrailPorts;
|
|
59
58
|
}
|
|
@@ -29,6 +29,8 @@ const DashboardLockRelease_js_1 = require("./DashboardLockRelease.js");
|
|
|
29
29
|
const cors_1 = __importDefault(require("cors"));
|
|
30
30
|
const open_1 = __importDefault(require("open"));
|
|
31
31
|
const child_process_1 = require("child_process");
|
|
32
|
+
const _pkg = require(path_1.default.join(__dirname, '../../../package.json'));
|
|
33
|
+
const CURRENT_VERSION = _pkg.version;
|
|
32
34
|
let HttpServer = class HttpServer {
|
|
33
35
|
constructor(sessionManager, processLifecyclePolicy, processSignals, shutdownEvents, dashboardMode, browserBehavior) {
|
|
34
36
|
this.sessionManager = sessionManager;
|
|
@@ -41,6 +43,7 @@ let HttpServer = class HttpServer {
|
|
|
41
43
|
this.baseUrl = '';
|
|
42
44
|
this.isPrimary = false;
|
|
43
45
|
this.config = {};
|
|
46
|
+
this._routeDisposers = [];
|
|
44
47
|
this.port = 3456;
|
|
45
48
|
this.lockFile = path_1.default.join(os_1.default.homedir(), '.workrail', 'dashboard.lock');
|
|
46
49
|
this.heartbeat = new DashboardHeartbeat_js_1.DashboardHeartbeat(this.lockFile, () => this.isPrimary);
|
|
@@ -314,7 +317,7 @@ let HttpServer = class HttpServer {
|
|
|
314
317
|
});
|
|
315
318
|
}
|
|
316
319
|
});
|
|
317
|
-
this.app.get('/api/health', (
|
|
320
|
+
this.app.get('/api/health', (_req, res) => {
|
|
318
321
|
res.json({
|
|
319
322
|
success: true,
|
|
320
323
|
status: 'healthy',
|
|
@@ -322,12 +325,12 @@ let HttpServer = class HttpServer {
|
|
|
322
325
|
timestamp: new Date().toISOString(),
|
|
323
326
|
isPrimary: this.isPrimary,
|
|
324
327
|
pid: process.pid,
|
|
325
|
-
port: this.port
|
|
328
|
+
port: this.port,
|
|
329
|
+
version: CURRENT_VERSION
|
|
326
330
|
});
|
|
327
331
|
});
|
|
328
332
|
}
|
|
329
333
|
async start() {
|
|
330
|
-
await this.quickCleanup();
|
|
331
334
|
const mode = this.config.dashboardMode ?? this.dashboardMode;
|
|
332
335
|
if (mode.kind === 'legacy') {
|
|
333
336
|
console.error('[Dashboard] Unified dashboard disabled, using legacy mode');
|
|
@@ -340,7 +343,8 @@ let HttpServer = class HttpServer {
|
|
|
340
343
|
}
|
|
341
344
|
catch (error) {
|
|
342
345
|
if (error.code === 'EADDRINUSE') {
|
|
343
|
-
console.error(`[Dashboard] Port ${this.port}
|
|
346
|
+
console.error(`[Dashboard] Port ${this.port} still held by previous instance -- ` +
|
|
347
|
+
`running on next available port. Restart the old instance to move to ${this.port}.`);
|
|
344
348
|
await promises_1.default.unlink(this.lockFile).catch(() => { });
|
|
345
349
|
return await this.startLegacyMode();
|
|
346
350
|
}
|
|
@@ -361,7 +365,8 @@ let HttpServer = class HttpServer {
|
|
|
361
365
|
startedAt: new Date().toISOString(),
|
|
362
366
|
lastHeartbeat: new Date().toISOString(),
|
|
363
367
|
projectId: this.sessionManager.getProjectId(),
|
|
364
|
-
projectPath: this.sessionManager.getProjectPath()
|
|
368
|
+
projectPath: this.sessionManager.getProjectPath(),
|
|
369
|
+
version: CURRENT_VERSION
|
|
365
370
|
};
|
|
366
371
|
await promises_1.default.writeFile(this.lockFile, JSON.stringify(lockData, null, 2), { flag: 'wx' });
|
|
367
372
|
console.error('[Dashboard] Primary elected');
|
|
@@ -385,6 +390,9 @@ let HttpServer = class HttpServer {
|
|
|
385
390
|
if (!lockData.pid || !lockData.port || !lockData.startedAt) {
|
|
386
391
|
return { reclaim: true, reason: 'invalid lock structure' };
|
|
387
392
|
}
|
|
393
|
+
if (lockData.version !== CURRENT_VERSION) {
|
|
394
|
+
return { reclaim: true, reason: `version mismatch (lock=${lockData.version}, current=${CURRENT_VERSION})` };
|
|
395
|
+
}
|
|
388
396
|
const lastHeartbeat = new Date(lockData.lastHeartbeat || lockData.startedAt);
|
|
389
397
|
const ageMinutes = (Date.now() - lastHeartbeat.getTime()) / 60000;
|
|
390
398
|
if (ageMinutes > 2) {
|
|
@@ -404,20 +412,8 @@ let HttpServer = class HttpServer {
|
|
|
404
412
|
const lockData = JSON.parse(lockContent);
|
|
405
413
|
const { reclaim, reason } = this.shouldReclaimLock(lockData);
|
|
406
414
|
if (!reclaim) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return false;
|
|
410
|
-
}
|
|
411
|
-
console.error(`[Dashboard] Primary (PID ${lockData.pid}) not responding, attempting graceful shutdown`);
|
|
412
|
-
try {
|
|
413
|
-
process.kill(lockData.pid, 'SIGTERM');
|
|
414
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
415
|
-
}
|
|
416
|
-
catch { }
|
|
417
|
-
const stillHealthy = await this.checkHealth(lockData.port);
|
|
418
|
-
if (stillHealthy) {
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
415
|
+
console.error(`[Dashboard] Secondary mode: primary lock valid (PID ${lockData.pid}), yielding`);
|
|
416
|
+
return false;
|
|
421
417
|
}
|
|
422
418
|
else {
|
|
423
419
|
console.error(`[Dashboard] Lock reclaim needed: ${reason}`);
|
|
@@ -429,7 +425,8 @@ let HttpServer = class HttpServer {
|
|
|
429
425
|
startedAt: new Date().toISOString(),
|
|
430
426
|
lastHeartbeat: new Date().toISOString(),
|
|
431
427
|
projectId: this.sessionManager.getProjectId(),
|
|
432
|
-
projectPath: this.sessionManager.getProjectPath()
|
|
428
|
+
projectPath: this.sessionManager.getProjectPath(),
|
|
429
|
+
version: CURRENT_VERSION
|
|
433
430
|
};
|
|
434
431
|
try {
|
|
435
432
|
await promises_1.default.writeFile(tempPath, JSON.stringify(newLockData, null, 2));
|
|
@@ -474,23 +471,6 @@ let HttpServer = class HttpServer {
|
|
|
474
471
|
return await this.tryBecomePrimary();
|
|
475
472
|
}
|
|
476
473
|
}
|
|
477
|
-
async checkHealth(port) {
|
|
478
|
-
try {
|
|
479
|
-
const controller = new AbortController();
|
|
480
|
-
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
481
|
-
const response = await fetch(`http://localhost:${port}/api/health`, {
|
|
482
|
-
signal: controller.signal
|
|
483
|
-
});
|
|
484
|
-
clearTimeout(timeout);
|
|
485
|
-
if (!response.ok)
|
|
486
|
-
return false;
|
|
487
|
-
const data = await response.json();
|
|
488
|
-
return data.status === 'healthy' && data.isPrimary !== undefined;
|
|
489
|
-
}
|
|
490
|
-
catch {
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
474
|
setupPrimaryCleanup() {
|
|
495
475
|
if (this.processLifecyclePolicy.kind === 'no_signal_handlers') {
|
|
496
476
|
return;
|
|
@@ -609,6 +589,13 @@ let HttpServer = class HttpServer {
|
|
|
609
589
|
async stop() {
|
|
610
590
|
this.heartbeat.stop();
|
|
611
591
|
this.sessionManager.unwatchAll();
|
|
592
|
+
for (const dispose of this._routeDisposers) {
|
|
593
|
+
try {
|
|
594
|
+
dispose();
|
|
595
|
+
}
|
|
596
|
+
catch { }
|
|
597
|
+
}
|
|
598
|
+
this._routeDisposers.length = 0;
|
|
612
599
|
await new Promise((resolve) => {
|
|
613
600
|
if (!this.server)
|
|
614
601
|
return resolve();
|
|
@@ -628,7 +615,10 @@ let HttpServer = class HttpServer {
|
|
|
628
615
|
}
|
|
629
616
|
}
|
|
630
617
|
mountRoutes(installer) {
|
|
631
|
-
installer(this.app);
|
|
618
|
+
const disposer = installer(this.app);
|
|
619
|
+
if (disposer != null) {
|
|
620
|
+
this._routeDisposers.push(disposer);
|
|
621
|
+
}
|
|
632
622
|
}
|
|
633
623
|
finalize() {
|
|
634
624
|
this.app.use((req, res) => {
|
|
@@ -645,42 +635,50 @@ let HttpServer = class HttpServer {
|
|
|
645
635
|
getPort() {
|
|
646
636
|
return this.port;
|
|
647
637
|
}
|
|
648
|
-
async
|
|
638
|
+
async fullCleanup() {
|
|
649
639
|
try {
|
|
650
640
|
const busyPorts = await this.getWorkrailPorts();
|
|
651
641
|
if (busyPorts.length === 0) {
|
|
652
|
-
|
|
642
|
+
console.error('[Cleanup] No workrail processes found');
|
|
643
|
+
return 0;
|
|
653
644
|
}
|
|
654
|
-
console.error(`[Cleanup] Found ${busyPorts.length} workrail process(es),
|
|
645
|
+
console.error(`[Cleanup] Found ${busyPorts.length} workrail process(es), removing all...`);
|
|
655
646
|
let cleanedCount = 0;
|
|
656
647
|
for (const { port, pid } of busyPorts) {
|
|
657
|
-
if (pid === process.pid)
|
|
648
|
+
if (pid === process.pid) {
|
|
649
|
+
console.error(`[Cleanup] Skipping current process ${pid}`);
|
|
658
650
|
continue;
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
651
|
+
}
|
|
652
|
+
console.error(`[Cleanup] Killing process ${pid} on port ${port}`);
|
|
653
|
+
try {
|
|
654
|
+
process.kill(pid, 'SIGTERM');
|
|
655
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
662
656
|
try {
|
|
663
|
-
process.kill(pid,
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
process.kill(pid, 0);
|
|
667
|
-
process.kill(pid, 'SIGKILL');
|
|
668
|
-
}
|
|
669
|
-
catch {
|
|
670
|
-
}
|
|
671
|
-
cleanedCount++;
|
|
657
|
+
process.kill(pid, 0);
|
|
658
|
+
process.kill(pid, 'SIGKILL');
|
|
659
|
+
console.error(`[Cleanup] Force killed process ${pid}`);
|
|
672
660
|
}
|
|
673
|
-
catch
|
|
661
|
+
catch {
|
|
662
|
+
console.error(`[Cleanup] Process ${pid} terminated gracefully`);
|
|
674
663
|
}
|
|
664
|
+
cleanedCount++;
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
console.error(`[Cleanup] Failed to kill process ${pid}:`, error);
|
|
675
668
|
}
|
|
676
669
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
await
|
|
670
|
+
console.error(`[Cleanup] Cleaned up ${cleanedCount} process(es)`);
|
|
671
|
+
try {
|
|
672
|
+
await promises_1.default.unlink(this.lockFile);
|
|
673
|
+
console.error('[Cleanup] Removed lock file');
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
680
676
|
}
|
|
677
|
+
return cleanedCount;
|
|
681
678
|
}
|
|
682
679
|
catch (error) {
|
|
683
|
-
console.error('[Cleanup]
|
|
680
|
+
console.error('[Cleanup] Full cleanup failed:', error);
|
|
681
|
+
throw error;
|
|
684
682
|
}
|
|
685
683
|
}
|
|
686
684
|
async getWorkrailPorts() {
|
|
@@ -719,56 +717,10 @@ let HttpServer = class HttpServer {
|
|
|
719
717
|
}
|
|
720
718
|
return [];
|
|
721
719
|
}
|
|
722
|
-
catch
|
|
720
|
+
catch {
|
|
723
721
|
return [];
|
|
724
722
|
}
|
|
725
723
|
}
|
|
726
|
-
async fullCleanup() {
|
|
727
|
-
try {
|
|
728
|
-
const busyPorts = await this.getWorkrailPorts();
|
|
729
|
-
if (busyPorts.length === 0) {
|
|
730
|
-
console.error('[Cleanup] No workrail processes found');
|
|
731
|
-
return 0;
|
|
732
|
-
}
|
|
733
|
-
console.error(`[Cleanup] Found ${busyPorts.length} workrail process(es), removing all...`);
|
|
734
|
-
let cleanedCount = 0;
|
|
735
|
-
for (const { port, pid } of busyPorts) {
|
|
736
|
-
if (pid === process.pid) {
|
|
737
|
-
console.error(`[Cleanup] Skipping current process ${pid}`);
|
|
738
|
-
continue;
|
|
739
|
-
}
|
|
740
|
-
console.error(`[Cleanup] Killing process ${pid} on port ${port}`);
|
|
741
|
-
try {
|
|
742
|
-
process.kill(pid, 'SIGTERM');
|
|
743
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
744
|
-
try {
|
|
745
|
-
process.kill(pid, 0);
|
|
746
|
-
process.kill(pid, 'SIGKILL');
|
|
747
|
-
console.error(`[Cleanup] Force killed process ${pid}`);
|
|
748
|
-
}
|
|
749
|
-
catch {
|
|
750
|
-
console.error(`[Cleanup] Process ${pid} terminated gracefully`);
|
|
751
|
-
}
|
|
752
|
-
cleanedCount++;
|
|
753
|
-
}
|
|
754
|
-
catch (error) {
|
|
755
|
-
console.error(`[Cleanup] Failed to kill process ${pid}:`, error);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
console.error(`[Cleanup] Cleaned up ${cleanedCount} process(es)`);
|
|
759
|
-
try {
|
|
760
|
-
await promises_1.default.unlink(this.lockFile);
|
|
761
|
-
console.error('[Cleanup] Removed lock file');
|
|
762
|
-
}
|
|
763
|
-
catch {
|
|
764
|
-
}
|
|
765
|
-
return cleanedCount;
|
|
766
|
-
}
|
|
767
|
-
catch (error) {
|
|
768
|
-
console.error('[Cleanup] Full cleanup failed:', error);
|
|
769
|
-
throw error;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
724
|
};
|
|
773
725
|
exports.HttpServer = HttpServer;
|
|
774
726
|
exports.HttpServer = HttpServer = __decorate([
|
|
@@ -6,6 +6,7 @@ export declare class CachingWorkflowStorage implements IWorkflowStorage {
|
|
|
6
6
|
readonly kind: "single";
|
|
7
7
|
private workflowCache;
|
|
8
8
|
private summaryCache;
|
|
9
|
+
private workflowCacheIndex;
|
|
9
10
|
private stats;
|
|
10
11
|
constructor(inner: IWorkflowStorage, ttlMs: number);
|
|
11
12
|
get source(): WorkflowSource;
|
|
@@ -26,6 +27,7 @@ export declare class CachingCompositeWorkflowStorage implements ICompositeWorkfl
|
|
|
26
27
|
readonly kind: "composite";
|
|
27
28
|
private workflowCache;
|
|
28
29
|
private summaryCache;
|
|
30
|
+
private workflowCacheIndex;
|
|
29
31
|
private stats;
|
|
30
32
|
constructor(inner: ICompositeWorkflowStorage, ttlMs: number);
|
|
31
33
|
private isFresh;
|
|
@@ -14,6 +14,7 @@ class CachingWorkflowStorage {
|
|
|
14
14
|
this.kind = 'single';
|
|
15
15
|
this.workflowCache = null;
|
|
16
16
|
this.summaryCache = null;
|
|
17
|
+
this.workflowCacheIndex = null;
|
|
17
18
|
this.stats = { hits: 0, misses: 0 };
|
|
18
19
|
}
|
|
19
20
|
get source() {
|
|
@@ -25,6 +26,7 @@ class CachingWorkflowStorage {
|
|
|
25
26
|
clearCache() {
|
|
26
27
|
this.workflowCache = null;
|
|
27
28
|
this.summaryCache = null;
|
|
29
|
+
this.workflowCacheIndex = null;
|
|
28
30
|
}
|
|
29
31
|
isFresh(cache) {
|
|
30
32
|
return cache !== null && Date.now() - cache.timestamp < this.ttlMs;
|
|
@@ -37,16 +39,18 @@ class CachingWorkflowStorage {
|
|
|
37
39
|
this.stats.misses += 1;
|
|
38
40
|
const workflows = await this.inner.loadAllWorkflows();
|
|
39
41
|
this.workflowCache = { value: workflows, timestamp: Date.now() };
|
|
42
|
+
this.workflowCacheIndex = new Map(workflows.map((w) => [w.definition.id, w]));
|
|
40
43
|
this.summaryCache = null;
|
|
41
44
|
return deepClone(workflows);
|
|
42
45
|
}
|
|
43
46
|
async getWorkflowById(id) {
|
|
44
|
-
if (this.isFresh(this.workflowCache)) {
|
|
45
|
-
const wf = this.
|
|
46
|
-
if (wf) {
|
|
47
|
+
if (this.isFresh(this.workflowCache) && this.workflowCacheIndex !== null) {
|
|
48
|
+
const wf = this.workflowCacheIndex.get(id);
|
|
49
|
+
if (wf !== undefined) {
|
|
47
50
|
this.stats.hits += 1;
|
|
48
51
|
return deepClone(wf);
|
|
49
52
|
}
|
|
53
|
+
return null;
|
|
50
54
|
}
|
|
51
55
|
this.stats.misses += 1;
|
|
52
56
|
const workflow = await this.inner.getWorkflowById(id);
|
|
@@ -77,6 +81,7 @@ class CachingCompositeWorkflowStorage {
|
|
|
77
81
|
this.kind = 'composite';
|
|
78
82
|
this.workflowCache = null;
|
|
79
83
|
this.summaryCache = null;
|
|
84
|
+
this.workflowCacheIndex = null;
|
|
80
85
|
this.stats = { hits: 0, misses: 0 };
|
|
81
86
|
}
|
|
82
87
|
isFresh(cache) {
|
|
@@ -96,16 +101,18 @@ class CachingCompositeWorkflowStorage {
|
|
|
96
101
|
this.stats.misses += 1;
|
|
97
102
|
const workflows = await this.inner.loadAllWorkflows();
|
|
98
103
|
this.workflowCache = { value: workflows, timestamp: Date.now() };
|
|
104
|
+
this.workflowCacheIndex = new Map(workflows.map((w) => [w.definition.id, w]));
|
|
99
105
|
this.summaryCache = null;
|
|
100
106
|
return deepClone(workflows);
|
|
101
107
|
}
|
|
102
108
|
async getWorkflowById(id) {
|
|
103
|
-
if (this.isFresh(this.workflowCache)) {
|
|
104
|
-
const wf = this.
|
|
105
|
-
if (wf) {
|
|
109
|
+
if (this.isFresh(this.workflowCache) && this.workflowCacheIndex !== null) {
|
|
110
|
+
const wf = this.workflowCacheIndex.get(id);
|
|
111
|
+
if (wf !== undefined) {
|
|
106
112
|
this.stats.hits += 1;
|
|
107
113
|
return deepClone(wf);
|
|
108
114
|
}
|
|
115
|
+
return null;
|
|
109
116
|
}
|
|
110
117
|
this.stats.misses += 1;
|
|
111
118
|
const workflow = await this.inner.getWorkflowById(id);
|
|
@@ -126,6 +133,7 @@ class CachingCompositeWorkflowStorage {
|
|
|
126
133
|
await this.inner.save(definition);
|
|
127
134
|
this.workflowCache = null;
|
|
128
135
|
this.summaryCache = null;
|
|
136
|
+
this.workflowCacheIndex = null;
|
|
129
137
|
}
|
|
130
138
|
}
|
|
131
139
|
getCacheStats() {
|
|
@@ -134,6 +142,7 @@ class CachingCompositeWorkflowStorage {
|
|
|
134
142
|
clearCache() {
|
|
135
143
|
this.workflowCache = null;
|
|
136
144
|
this.summaryCache = null;
|
|
145
|
+
this.workflowCacheIndex = null;
|
|
137
146
|
}
|
|
138
147
|
}
|
|
139
148
|
exports.CachingCompositeWorkflowStorage = CachingCompositeWorkflowStorage;
|
|
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.FileWorkflowStorage = void 0;
|
|
7
7
|
exports.createDefaultFileWorkflowStorage = createDefaultFileWorkflowStorage;
|
|
8
8
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
-
const fs_1 = require("fs");
|
|
10
9
|
const path_1 = __importDefault(require("path"));
|
|
11
10
|
const workflow_1 = require("../../types/workflow");
|
|
12
11
|
const error_handler_1 = require("../../core/error-handler");
|
|
@@ -63,7 +62,7 @@ class FileWorkflowStorage {
|
|
|
63
62
|
}
|
|
64
63
|
const filePathRaw = path_1.default.resolve(this.baseDirReal, file);
|
|
65
64
|
assertWithinBase(filePathRaw, this.baseDirReal);
|
|
66
|
-
const stats =
|
|
65
|
+
const stats = await promises_1.default.stat(filePathRaw);
|
|
67
66
|
if (stats.size > this.maxFileSize)
|
|
68
67
|
continue;
|
|
69
68
|
const raw = await promises_1.default.readFile(filePathRaw, 'utf-8');
|
|
@@ -94,7 +93,7 @@ class FileWorkflowStorage {
|
|
|
94
93
|
const selection = (0, workflow_resolution_1.selectVariant)(candidates, flags);
|
|
95
94
|
const selected = files.find(f => f.file === selection.selectedIdentifier) ?? files[0];
|
|
96
95
|
const filePath = path_1.default.resolve(this.baseDirReal, selected.file);
|
|
97
|
-
const stats =
|
|
96
|
+
const stats = await promises_1.default.stat(filePath);
|
|
98
97
|
index.set(id, {
|
|
99
98
|
id: selected.definition.id,
|
|
100
99
|
filename: selected.file,
|
|
@@ -117,7 +116,7 @@ class FileWorkflowStorage {
|
|
|
117
116
|
const filePath = path_1.default.resolve(this.baseDirReal, filename);
|
|
118
117
|
assertWithinBase(filePath, this.baseDirReal);
|
|
119
118
|
try {
|
|
120
|
-
const stats =
|
|
119
|
+
const stats = await promises_1.default.stat(filePath);
|
|
121
120
|
if (stats.size > this.maxFileSize) {
|
|
122
121
|
throw new error_handler_1.SecurityError('Workflow file exceeds size limit', 'file-size');
|
|
123
122
|
}
|
|
@@ -10,6 +10,13 @@ const ajv_1 = __importDefault(require("ajv"));
|
|
|
10
10
|
const workflow_1 = require("../../types/workflow");
|
|
11
11
|
const error_handler_1 = require("../../core/error-handler");
|
|
12
12
|
const workflow_id_policy_1 = require("../../domain/workflow-id-policy");
|
|
13
|
+
function buildModuleValidator() {
|
|
14
|
+
const schemaPath = path_1.default.resolve(__dirname, '../../../spec/workflow.schema.json');
|
|
15
|
+
const schema = JSON.parse(fs_1.default.readFileSync(schemaPath, 'utf-8'));
|
|
16
|
+
const ajv = new ajv_1.default({ allErrors: true, strict: false });
|
|
17
|
+
return ajv.compile(schema);
|
|
18
|
+
}
|
|
19
|
+
const MODULE_WORKFLOW_VALIDATOR = buildModuleValidator();
|
|
13
20
|
const VALIDATION_ERROR_PREFIX = '[ValidationError]';
|
|
14
21
|
function reportValidationFailure(workflowId, sourceKind, error) {
|
|
15
22
|
console.error(`${VALIDATION_ERROR_PREFIX} ${sourceKind}/${workflowId}: ${error}`);
|
|
@@ -18,10 +25,7 @@ class SchemaValidatingWorkflowStorage {
|
|
|
18
25
|
constructor(inner) {
|
|
19
26
|
this.inner = inner;
|
|
20
27
|
this.kind = 'single';
|
|
21
|
-
|
|
22
|
-
const schema = JSON.parse(fs_1.default.readFileSync(schemaPath, 'utf-8'));
|
|
23
|
-
const ajv = new ajv_1.default({ allErrors: true, strict: false });
|
|
24
|
-
this.validator = ajv.compile(schema);
|
|
28
|
+
this.validator = MODULE_WORKFLOW_VALIDATOR;
|
|
25
29
|
}
|
|
26
30
|
get source() {
|
|
27
31
|
return this.inner.source;
|
|
@@ -81,10 +85,7 @@ class SchemaValidatingCompositeWorkflowStorage {
|
|
|
81
85
|
constructor(inner) {
|
|
82
86
|
this.inner = inner;
|
|
83
87
|
this.kind = 'composite';
|
|
84
|
-
|
|
85
|
-
const schema = JSON.parse(fs_1.default.readFileSync(schemaPath, 'utf-8'));
|
|
86
|
-
const ajv = new ajv_1.default({ allErrors: true, strict: false });
|
|
87
|
-
this.validator = ajv.compile(schema);
|
|
88
|
+
this.validator = MODULE_WORKFLOW_VALIDATOR;
|
|
88
89
|
}
|
|
89
90
|
validateDefinition(definition, sourceKind) {
|
|
90
91
|
const isValid = this.validator(definition);
|