@exaudeus/workrail 3.40.0 → 3.41.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/init.js +0 -3
- package/dist/cli-worktrain.js +8 -0
- package/dist/cli.js +0 -18
- package/dist/config/app-config.d.ts +0 -16
- package/dist/config/app-config.js +0 -14
- package/dist/config/config-file.js +0 -3
- package/dist/console-ui/assets/index-CQt4UhPB.js +28 -0
- package/dist/console-ui/assets/index-DGj8EsFR.css +1 -0
- package/dist/console-ui/index.html +2 -2
- package/dist/coordinators/pr-review.d.ts +17 -0
- package/dist/coordinators/pr-review.js +164 -0
- package/dist/daemon/daemon-events.d.ts +9 -1
- package/dist/daemon/soul-template.d.ts +2 -2
- package/dist/daemon/soul-template.js +11 -1
- package/dist/daemon/workflow-runner.d.ts +14 -1
- package/dist/daemon/workflow-runner.js +395 -25
- package/dist/di/container.js +1 -25
- package/dist/di/tokens.d.ts +0 -3
- package/dist/di/tokens.js +0 -3
- package/dist/engine/engine-factory.js +0 -1
- package/dist/infrastructure/console-defaults.d.ts +1 -0
- package/dist/infrastructure/console-defaults.js +4 -0
- package/dist/infrastructure/session/index.d.ts +0 -1
- package/dist/infrastructure/session/index.js +1 -3
- package/dist/manifest.json +87 -103
- package/dist/mcp/handlers/session.d.ts +1 -0
- package/dist/mcp/handlers/session.js +61 -13
- package/dist/mcp/server.js +1 -18
- package/dist/mcp/transports/http-entry.js +0 -2
- package/dist/mcp/transports/stdio-entry.js +1 -2
- package/dist/mcp/types.d.ts +0 -2
- package/dist/trigger/daemon-console.d.ts +2 -0
- package/dist/trigger/daemon-console.js +1 -1
- package/dist/trigger/trigger-listener.d.ts +2 -0
- package/dist/trigger/trigger-listener.js +3 -1
- package/dist/trigger/trigger-router.d.ts +4 -3
- package/dist/trigger/trigger-router.js +4 -3
- package/dist/trigger/trigger-store.js +17 -4
- package/dist/v2/usecases/console-routes.d.ts +2 -1
- package/dist/v2/usecases/console-routes.js +29 -5
- package/dist/v2/usecases/console-service.js +14 -0
- package/dist/v2/usecases/console-types.d.ts +1 -0
- package/docs/authoring.md +16 -16
- package/docs/design/coordinator-message-queue-drain-plan.md +241 -0
- package/docs/design/coordinator-message-queue-drain-review.md +120 -0
- package/docs/design/coordinator-message-queue-drain.md +289 -0
- package/docs/design/shaping-workflow-external-research.md +119 -0
- package/docs/discovery/late-bound-goals-impl-plan.md +147 -0
- package/docs/discovery/late-bound-goals-review.md +82 -0
- package/docs/discovery/late-bound-goals.md +118 -0
- package/docs/discovery/steer-endpoint-design-candidates.md +288 -0
- package/docs/discovery/steer-endpoint-design-review-findings.md +104 -0
- package/docs/discovery/steer-endpoint-implementation-plan.md +284 -0
- package/docs/ideas/backlog.md +292 -0
- package/docs/ideas/design-candidates-console-session-tree-impl.md +64 -0
- package/docs/ideas/design-candidates-session-tree-view.md +196 -0
- package/docs/ideas/design-review-findings-console-session-tree-impl.md +75 -0
- package/docs/ideas/design-review-findings-session-tree-view.md +88 -0
- package/docs/ideas/implementation_plan_session_tree_view.md +238 -0
- package/package.json +2 -1
- package/spec/authoring-spec.json +16 -16
- package/spec/shape.schema.json +178 -0
- package/spec/workflow-tags.json +232 -47
- package/workflows/coding-task-workflow-agentic.json +491 -480
- package/workflows/wr.shaping.json +182 -0
- package/dist/console-ui/assets/index-8dh0Psu-.css +0 -1
- package/dist/console-ui/assets/index-CXWCAonr.js +0 -28
- package/dist/infrastructure/session/DashboardHeartbeat.d.ts +0 -8
- package/dist/infrastructure/session/DashboardHeartbeat.js +0 -39
- package/dist/infrastructure/session/DashboardLockRelease.d.ts +0 -2
- package/dist/infrastructure/session/DashboardLockRelease.js +0 -29
- package/dist/infrastructure/session/HttpServer.d.ts +0 -60
- package/dist/infrastructure/session/HttpServer.js +0 -912
- package/workflows/coding-task-workflow-agentic.lean.v2.json +0 -648
- package/workflows/coding-task-workflow-agentic.v2.json +0 -324
|
@@ -36,15 +36,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.DAEMON_SOUL_TEMPLATE = exports.DAEMON_SOUL_DEFAULT = exports.DAEMON_SESSIONS_DIR = void 0;
|
|
39
|
+
exports.DAEMON_SIGNALS_DIR = exports.DAEMON_SOUL_TEMPLATE = exports.DAEMON_SOUL_DEFAULT = exports.DAEMON_SESSIONS_DIR = void 0;
|
|
40
40
|
exports.readDaemonSessionState = readDaemonSessionState;
|
|
41
41
|
exports.readAllDaemonSessions = readAllDaemonSessions;
|
|
42
42
|
exports.runStartupRecovery = runStartupRecovery;
|
|
43
43
|
exports.makeContinueWorkflowTool = makeContinueWorkflowTool;
|
|
44
44
|
exports.makeCompleteStepTool = makeCompleteStepTool;
|
|
45
45
|
exports.makeBashTool = makeBashTool;
|
|
46
|
+
exports.makeReadTool = makeReadTool;
|
|
47
|
+
exports.makeWriteTool = makeWriteTool;
|
|
48
|
+
exports.makeGlobTool = makeGlobTool;
|
|
49
|
+
exports.makeGrepTool = makeGrepTool;
|
|
50
|
+
exports.makeEditTool = makeEditTool;
|
|
46
51
|
exports.makeSpawnAgentTool = makeSpawnAgentTool;
|
|
47
52
|
exports.makeReportIssueTool = makeReportIssueTool;
|
|
53
|
+
exports.makeSignalCoordinatorTool = makeSignalCoordinatorTool;
|
|
48
54
|
exports.buildSessionRecap = buildSessionRecap;
|
|
49
55
|
exports.buildSystemPrompt = buildSystemPrompt;
|
|
50
56
|
exports.runWorkflow = runWorkflow;
|
|
@@ -54,6 +60,7 @@ const path = __importStar(require("node:path"));
|
|
|
54
60
|
const os = __importStar(require("node:os"));
|
|
55
61
|
const node_child_process_1 = require("node:child_process");
|
|
56
62
|
const node_util_1 = require("node:util");
|
|
63
|
+
const tinyglobby_1 = require("tinyglobby");
|
|
57
64
|
const node_crypto_1 = require("node:crypto");
|
|
58
65
|
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
59
66
|
const bedrock_sdk_1 = require("@anthropic-ai/bedrock-sdk");
|
|
@@ -65,6 +72,7 @@ const index_js_2 = require("../v2/durable-core/ids/index.js");
|
|
|
65
72
|
const node_outputs_js_1 = require("../v2/projections/node-outputs.js");
|
|
66
73
|
const assert_never_js_1 = require("../runtime/assert-never.js");
|
|
67
74
|
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
75
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
68
76
|
const BASH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
69
77
|
const MAX_SESSION_RECAP_NOTES = 3;
|
|
70
78
|
const MAX_SESSION_NOTE_CHARS = 800;
|
|
@@ -365,7 +373,9 @@ function getSchemas() {
|
|
|
365
373
|
ReadParams: {
|
|
366
374
|
type: 'object',
|
|
367
375
|
properties: {
|
|
368
|
-
filePath: { type: 'string', description: 'Absolute path to the file to read' },
|
|
376
|
+
filePath: { type: 'string', description: 'Absolute path to the file to read. Content is returned in cat -n format: each line prefixed with its 1-indexed line number and a tab character.' },
|
|
377
|
+
offset: { type: 'number', description: '0-indexed line number to start reading from (inclusive). Omit to read from the beginning.' },
|
|
378
|
+
limit: { type: 'number', description: 'Maximum number of lines to return. Omit to read to end of file.' },
|
|
369
379
|
},
|
|
370
380
|
required: ['filePath'],
|
|
371
381
|
},
|
|
@@ -377,6 +387,39 @@ function getSchemas() {
|
|
|
377
387
|
},
|
|
378
388
|
required: ['filePath', 'content'],
|
|
379
389
|
},
|
|
390
|
+
GlobParams: {
|
|
391
|
+
type: 'object',
|
|
392
|
+
properties: {
|
|
393
|
+
pattern: { type: 'string', description: 'Glob pattern to match (e.g. "**/*.ts"). Supports standard glob syntax.' },
|
|
394
|
+
path: { type: 'string', description: 'Absolute path to search root. Defaults to the workspace root.' },
|
|
395
|
+
},
|
|
396
|
+
required: ['pattern'],
|
|
397
|
+
},
|
|
398
|
+
GrepParams: {
|
|
399
|
+
type: 'object',
|
|
400
|
+
properties: {
|
|
401
|
+
pattern: { type: 'string', description: 'Regular expression pattern to search for in file contents.' },
|
|
402
|
+
path: { type: 'string', description: 'Absolute path to search in. Defaults to the workspace root.' },
|
|
403
|
+
glob: { type: 'string', description: 'Glob pattern to restrict which files are searched (e.g. "*.ts").' },
|
|
404
|
+
type: { type: 'string', description: 'File type filter for ripgrep (e.g. "ts", "js", "py").' },
|
|
405
|
+
output_mode: { type: 'string', enum: ['content', 'files_with_matches', 'count'], description: 'Output mode. "files_with_matches": only file paths (default). "content": matching lines with context. "count": match counts per file.' },
|
|
406
|
+
head_limit: { type: 'number', description: 'Maximum number of output lines to return. Default: 250.' },
|
|
407
|
+
context: { type: 'number', description: 'Number of lines of context to show before and after each match (output_mode=content only).' },
|
|
408
|
+
'-i': { type: 'boolean', description: 'Case-insensitive search.' },
|
|
409
|
+
},
|
|
410
|
+
required: ['pattern'],
|
|
411
|
+
},
|
|
412
|
+
EditParams: {
|
|
413
|
+
type: 'object',
|
|
414
|
+
properties: {
|
|
415
|
+
file_path: { type: 'string', description: 'Absolute path to the file to edit. The file must have been read in this session via the Read tool.' },
|
|
416
|
+
old_string: { type: 'string', description: 'Exact string to find and replace. Must appear exactly once in the file (or use replace_all=true for multiple occurrences). Do NOT include line-number prefixes from Read output.' },
|
|
417
|
+
new_string: { type: 'string', description: 'Replacement string. Must differ from old_string.' },
|
|
418
|
+
replace_all: { type: 'boolean', description: 'Replace all occurrences of old_string. Default: false (fails if more than one match).' },
|
|
419
|
+
},
|
|
420
|
+
required: ['file_path', 'old_string', 'new_string'],
|
|
421
|
+
additionalProperties: false,
|
|
422
|
+
},
|
|
380
423
|
SpawnAgentParams: {
|
|
381
424
|
type: 'object',
|
|
382
425
|
properties: {
|
|
@@ -648,37 +691,282 @@ function makeBashTool(workspacePath, schemas, sessionId, emitter, workrailSessio
|
|
|
648
691
|
},
|
|
649
692
|
};
|
|
650
693
|
}
|
|
651
|
-
function
|
|
694
|
+
function findActualString(fileContent, oldString) {
|
|
695
|
+
if (fileContent.includes(oldString))
|
|
696
|
+
return oldString;
|
|
697
|
+
const normalized = oldString
|
|
698
|
+
.replace(/[\u2018\u2019]/g, "'")
|
|
699
|
+
.replace(/[\u201C\u201D]/g, '"')
|
|
700
|
+
.replace(/\u2013/g, '-')
|
|
701
|
+
.replace(/\u2014/g, '--');
|
|
702
|
+
if (fileContent.includes(normalized))
|
|
703
|
+
return normalized;
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
const READ_SIZE_CAP_BYTES = 256 * 1024;
|
|
707
|
+
const GLOB_ALWAYS_EXCLUDE = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'];
|
|
708
|
+
function makeReadTool(readFileState, schemas, sessionId, emitter, workrailSessionId) {
|
|
652
709
|
return {
|
|
653
710
|
name: 'Read',
|
|
654
|
-
description: 'Read the contents of a file at the given absolute path.'
|
|
711
|
+
description: 'Read the contents of a file at the given absolute path. ' +
|
|
712
|
+
'Content is returned in cat -n format: each line is prefixed with its 1-indexed line number and a tab character (e.g. "1\\tline one\\n2\\tline two"). ' +
|
|
713
|
+
'Use offset (0-indexed start line) and limit (max lines) to read a slice of a large file.',
|
|
655
714
|
inputSchema: schemas['ReadParams'],
|
|
656
715
|
label: 'Read',
|
|
657
716
|
execute: async (_toolCallId, params) => {
|
|
717
|
+
const filePath = params.filePath;
|
|
658
718
|
if (sessionId)
|
|
659
|
-
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Read', summary:
|
|
660
|
-
const
|
|
719
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Read', summary: filePath.slice(0, 80), ...withWorkrailSession(workrailSessionId) });
|
|
720
|
+
const devPaths = ['/dev/stdin', '/dev/tty', '/dev/zero', '/dev/random', '/dev/full', '/dev/urandom'];
|
|
721
|
+
if (devPaths.some(d => filePath === d)) {
|
|
722
|
+
throw new Error(`Refusing to read device path: ${filePath}`);
|
|
723
|
+
}
|
|
724
|
+
const stat = await fs.stat(filePath);
|
|
725
|
+
const offset = params.offset ?? 0;
|
|
726
|
+
const limit = params.limit;
|
|
727
|
+
const isPaginated = params.offset !== undefined || params.limit !== undefined;
|
|
728
|
+
if (!isPaginated && stat.size > READ_SIZE_CAP_BYTES) {
|
|
729
|
+
throw new Error(`File is too large to read at once (${stat.size} bytes, cap is ${READ_SIZE_CAP_BYTES} bytes). ` +
|
|
730
|
+
`Use offset and limit parameters to read a specific range of lines.`);
|
|
731
|
+
}
|
|
732
|
+
const rawContent = await fs.readFile(filePath, 'utf8');
|
|
733
|
+
const allLines = rawContent.split('\n');
|
|
734
|
+
const isPartialView = offset !== 0 || limit != null;
|
|
735
|
+
const slicedLines = limit != null ? allLines.slice(offset, offset + limit) : allLines.slice(offset);
|
|
736
|
+
const startLine = offset;
|
|
737
|
+
const formatted = slicedLines.map((l, i) => `${startLine + i + 1}\t${l}`).join('\n');
|
|
738
|
+
readFileState.set(filePath, { content: rawContent, timestamp: stat.mtimeMs, isPartialView });
|
|
661
739
|
return {
|
|
662
|
-
content: [{ type: 'text', text:
|
|
663
|
-
details: { filePath:
|
|
740
|
+
content: [{ type: 'text', text: formatted }],
|
|
741
|
+
details: { filePath, totalLines: allLines.length, returnedLines: slicedLines.length, offset, isPartialView },
|
|
664
742
|
};
|
|
665
743
|
},
|
|
666
744
|
};
|
|
667
745
|
}
|
|
668
|
-
function makeWriteTool(schemas, sessionId, emitter, workrailSessionId) {
|
|
746
|
+
function makeWriteTool(readFileState, schemas, sessionId, emitter, workrailSessionId) {
|
|
669
747
|
return {
|
|
670
748
|
name: 'Write',
|
|
671
|
-
description: 'Write content to a file at the given absolute path. Creates parent directories if needed.'
|
|
749
|
+
description: 'Write content to a file at the given absolute path. Creates parent directories if needed. ' +
|
|
750
|
+
'For existing files: the file must have been read in this session and must not have changed on disk since then. ' +
|
|
751
|
+
'For new files (path does not exist): no prior read is required.',
|
|
672
752
|
inputSchema: schemas['WriteParams'],
|
|
673
753
|
label: 'Write',
|
|
674
754
|
execute: async (_toolCallId, params) => {
|
|
755
|
+
const filePath = params.filePath;
|
|
756
|
+
if (sessionId)
|
|
757
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Write', summary: filePath.slice(0, 80), ...withWorkrailSession(workrailSessionId) });
|
|
758
|
+
let existsOnDisk = false;
|
|
759
|
+
try {
|
|
760
|
+
await fs.access(filePath);
|
|
761
|
+
existsOnDisk = true;
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
}
|
|
765
|
+
if (existsOnDisk) {
|
|
766
|
+
const state = readFileState.get(filePath);
|
|
767
|
+
if (!state) {
|
|
768
|
+
throw new Error(`File has not been read in this session. Call Read first before writing to it: ${filePath}`);
|
|
769
|
+
}
|
|
770
|
+
const stat = await fs.stat(filePath);
|
|
771
|
+
if (stat.mtimeMs !== state.timestamp) {
|
|
772
|
+
throw new Error(`File has been modified since it was read. Re-read before writing: ${filePath}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
776
|
+
await fs.writeFile(filePath, params.content, 'utf8');
|
|
777
|
+
const newStat = await fs.stat(filePath);
|
|
778
|
+
readFileState.set(filePath, { content: params.content, timestamp: newStat.mtimeMs, isPartialView: false });
|
|
779
|
+
return {
|
|
780
|
+
content: [{ type: 'text', text: `Written ${params.content.length} bytes to ${filePath}` }],
|
|
781
|
+
details: { filePath, length: params.content.length },
|
|
782
|
+
};
|
|
783
|
+
},
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function makeGlobTool(workspacePath, schemas, sessionId, emitter, workrailSessionId) {
|
|
787
|
+
return {
|
|
788
|
+
name: 'Glob',
|
|
789
|
+
description: 'Find files matching a glob pattern. Returns newline-separated relative file paths, sorted by modification time descending. ' +
|
|
790
|
+
'node_modules, .git, dist, and build directories are always excluded. ' +
|
|
791
|
+
'Results are capped at 100 files.',
|
|
792
|
+
inputSchema: schemas['GlobParams'],
|
|
793
|
+
label: 'Glob',
|
|
794
|
+
execute: async (_toolCallId, params) => {
|
|
795
|
+
const pattern = params.pattern;
|
|
796
|
+
const searchRoot = params.path ?? workspacePath;
|
|
797
|
+
if (sessionId)
|
|
798
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Glob', summary: pattern.slice(0, 80), ...withWorkrailSession(workrailSessionId) });
|
|
799
|
+
const GLOB_LIMIT = 100;
|
|
800
|
+
let paths;
|
|
801
|
+
try {
|
|
802
|
+
paths = await (0, tinyglobby_1.glob)(pattern, {
|
|
803
|
+
cwd: searchRoot,
|
|
804
|
+
ignore: GLOB_ALWAYS_EXCLUDE,
|
|
805
|
+
absolute: false,
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
catch {
|
|
809
|
+
paths = [];
|
|
810
|
+
}
|
|
811
|
+
const withMtimes = await Promise.all(paths.map(async (p) => {
|
|
812
|
+
try {
|
|
813
|
+
const stat = await fs.stat(path.join(searchRoot, p));
|
|
814
|
+
return { p, mtime: stat.mtimeMs };
|
|
815
|
+
}
|
|
816
|
+
catch {
|
|
817
|
+
return { p, mtime: 0 };
|
|
818
|
+
}
|
|
819
|
+
}));
|
|
820
|
+
withMtimes.sort((a, b) => b.mtime - a.mtime);
|
|
821
|
+
const sorted = withMtimes.map(x => x.p);
|
|
822
|
+
const truncated = sorted.length > GLOB_LIMIT;
|
|
823
|
+
const result = sorted.slice(0, GLOB_LIMIT);
|
|
824
|
+
let text = result.join('\n');
|
|
825
|
+
if (truncated) {
|
|
826
|
+
text += '\n[Results truncated at 100 files]';
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
content: [{ type: 'text', text: text || '(no matches)' }],
|
|
830
|
+
details: { pattern, searchRoot, matchCount: sorted.length, truncated },
|
|
831
|
+
};
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function makeGrepTool(workspacePath, schemas, sessionId, emitter, workrailSessionId) {
|
|
836
|
+
return {
|
|
837
|
+
name: 'Grep',
|
|
838
|
+
description: 'Search file contents using ripgrep (rg). Fast regex search with optional context lines, file-type filtering, and case-insensitive mode. ' +
|
|
839
|
+
'output_mode: "files_with_matches" (default) returns only file paths; "content" returns matching lines; "count" returns match counts per file. ' +
|
|
840
|
+
'node_modules and .git are always excluded.',
|
|
841
|
+
inputSchema: schemas['GrepParams'],
|
|
842
|
+
label: 'Grep',
|
|
843
|
+
execute: async (_toolCallId, params) => {
|
|
844
|
+
const pattern = params.pattern;
|
|
845
|
+
const searchPath = params.path ?? workspacePath;
|
|
846
|
+
const outputMode = params.output_mode ?? 'files_with_matches';
|
|
847
|
+
const headLimit = params.head_limit ?? 250;
|
|
848
|
+
if (sessionId)
|
|
849
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Grep', summary: pattern.slice(0, 80), ...withWorkrailSession(workrailSessionId) });
|
|
850
|
+
const args = [
|
|
851
|
+
'--hidden',
|
|
852
|
+
'--glob', '!node_modules',
|
|
853
|
+
'--glob', '!.git',
|
|
854
|
+
'--max-columns', '500',
|
|
855
|
+
];
|
|
856
|
+
if (params['-i'])
|
|
857
|
+
args.push('-i');
|
|
858
|
+
if (params.glob) {
|
|
859
|
+
args.push('--glob', params.glob);
|
|
860
|
+
}
|
|
861
|
+
if (params.type) {
|
|
862
|
+
args.push('--type', params.type);
|
|
863
|
+
}
|
|
864
|
+
switch (outputMode) {
|
|
865
|
+
case 'files_with_matches':
|
|
866
|
+
args.push('--files-with-matches');
|
|
867
|
+
break;
|
|
868
|
+
case 'count':
|
|
869
|
+
args.push('--count');
|
|
870
|
+
break;
|
|
871
|
+
case 'content':
|
|
872
|
+
args.push('--vimgrep');
|
|
873
|
+
if (params.context != null) {
|
|
874
|
+
args.push('-C', String(params.context));
|
|
875
|
+
}
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
args.push('--', pattern, searchPath);
|
|
879
|
+
let stdout;
|
|
880
|
+
try {
|
|
881
|
+
const result = await execFileAsync('rg', args, { cwd: workspacePath, maxBuffer: 10 * 1024 * 1024 });
|
|
882
|
+
stdout = result.stdout;
|
|
883
|
+
}
|
|
884
|
+
catch (err) {
|
|
885
|
+
const nodeErr = err;
|
|
886
|
+
if (nodeErr.code === 'ENOENT') {
|
|
887
|
+
throw new Error('ripgrep (rg) is not installed. Install it with: brew install ripgrep (macOS) or apt install ripgrep (Ubuntu/Debian).');
|
|
888
|
+
}
|
|
889
|
+
if (typeof nodeErr.code === 'number' && nodeErr.code === 1) {
|
|
890
|
+
return {
|
|
891
|
+
content: [{ type: 'text', text: '(no matches)' }],
|
|
892
|
+
details: { pattern, searchPath, outputMode },
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
throw new Error(`rg failed: ${nodeErr.message ?? String(err)}`);
|
|
896
|
+
}
|
|
897
|
+
const lines = stdout.split('\n').filter(l => l.length > 0);
|
|
898
|
+
const truncated = lines.length > headLimit;
|
|
899
|
+
let result = lines.slice(0, headLimit).join('\n');
|
|
900
|
+
if (truncated) {
|
|
901
|
+
result += `\n[Results truncated at ${headLimit} lines. Use a more specific pattern or increase head_limit.]`;
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
content: [{ type: 'text', text: result || '(no matches)' }],
|
|
905
|
+
details: { pattern, searchPath, outputMode, lineCount: lines.length, truncated },
|
|
906
|
+
};
|
|
907
|
+
},
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function makeEditTool(workspacePath, readFileState, schemas, sessionId, emitter, workrailSessionId) {
|
|
911
|
+
return {
|
|
912
|
+
name: 'Edit',
|
|
913
|
+
description: 'Perform an exact string replacement in a file. ' +
|
|
914
|
+
'The file must have been read in this session via the Read tool. ' +
|
|
915
|
+
'By default, old_string must appear exactly once; use replace_all=true to replace all occurrences. ' +
|
|
916
|
+
'Do NOT include line-number prefixes (e.g. "1\\t") from Read output in old_string or new_string.',
|
|
917
|
+
inputSchema: schemas['EditParams'],
|
|
918
|
+
label: 'Edit',
|
|
919
|
+
execute: async (_toolCallId, params) => {
|
|
920
|
+
const rawFilePath = params.file_path;
|
|
921
|
+
const absoluteFilePath = path.isAbsolute(rawFilePath)
|
|
922
|
+
? rawFilePath
|
|
923
|
+
: path.join(workspacePath, rawFilePath);
|
|
924
|
+
if (!absoluteFilePath.startsWith(workspacePath)) {
|
|
925
|
+
throw new Error(`Edit target is outside the workspace: ${rawFilePath}`);
|
|
926
|
+
}
|
|
927
|
+
const filePath = absoluteFilePath;
|
|
928
|
+
const oldString = params.old_string;
|
|
929
|
+
const newString = params.new_string;
|
|
930
|
+
const replaceAll = params.replace_all ?? false;
|
|
675
931
|
if (sessionId)
|
|
676
|
-
emitter?.emit({ kind: 'tool_called', sessionId, toolName: '
|
|
677
|
-
|
|
678
|
-
|
|
932
|
+
emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Edit', summary: filePath.slice(0, 80), ...withWorkrailSession(workrailSessionId) });
|
|
933
|
+
if (oldString === newString) {
|
|
934
|
+
throw new Error('old_string and new_string are identical. No edit needed.');
|
|
935
|
+
}
|
|
936
|
+
const state = readFileState.get(filePath);
|
|
937
|
+
if (!state) {
|
|
938
|
+
throw new Error(`File has not been read in this session. Call Read first before editing: ${filePath}`);
|
|
939
|
+
}
|
|
940
|
+
let stat;
|
|
941
|
+
try {
|
|
942
|
+
stat = await fs.stat(filePath);
|
|
943
|
+
}
|
|
944
|
+
catch {
|
|
945
|
+
throw new Error(`File not found: ${filePath}. It may have been deleted after it was read.`);
|
|
946
|
+
}
|
|
947
|
+
if (stat.mtimeMs !== state.timestamp) {
|
|
948
|
+
throw new Error(`File has been modified since it was read. Re-read before editing: ${filePath}`);
|
|
949
|
+
}
|
|
950
|
+
const currentContent = await fs.readFile(filePath, 'utf8');
|
|
951
|
+
const actualString = findActualString(currentContent, oldString);
|
|
952
|
+
if (actualString === null) {
|
|
953
|
+
throw new Error(`String to replace not found in file. Make sure old_string exactly matches the file content ` +
|
|
954
|
+
`(do not include line-number prefixes from Read output): ${filePath}`);
|
|
955
|
+
}
|
|
956
|
+
const occurrences = currentContent.split(actualString).length - 1;
|
|
957
|
+
if (!replaceAll && occurrences > 1) {
|
|
958
|
+
throw new Error(`old_string appears ${occurrences} times in the file. ` +
|
|
959
|
+
`Provide a more specific string that matches exactly once, or set replace_all=true to replace all occurrences.`);
|
|
960
|
+
}
|
|
961
|
+
const updatedContent = replaceAll
|
|
962
|
+
? currentContent.split(actualString).join(newString)
|
|
963
|
+
: currentContent.replace(actualString, newString);
|
|
964
|
+
await fs.writeFile(filePath, updatedContent, 'utf8');
|
|
965
|
+
const newStat = await fs.stat(filePath);
|
|
966
|
+
readFileState.set(filePath, { content: updatedContent, timestamp: newStat.mtimeMs, isPartialView: false });
|
|
679
967
|
return {
|
|
680
|
-
content: [{ type: 'text', text: `
|
|
681
|
-
details: { filePath
|
|
968
|
+
content: [{ type: 'text', text: `The file ${filePath} has been updated successfully.` }],
|
|
969
|
+
details: { filePath, occurrencesReplaced: occurrences },
|
|
682
970
|
};
|
|
683
971
|
},
|
|
684
972
|
};
|
|
@@ -691,7 +979,8 @@ function makeSpawnAgentTool(sessionId, ctx, apiKey, thisWorkrailSessionId, curre
|
|
|
691
979
|
'Use this when a step requires delegating a well-defined sub-task to a separate workflow. ' +
|
|
692
980
|
'IMPORTANT: The parent session\'s time limit (maxSessionMinutes) keeps ticking while the child runs. ' +
|
|
693
981
|
'Configure the parent with enough time to cover both its own work and the child\'s work. ' +
|
|
694
|
-
'Returns: { childSessionId, outcome: "success"|"error"|"timeout", notes: string }. ' +
|
|
982
|
+
'Returns: { childSessionId, outcome: "success"|"error"|"timeout", notes: string, artifacts?: readonly unknown[] }. ' +
|
|
983
|
+
'On success, artifacts contains the child session\'s final step artifacts if any were produced. ' +
|
|
695
984
|
'Check outcome before using notes -- on error/timeout, notes contains the error message.',
|
|
696
985
|
inputSchema: schemas['SpawnAgentParams'],
|
|
697
986
|
label: 'Spawn Agent',
|
|
@@ -755,6 +1044,7 @@ function makeSpawnAgentTool(sessionId, ctx, apiKey, thisWorkrailSessionId, curre
|
|
|
755
1044
|
childSessionId,
|
|
756
1045
|
outcome: 'success',
|
|
757
1046
|
notes: childResult.lastStepNotes ?? '(no notes from child session)',
|
|
1047
|
+
...(childResult.lastStepArtifacts !== undefined ? { artifacts: childResult.lastStepArtifacts } : {}),
|
|
758
1048
|
};
|
|
759
1049
|
}
|
|
760
1050
|
else if (childResult._tag === 'error') {
|
|
@@ -873,6 +1163,74 @@ function makeReportIssueTool(sessionId, emitter, workrailSessionId, issuesDirOve
|
|
|
873
1163
|
},
|
|
874
1164
|
};
|
|
875
1165
|
}
|
|
1166
|
+
exports.DAEMON_SIGNALS_DIR = path.join(os.homedir(), '.workrail', 'signals');
|
|
1167
|
+
async function appendSignalAsync(signalsDir, sessionId, record) {
|
|
1168
|
+
await fs.mkdir(signalsDir, { recursive: true });
|
|
1169
|
+
const filePath = path.join(signalsDir, `${sessionId}.jsonl`);
|
|
1170
|
+
const line = JSON.stringify({ ...record, ts: Date.now() }) + '\n';
|
|
1171
|
+
await fs.appendFile(filePath, line, 'utf8');
|
|
1172
|
+
}
|
|
1173
|
+
function makeSignalCoordinatorTool(sessionId, emitter, workrailSessionId, signalsDirOverride) {
|
|
1174
|
+
const signalsDir = signalsDirOverride ?? exports.DAEMON_SIGNALS_DIR;
|
|
1175
|
+
return {
|
|
1176
|
+
name: 'signal_coordinator',
|
|
1177
|
+
description: 'Emit a structured mid-session signal to the coordinator WITHOUT advancing the workflow step. ' +
|
|
1178
|
+
'Use this to surface progress updates, intermediate findings, data requests, ' +
|
|
1179
|
+
'approval requests, or blocking conditions while the session continues. ' +
|
|
1180
|
+
'Always returns immediately -- fire-and-observe, never blocks. ' +
|
|
1181
|
+
'Signal kinds: "progress" (heartbeat, no data needed), "finding" (intermediate result), ' +
|
|
1182
|
+
'"data_needed" (request external data), "approval_needed" (request coordinator approval), ' +
|
|
1183
|
+
'"blocked" (cannot continue without coordinator intervention).',
|
|
1184
|
+
inputSchema: {
|
|
1185
|
+
type: 'object',
|
|
1186
|
+
properties: {
|
|
1187
|
+
signalKind: {
|
|
1188
|
+
type: 'string',
|
|
1189
|
+
enum: ['progress', 'finding', 'data_needed', 'approval_needed', 'blocked'],
|
|
1190
|
+
description: 'The kind of signal to emit.',
|
|
1191
|
+
},
|
|
1192
|
+
payload: {
|
|
1193
|
+
type: 'object',
|
|
1194
|
+
additionalProperties: true,
|
|
1195
|
+
description: 'Structured data accompanying the signal. Pass {} for progress signals.',
|
|
1196
|
+
},
|
|
1197
|
+
},
|
|
1198
|
+
required: ['signalKind', 'payload'],
|
|
1199
|
+
additionalProperties: false,
|
|
1200
|
+
},
|
|
1201
|
+
label: 'signal_coordinator',
|
|
1202
|
+
execute: async (_toolCallId, params) => {
|
|
1203
|
+
const signalId = 'sig_' + (0, node_crypto_1.randomUUID)().replace(/-/g, '').slice(0, 8);
|
|
1204
|
+
const signalKind = String(params.signalKind ?? 'progress');
|
|
1205
|
+
const payload = (typeof params.payload === 'object' && params.payload !== null && !Array.isArray(params.payload))
|
|
1206
|
+
? params.payload
|
|
1207
|
+
: {};
|
|
1208
|
+
console.log(`[WorkflowRunner] Tool: signal_coordinator sessionId=${sessionId} signalKind=${signalKind} signalId=${signalId}`);
|
|
1209
|
+
const record = {
|
|
1210
|
+
signalId,
|
|
1211
|
+
sessionId,
|
|
1212
|
+
...(workrailSessionId != null ? { workrailSessionId } : {}),
|
|
1213
|
+
signalKind,
|
|
1214
|
+
payload,
|
|
1215
|
+
};
|
|
1216
|
+
void appendSignalAsync(signalsDir, sessionId, record).catch(() => {
|
|
1217
|
+
});
|
|
1218
|
+
emitter?.emit({
|
|
1219
|
+
kind: 'signal_emitted',
|
|
1220
|
+
sessionId,
|
|
1221
|
+
signalKind,
|
|
1222
|
+
signalId,
|
|
1223
|
+
payload,
|
|
1224
|
+
...(workrailSessionId != null ? { workrailSessionId } : {}),
|
|
1225
|
+
});
|
|
1226
|
+
const result = { status: 'recorded', signalId };
|
|
1227
|
+
return {
|
|
1228
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
1229
|
+
details: result,
|
|
1230
|
+
};
|
|
1231
|
+
},
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
876
1234
|
const BASE_SYSTEM_PROMPT = `\
|
|
877
1235
|
You are WorkRail Auto, an autonomous agent that executes workflows step by step. You are running unattended -- there is no user watching. Your entire job is to faithfully complete the current workflow.
|
|
878
1236
|
|
|
@@ -900,6 +1258,7 @@ Good pattern: "Question: Should I check the middleware? Answer: The workflow ste
|
|
|
900
1258
|
- \`Write\`: Write files.
|
|
901
1259
|
- \`report_issue\`: Record a structured issue, error, or unexpected behavior. Call this AND complete_step (unless fatal). Does not stop the session -- it creates a record for the auto-fix coordinator.
|
|
902
1260
|
- \`spawn_agent\`: Delegate a sub-task to a child WorkRail session. BLOCKS until the child completes. Returns \`{ childSessionId, outcome: "success"|"error"|"timeout", notes: string }\`. Always check \`outcome\` before using \`notes\`. IMPORTANT: your session's time limit (maxSessionMinutes) keeps running while the child executes -- ensure your parent session has enough time for both your work AND the child's work. Maximum spawn depth is 3 by default (configurable). Use only when a step explicitly asks for delegation or when a clearly separable sub-task would benefit from its own WorkRail audit trail.
|
|
1261
|
+
- \`signal_coordinator\`: Emit a structured mid-session signal to the coordinator WITHOUT advancing the workflow step. Use when the step asks you to surface a finding, request data, request approval, or report a blocking condition. Always returns immediately -- fire-and-observe. Signal kinds: "progress", "finding", "data_needed", "approval_needed", "blocked".
|
|
903
1262
|
|
|
904
1263
|
## Execution contract
|
|
905
1264
|
1. Read the step carefully. Do ALL the work the step asks for.
|
|
@@ -964,7 +1323,7 @@ function buildUserMessage(text) {
|
|
|
964
1323
|
timestamp: Date.now(),
|
|
965
1324
|
};
|
|
966
1325
|
}
|
|
967
|
-
async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
1326
|
+
async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter, steerRegistry) {
|
|
968
1327
|
const sessionId = (0, node_crypto_1.randomUUID)();
|
|
969
1328
|
console.log(`[WorkflowRunner] Session started: sessionId=${sessionId} workflowId=${trigger.workflowId}`);
|
|
970
1329
|
emitter?.emit({
|
|
@@ -1002,7 +1361,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
1002
1361
|
}
|
|
1003
1362
|
}
|
|
1004
1363
|
let isComplete = false;
|
|
1005
|
-
|
|
1364
|
+
const pendingSteerParts = [];
|
|
1006
1365
|
let lastStepNotes;
|
|
1007
1366
|
let lastStepArtifacts;
|
|
1008
1367
|
let stepAdvanceCount = 0;
|
|
@@ -1011,7 +1370,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
1011
1370
|
const issueSummaries = [];
|
|
1012
1371
|
const MAX_ISSUE_SUMMARIES = 10;
|
|
1013
1372
|
const onAdvance = (stepText, continueToken) => {
|
|
1014
|
-
|
|
1373
|
+
pendingSteerParts.push(stepText);
|
|
1015
1374
|
stepAdvanceCount++;
|
|
1016
1375
|
currentContinueToken = continueToken;
|
|
1017
1376
|
if (workrailSessionId !== null)
|
|
@@ -1054,6 +1413,9 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
1054
1413
|
if (workrailSessionId !== null) {
|
|
1055
1414
|
daemonRegistry?.register(workrailSessionId, trigger.workflowId);
|
|
1056
1415
|
}
|
|
1416
|
+
if (workrailSessionId !== null) {
|
|
1417
|
+
steerRegistry?.set(workrailSessionId, (text) => { pendingSteerParts.push(text); });
|
|
1418
|
+
}
|
|
1057
1419
|
if (startContinueToken) {
|
|
1058
1420
|
await persistTokens(sessionId, startContinueToken, startCheckpointToken);
|
|
1059
1421
|
}
|
|
@@ -1067,18 +1429,23 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
1067
1429
|
const schemas = getSchemas();
|
|
1068
1430
|
const spawnCurrentDepth = trigger.spawnDepth ?? 0;
|
|
1069
1431
|
const spawnMaxDepth = trigger.agentConfig?.maxSubagentDepth ?? 3;
|
|
1432
|
+
const readFileState = new Map();
|
|
1070
1433
|
const tools = [
|
|
1071
1434
|
makeCompleteStepTool(sessionId, ctx, () => currentContinueToken, onAdvance, onComplete, (t) => { currentContinueToken = t; }, schemas, index_js_1.executeContinueWorkflow, emitter, workrailSessionId),
|
|
1072
1435
|
makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, index_js_1.executeContinueWorkflow, emitter, workrailSessionId),
|
|
1073
1436
|
makeBashTool(trigger.workspacePath, schemas, sessionId, emitter, workrailSessionId),
|
|
1074
|
-
makeReadTool(schemas, sessionId, emitter, workrailSessionId),
|
|
1075
|
-
makeWriteTool(schemas, sessionId, emitter, workrailSessionId),
|
|
1437
|
+
makeReadTool(readFileState, schemas, sessionId, emitter, workrailSessionId),
|
|
1438
|
+
makeWriteTool(readFileState, schemas, sessionId, emitter, workrailSessionId),
|
|
1439
|
+
makeGlobTool(trigger.workspacePath, schemas, sessionId, emitter, workrailSessionId),
|
|
1440
|
+
makeGrepTool(trigger.workspacePath, schemas, sessionId, emitter, workrailSessionId),
|
|
1441
|
+
makeEditTool(trigger.workspacePath, readFileState, schemas, sessionId, emitter, workrailSessionId),
|
|
1076
1442
|
makeReportIssueTool(sessionId, emitter, workrailSessionId, undefined, (summary) => {
|
|
1077
1443
|
if (issueSummaries.length < MAX_ISSUE_SUMMARIES) {
|
|
1078
1444
|
issueSummaries.push(summary);
|
|
1079
1445
|
}
|
|
1080
1446
|
}),
|
|
1081
1447
|
makeSpawnAgentTool(sessionId, ctx, apiKey, workrailSessionId ?? '', spawnCurrentDepth, spawnMaxDepth, runWorkflow, schemas, emitter),
|
|
1448
|
+
makeSignalCoordinatorTool(sessionId, emitter, workrailSessionId),
|
|
1082
1449
|
];
|
|
1083
1450
|
const [soulContent, workspaceContext, sessionNotes] = await Promise.all([
|
|
1084
1451
|
loadDaemonSoul(trigger.soulFile),
|
|
@@ -1193,10 +1560,10 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
1193
1560
|
...withWorkrailSession(workrailSessionId),
|
|
1194
1561
|
});
|
|
1195
1562
|
}
|
|
1196
|
-
if (
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
agent.steer(buildUserMessage(
|
|
1563
|
+
if (pendingSteerParts.length > 0 && !isComplete) {
|
|
1564
|
+
const joined = pendingSteerParts.join('\n\n');
|
|
1565
|
+
pendingSteerParts.length = 0;
|
|
1566
|
+
agent.steer(buildUserMessage(joined));
|
|
1200
1567
|
}
|
|
1201
1568
|
});
|
|
1202
1569
|
let stopReason = 'stop';
|
|
@@ -1237,6 +1604,9 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
|
|
|
1237
1604
|
unsubscribe();
|
|
1238
1605
|
if (timeoutHandle !== undefined)
|
|
1239
1606
|
clearTimeout(timeoutHandle);
|
|
1607
|
+
if (workrailSessionId !== null) {
|
|
1608
|
+
steerRegistry?.delete(workrailSessionId);
|
|
1609
|
+
}
|
|
1240
1610
|
console.log(`[WorkflowRunner] Agent loop ended: sessionId=${sessionId} stopReason=${stopReason}${errorMessage ? ` error=${errorMessage.slice(0, 120)}` : ''}`);
|
|
1241
1611
|
}
|
|
1242
1612
|
if (timeoutReason !== null) {
|
package/dist/di/container.js
CHANGED
|
@@ -81,8 +81,6 @@ async function registerConfig(env) {
|
|
|
81
81
|
tsyringe_1.container.register(tokens_js_1.DI.Config.CacheTTL, { useValue: config.cache.ttlMs });
|
|
82
82
|
tsyringe_1.container.register(tokens_js_1.DI.Config.WorkflowDir, { useValue: config.paths.workflowDir });
|
|
83
83
|
tsyringe_1.container.register(tokens_js_1.DI.Config.ProjectPath, { useValue: config.paths.projectPath });
|
|
84
|
-
tsyringe_1.container.register(tokens_js_1.DI.Config.DashboardMode, { useValue: config.dashboard.mode });
|
|
85
|
-
tsyringe_1.container.register(tokens_js_1.DI.Config.BrowserBehavior, { useValue: config.dashboard.browserBehavior });
|
|
86
84
|
}
|
|
87
85
|
if (!tsyringe_1.container.isRegistered(tokens_js_1.DI.Infra.FeatureFlags)) {
|
|
88
86
|
const { CustomEnvFeatureFlagProvider } = await Promise.resolve().then(() => __importStar(require('../config/feature-flags.js')));
|
|
@@ -156,7 +154,6 @@ async function registerServices() {
|
|
|
156
154
|
const { ToolDescriptionProvider } = await Promise.resolve().then(() => __importStar(require('../mcp/tool-description-provider.js')));
|
|
157
155
|
const { DefaultWorkflowService } = await Promise.resolve().then(() => __importStar(require('../application/services/workflow-service.js')));
|
|
158
156
|
const { SessionManager } = await Promise.resolve().then(() => __importStar(require('../infrastructure/session/SessionManager.js')));
|
|
159
|
-
const { HttpServer } = await Promise.resolve().then(() => __importStar(require('../infrastructure/session/HttpServer.js')));
|
|
160
157
|
tsyringe_1.container.register(tokens_js_1.DI.Infra.EnhancedLoopValidator, {
|
|
161
158
|
useFactory: (0, tsyringe_1.instanceCachingFactory)((c) => c.resolve(EnhancedLoopValidator))
|
|
162
159
|
});
|
|
@@ -181,9 +178,6 @@ async function registerServices() {
|
|
|
181
178
|
tsyringe_1.container.register(tokens_js_1.DI.Infra.SessionManager, {
|
|
182
179
|
useFactory: (0, tsyringe_1.instanceCachingFactory)((c) => c.resolve(SessionManager))
|
|
183
180
|
});
|
|
184
|
-
tsyringe_1.container.register(tokens_js_1.DI.Infra.HttpServer, {
|
|
185
|
-
useFactory: (0, tsyringe_1.instanceCachingFactory)((c) => c.resolve(HttpServer))
|
|
186
|
-
});
|
|
187
181
|
if (!tsyringe_1.container.isRegistered(tokens_js_1.DI.Mcp.DescriptionProvider)) {
|
|
188
182
|
tsyringe_1.container.registerSingleton(tokens_js_1.DI.Mcp.DescriptionProvider, ToolDescriptionProvider);
|
|
189
183
|
}
|
|
@@ -358,25 +352,7 @@ async function startAsyncServices() {
|
|
|
358
352
|
}
|
|
359
353
|
if (asyncInitialized)
|
|
360
354
|
return;
|
|
361
|
-
|
|
362
|
-
const flags = tsyringe_1.container.resolve(tokens_js_1.DI.Infra.FeatureFlags);
|
|
363
|
-
if (flags.isEnabled('sessionTools')) {
|
|
364
|
-
const server = tsyringe_1.container.resolve(tokens_js_1.DI.Infra.HttpServer);
|
|
365
|
-
try {
|
|
366
|
-
await server.start();
|
|
367
|
-
console.error('[DI] HTTP server started');
|
|
368
|
-
}
|
|
369
|
-
catch (httpError) {
|
|
370
|
-
const message = httpError instanceof Error ? httpError.message : String(httpError);
|
|
371
|
-
console.error(`[DI] Dashboard HTTP server unavailable: ${message}. MCP tools will still work.`);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
asyncInitialized = true;
|
|
375
|
-
}
|
|
376
|
-
catch (error) {
|
|
377
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
378
|
-
throw new Error(`[DI] Async services initialization failed: ${message}`);
|
|
379
|
-
}
|
|
355
|
+
asyncInitialized = true;
|
|
380
356
|
}
|
|
381
357
|
async function bootstrap(options = {}) {
|
|
382
358
|
await initializeContainer(options);
|
package/dist/di/tokens.d.ts
CHANGED
|
@@ -12,7 +12,6 @@ export declare const DI: {
|
|
|
12
12
|
readonly Infra: {
|
|
13
13
|
readonly FeatureFlags: symbol;
|
|
14
14
|
readonly SessionManager: symbol;
|
|
15
|
-
readonly HttpServer: symbol;
|
|
16
15
|
readonly ValidationEngine: symbol;
|
|
17
16
|
readonly EnhancedLoopValidator: symbol;
|
|
18
17
|
readonly SessionDataNormalizer: symbol;
|
|
@@ -55,8 +54,6 @@ export declare const DI: {
|
|
|
55
54
|
readonly CacheTTL: symbol;
|
|
56
55
|
readonly WorkflowDir: symbol;
|
|
57
56
|
readonly ProjectPath: symbol;
|
|
58
|
-
readonly DashboardMode: symbol;
|
|
59
|
-
readonly BrowserBehavior: symbol;
|
|
60
57
|
};
|
|
61
58
|
};
|
|
62
59
|
export type DIToken = typeof DI[keyof typeof DI][keyof typeof DI[keyof typeof DI]];
|
package/dist/di/tokens.js
CHANGED
|
@@ -15,7 +15,6 @@ exports.DI = {
|
|
|
15
15
|
Infra: {
|
|
16
16
|
FeatureFlags: Symbol('Infra.FeatureFlags'),
|
|
17
17
|
SessionManager: Symbol('Infra.SessionManager'),
|
|
18
|
-
HttpServer: Symbol('Infra.HttpServer'),
|
|
19
18
|
ValidationEngine: Symbol('Infra.ValidationEngine'),
|
|
20
19
|
EnhancedLoopValidator: Symbol('Infra.EnhancedLoopValidator'),
|
|
21
20
|
SessionDataNormalizer: Symbol('Infra.SessionDataNormalizer'),
|
|
@@ -58,7 +57,5 @@ exports.DI = {
|
|
|
58
57
|
CacheTTL: Symbol('Config.CacheTTL'),
|
|
59
58
|
WorkflowDir: Symbol('Config.WorkflowDir'),
|
|
60
59
|
ProjectPath: Symbol('Config.ProjectPath'),
|
|
61
|
-
DashboardMode: Symbol('Config.DashboardMode'),
|
|
62
|
-
BrowserBehavior: Symbol('Config.BrowserBehavior'),
|
|
63
60
|
},
|
|
64
61
|
};
|