@exaudeus/workrail 3.16.0 → 3.18.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.
Files changed (68) hide show
  1. package/dist/application/services/validation-engine.js +7 -11
  2. package/dist/application/services/workflow-compiler.js +9 -11
  3. package/dist/application/use-cases/raw-workflow-file-scanner.js +10 -13
  4. package/dist/cli/commands/index.d.ts +1 -1
  5. package/dist/cli/commands/index.js +2 -1
  6. package/dist/cli/commands/init.d.ts +10 -0
  7. package/dist/cli/commands/init.js +72 -0
  8. package/dist/cli.js +13 -1
  9. package/dist/config/config-file.d.ts +8 -0
  10. package/dist/config/config-file.js +141 -0
  11. package/dist/config/feature-flags.js +8 -0
  12. package/dist/console/assets/index-BwJelCXK.js +28 -0
  13. package/dist/console/index.html +1 -1
  14. package/dist/di/container.d.ts +1 -0
  15. package/dist/di/container.js +24 -7
  16. package/dist/infrastructure/session/HttpServer.d.ts +0 -1
  17. package/dist/infrastructure/session/HttpServer.js +4 -46
  18. package/dist/manifest.json +120 -128
  19. package/dist/mcp/assert-output.js +2 -1
  20. package/dist/mcp/dev-mode.d.ts +1 -0
  21. package/dist/mcp/dev-mode.js +12 -0
  22. package/dist/mcp/handler-factory.d.ts +1 -1
  23. package/dist/mcp/handler-factory.js +8 -7
  24. package/dist/mcp/handlers/shared/request-workflow-reader.d.ts +1 -0
  25. package/dist/mcp/handlers/shared/request-workflow-reader.js +90 -20
  26. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.d.ts +1 -1
  27. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.js +14 -11
  28. package/dist/mcp/handlers/v2-advance-core/assessment-validation.d.ts +5 -3
  29. package/dist/mcp/handlers/v2-advance-core/assessment-validation.js +109 -87
  30. package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +0 -4
  31. package/dist/mcp/handlers/v2-advance-core/input-validation.js +1 -3
  32. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +8 -3
  33. package/dist/mcp/handlers/v2-advance-core/outcome-success.js +8 -3
  34. package/dist/mcp/handlers/v2-execution/continue-advance.d.ts +1 -0
  35. package/dist/mcp/handlers/v2-execution/continue-advance.js +3 -1
  36. package/dist/mcp/handlers/v2-execution/continue-rehydrate.d.ts +1 -0
  37. package/dist/mcp/handlers/v2-execution/continue-rehydrate.js +2 -1
  38. package/dist/mcp/handlers/v2-execution/index.js +2 -0
  39. package/dist/mcp/handlers/v2-execution/replay.d.ts +2 -0
  40. package/dist/mcp/handlers/v2-execution/replay.js +7 -4
  41. package/dist/mcp/handlers/v2-execution/start.js +48 -20
  42. package/dist/mcp/handlers/v2-workflow.js +4 -2
  43. package/dist/mcp/output-schemas.d.ts +17 -12
  44. package/dist/mcp/output-schemas.js +12 -11
  45. package/dist/mcp/server.js +3 -2
  46. package/dist/mcp/v2-response-formatter.d.ts +1 -1
  47. package/dist/mcp/v2-response-formatter.js +2 -3
  48. package/dist/types/workflow-definition.d.ts +3 -1
  49. package/dist/types/workflow-definition.js +2 -0
  50. package/dist/v2/durable-core/domain/prompt-renderer.d.ts +1 -0
  51. package/dist/v2/durable-core/domain/prompt-renderer.js +5 -2
  52. package/dist/v2/durable-core/schemas/compiled-workflow/index.js +4 -3
  53. package/dist/v2/infra/local/pinned-workflow-store/index.d.ts +2 -0
  54. package/dist/v2/infra/local/pinned-workflow-store/index.js +49 -0
  55. package/dist/v2/infra/local/remembered-roots-store/index.d.ts +3 -1
  56. package/dist/v2/infra/local/remembered-roots-store/index.js +6 -3
  57. package/dist/v2/infra/local/workspace-anchor/index.js +4 -2
  58. package/dist/v2/ports/pinned-workflow-store.port.d.ts +2 -0
  59. package/dist/v2/usecases/console-routes.js +3 -2
  60. package/package.json +1 -1
  61. package/spec/authoring-spec.json +3 -3
  62. package/spec/workflow.schema.json +1 -2
  63. package/workflows/workflow-for-workflows.json +558 -448
  64. package/dist/console/assets/index-BE5PAgPO.js +0 -28
  65. package/dist/env-flags.d.ts +0 -1
  66. package/dist/env-flags.js +0 -4
  67. package/dist/mcp/handlers/v2-resolve-refs-envelope.d.ts +0 -5
  68. package/dist/mcp/handlers/v2-resolve-refs-envelope.js +0 -17
@@ -685,24 +685,24 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
685
685
  issues.push(`${stepLabel}: assessmentConsequences must not be empty when declared`);
686
686
  return;
687
687
  }
688
- if (!step.assessmentRefs || step.assessmentRefs.length !== 1) {
689
- issues.push(`${stepLabel}: assessmentConsequences require exactly one assessmentRef on the same step`);
690
- suggestions.push(`Add exactly one assessmentRef to ${stepLabel} before declaring assessmentConsequences`);
688
+ if (!step.assessmentRefs || step.assessmentRefs.length === 0) {
689
+ issues.push(`${stepLabel}: assessmentConsequences require at least one assessmentRef on the same step`);
690
+ suggestions.push(`Add at least one assessmentRef to ${stepLabel} before declaring assessmentConsequences`);
691
691
  return;
692
692
  }
693
693
  if (step.assessmentConsequences.length > 1) {
694
694
  issues.push(`${stepLabel}: v1 assessment support allows exactly one assessment consequence per step`);
695
695
  suggestions.push(`Reduce assessmentConsequences on ${stepLabel} to a single declaration`);
696
696
  }
697
- const assessmentDefinition = assessments.find(assessment => assessment.id === step.assessmentRefs?.[0]);
698
- if (!assessmentDefinition)
697
+ const referencedDefinitions = assessments.filter(assessment => step.assessmentRefs.includes(assessment.id));
698
+ if (referencedDefinitions.length === 0)
699
699
  return;
700
700
  for (const consequence of step.assessmentConsequences) {
701
701
  const trigger = consequence.when;
702
702
  const effect = consequence.effect;
703
- const allLevels = assessmentDefinition.dimensions.flatMap(d => d.levels);
703
+ const allLevels = referencedDefinitions.flatMap(def => def.dimensions.flatMap(d => d.levels));
704
704
  if (!allLevels.includes(trigger.anyEqualsLevel)) {
705
- issues.push(`${stepLabel}: assessment consequence anyEqualsLevel '${trigger.anyEqualsLevel}' is not declared in any dimension of assessment '${assessmentDefinition.id}'`);
705
+ issues.push(`${stepLabel}: assessment consequence anyEqualsLevel '${trigger.anyEqualsLevel}' is not declared in any dimension of any referenced assessment`);
706
706
  suggestions.push(`Use a level declared in one of the dimensions: ${[...new Set(allLevels)].join(', ')}`);
707
707
  }
708
708
  if (effect.kind !== 'require_followup') {
@@ -776,10 +776,6 @@ let ValidationEngine = ValidationEngine_1 = class ValidationEngine {
776
776
  }
777
777
  }
778
778
  validateAssessmentRefsForStep(typedStep, `Step '${step.id}'`);
779
- if (typedStep.assessmentRefs !== undefined && typedStep.assessmentRefs.length > 1) {
780
- issues.push(`Step '${step.id}': v1 assessment support allows exactly one assessmentRef per step`);
781
- suggestions.push(`Reduce assessmentRefs on step '${step.id}' to a single assessment id`);
782
- }
783
779
  validateAssessmentConsequencesForStep(typedStep, `Step '${step.id}'`);
784
780
  const callValidation = this.validateStepFunctionCalls(step, workflow.definition.functionDefinitions || []);
785
781
  if (!callValidation.valid) {
@@ -140,21 +140,19 @@ let WorkflowCompiler = class WorkflowCompiler {
140
140
  const assessmentConsequences = typedStep.assessmentConsequences;
141
141
  if (!assessmentConsequences)
142
142
  continue;
143
- if (!typedStep.assessmentRefs || typedStep.assessmentRefs.length !== 1) {
144
- return (0, neverthrow_1.err)(error_1.Err.invalidState(`Step '${step.id}' declares assessmentConsequences but does not declare exactly one assessmentRef`));
145
- }
146
- const assessment = (workflow.definition.assessments ?? []).find(candidate => candidate.id === typedStep.assessmentRefs?.[0]);
147
- if (!assessment) {
148
- return (0, neverthrow_1.err)(error_1.Err.invalidState(`Step '${step.id}' declares assessmentConsequences for unknown assessmentRef '${typedStep.assessmentRefs[0]}'`));
143
+ if (!typedStep.assessmentRefs || typedStep.assessmentRefs.length === 0) {
144
+ return (0, neverthrow_1.err)(error_1.Err.invalidState(`Step '${step.id}' declares assessmentConsequences but declares no assessmentRefs`));
149
145
  }
150
146
  if (assessmentConsequences.length > 1) {
151
147
  return (0, neverthrow_1.err)(error_1.Err.invalidState(`Step '${step.id}' declares ${assessmentConsequences.length} assessment consequences. V1 supports exactly one assessment consequence per step.`));
152
148
  }
149
+ const allLevelsAcrossRefs = (workflow.definition.assessments ?? [])
150
+ .filter(candidate => typedStep.assessmentRefs.includes(candidate.id))
151
+ .flatMap(assessment => assessment.dimensions.flatMap(d => d.levels));
153
152
  for (const consequence of assessmentConsequences) {
154
153
  const trigger = consequence.when;
155
- const allLevels = assessment.dimensions.flatMap(d => d.levels);
156
- if (!allLevels.includes(trigger.anyEqualsLevel)) {
157
- return (0, neverthrow_1.err)(error_1.Err.invalidState(`Step '${step.id}' declares consequence with anyEqualsLevel '${trigger.anyEqualsLevel}' that is not declared in any dimension of assessment '${assessment.id}'`));
154
+ if (!allLevelsAcrossRefs.includes(trigger.anyEqualsLevel)) {
155
+ return (0, neverthrow_1.err)(error_1.Err.invalidState(`Step '${step.id}' declares consequence with anyEqualsLevel '${trigger.anyEqualsLevel}' that is not declared in any dimension of any referenced assessment`));
158
156
  }
159
157
  if (consequence.effect.kind !== 'require_followup') {
160
158
  return (0, neverthrow_1.err)(error_1.Err.invalidState(`Step '${step.id}' declares unsupported assessment consequence effect '${String(consequence.effect.kind)}'`));
@@ -187,8 +185,8 @@ let WorkflowCompiler = class WorkflowCompiler {
187
185
  }
188
186
  }
189
187
  if (bodyStep.assessmentConsequences) {
190
- if (!bodyStep.assessmentRefs || bodyStep.assessmentRefs.length !== 1) {
191
- return (0, neverthrow_1.err)(error_1.Err.invalidState(`Loop body step '${bodyStep.id}' in loop '${loop.id}' declares assessmentConsequences but does not declare exactly one assessmentRef`));
188
+ if (!bodyStep.assessmentRefs || bodyStep.assessmentRefs.length === 0) {
189
+ return (0, neverthrow_1.err)(error_1.Err.invalidState(`Loop body step '${bodyStep.id}' in loop '${loop.id}' declares assessmentConsequences but declares no assessmentRefs`));
192
190
  }
193
191
  }
194
192
  }
@@ -6,26 +6,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.findWorkflowJsonFiles = findWorkflowJsonFiles;
7
7
  exports.scanRawWorkflowFiles = scanRawWorkflowFiles;
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
  async function findWorkflowJsonFiles(baseDirReal) {
12
11
  const files = [];
13
12
  async function scan(currentDir) {
14
13
  const entries = await promises_1.default.readdir(currentDir, { withFileTypes: true });
15
- for (const entry of entries) {
16
- const fullPath = path_1.default.join(currentDir, entry.name);
17
- if (entry.isDirectory()) {
18
- if (entry.name === 'examples') {
19
- continue;
20
- }
21
- await scan(fullPath);
22
- }
23
- else if (entry.isFile() && entry.name.endsWith('.json')) {
24
- files.push(fullPath);
25
- }
14
+ const jsonFiles = entries.filter((e) => e.isFile() && e.name.endsWith('.json'));
15
+ const subDirs = entries.filter((e) => e.isDirectory() && e.name !== 'examples');
16
+ for (const f of jsonFiles) {
17
+ files.push(path_1.default.join(currentDir, f.name));
26
18
  }
19
+ await Promise.all(subDirs.map((d) => scan(path_1.default.join(currentDir, d.name)).catch((err) => {
20
+ if (err.code !== 'ENOENT')
21
+ throw err;
22
+ })));
27
23
  }
28
24
  await scan(baseDirReal);
25
+ files.sort();
29
26
  return files;
30
27
  }
31
28
  async function scanRawWorkflowFiles(baseDirReal) {
@@ -34,7 +31,7 @@ async function scanRawWorkflowFiles(baseDirReal) {
34
31
  for (const filePath of allJsonFiles) {
35
32
  const relativeFilePath = path_1.default.relative(baseDirReal, filePath);
36
33
  try {
37
- const stats = (0, fs_1.statSync)(filePath);
34
+ const stats = await promises_1.default.stat(filePath);
38
35
  if (stats.size > 1000000) {
39
36
  results.push({
40
37
  kind: 'unparseable',
@@ -1,4 +1,4 @@
1
- export { executeInitCommand, type InitCommandDeps } from './init.js';
1
+ export { executeInitCommand, executeInitConfigCommand, type InitCommandDeps, type InitConfigCommandDeps } from './init.js';
2
2
  export { executeSourcesCommand, getWorkflowSources, type SourcesCommandDeps, type WorkflowSource } from './sources.js';
3
3
  export { executeListCommand, type ListCommandDeps, type ListCommandOptions } from './list.js';
4
4
  export { executeValidateCommand, type ValidateCommandDeps } from './validate.js';
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detectWorkflowVersion = exports.migrateWorkflowFile = exports.migrateWorkflow = exports.executeMigrateCommand = exports.executeCleanupCommand = exports.executeStartCommand = exports.executeValidateCommand = exports.executeListCommand = exports.getWorkflowSources = exports.executeSourcesCommand = exports.executeInitCommand = void 0;
3
+ exports.detectWorkflowVersion = exports.migrateWorkflowFile = exports.migrateWorkflow = exports.executeMigrateCommand = exports.executeCleanupCommand = exports.executeStartCommand = exports.executeValidateCommand = exports.executeListCommand = exports.getWorkflowSources = exports.executeSourcesCommand = exports.executeInitConfigCommand = exports.executeInitCommand = void 0;
4
4
  var init_js_1 = require("./init.js");
5
5
  Object.defineProperty(exports, "executeInitCommand", { enumerable: true, get: function () { return init_js_1.executeInitCommand; } });
6
+ Object.defineProperty(exports, "executeInitConfigCommand", { enumerable: true, get: function () { return init_js_1.executeInitConfigCommand; } });
6
7
  var sources_js_1 = require("./sources.js");
7
8
  Object.defineProperty(exports, "executeSourcesCommand", { enumerable: true, get: function () { return sources_js_1.executeSourcesCommand; } });
8
9
  Object.defineProperty(exports, "getWorkflowSources", { enumerable: true, get: function () { return sources_js_1.getWorkflowSources; } });
@@ -8,4 +8,14 @@ export interface InitCommandDeps {
8
8
  readonly homedir: () => string;
9
9
  readonly joinPath: (...paths: string[]) => string;
10
10
  }
11
+ export interface InitConfigCommandDeps {
12
+ readonly mkdir: (path: string, options: {
13
+ recursive: boolean;
14
+ }) => Promise<string | undefined>;
15
+ readonly readFile: (path: string) => Promise<string>;
16
+ readonly writeFile: (path: string, content: string) => Promise<void>;
17
+ readonly homedir: () => string;
18
+ readonly joinPath: (...paths: string[]) => string;
19
+ }
11
20
  export declare function executeInitCommand(deps: InitCommandDeps): Promise<CliResult>;
21
+ export declare function executeInitConfigCommand(deps: InitConfigCommandDeps): Promise<CliResult>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.executeInitCommand = executeInitCommand;
4
+ exports.executeInitConfigCommand = executeInitConfigCommand;
4
5
  const cli_result_js_1 = require("../types/cli-result.js");
5
6
  const SAMPLE_WORKFLOW = {
6
7
  id: 'my-custom-workflow',
@@ -47,3 +48,74 @@ async function executeInitCommand(deps) {
47
48
  });
48
49
  }
49
50
  }
51
+ const CONFIG_FILE_TEMPLATE = `{
52
+ "_comment": "WorkRail configuration file. Uncomment any key to override its default.",
53
+ "_docs": "Full reference: https://github.com/exaudeus/workrail/blob/main/docs/configuration.md",
54
+
55
+ "CACHE_TTL": "300000",
56
+ "WORKRAIL_WORKFLOWS_DIR": "",
57
+ "WORKRAIL_DISABLE_UNIFIED_DASHBOARD": "0",
58
+ "WORKRAIL_DISABLE_AUTO_OPEN": "0",
59
+ "WORKRAIL_DASHBOARD_PORT": "3456",
60
+
61
+ "WORKRAIL_ENABLE_SESSION_TOOLS": "true",
62
+ "WORKRAIL_ENABLE_EXPERIMENTAL_WORKFLOWS": "false",
63
+ "WORKRAIL_VERBOSE_LOGGING": "false",
64
+ "WORKRAIL_ENABLE_AGENTIC_ROUTINES": "true",
65
+ "WORKRAIL_ENABLE_LEAN_WORKFLOWS": "false",
66
+ "WORKRAIL_AUTHORITATIVE_DESCRIPTIONS": "false",
67
+ "WORKRAIL_ENABLE_V2_TOOLS": "true",
68
+ "WORKRAIL_CLEAN_RESPONSE_FORMAT": "false",
69
+
70
+ "WORKFLOW_STORAGE_PATH": "",
71
+ "WORKFLOW_GIT_REPOS": "",
72
+ "WORKFLOW_GIT_REPO_URL": "",
73
+ "WORKFLOW_GIT_REPO_BRANCH": "main",
74
+ "WORKFLOW_GIT_SYNC_INTERVAL": "60",
75
+
76
+ "WORKRAIL_LOG_LEVEL": "SILENT",
77
+ "WORKRAIL_LOG_FORMAT": "human",
78
+ "WORKRAIL_DATA_DIR": "",
79
+ "WORKRAIL_CACHE_DIR": "",
80
+
81
+ "WORKRAIL_JSON_RESPONSES": "false"
82
+ }
83
+ `;
84
+ async function executeInitConfigCommand(deps) {
85
+ const configDir = deps.joinPath(deps.homedir(), '.workrail');
86
+ const configPath = deps.joinPath(configDir, 'config.json');
87
+ try {
88
+ const existing = await deps.readFile(configPath);
89
+ return (0, cli_result_js_1.success)({
90
+ message: `Config file already exists at ${configPath}`,
91
+ details: [
92
+ 'Current contents:',
93
+ existing,
94
+ '(File was not modified. Delete it and re-run to regenerate the template.)',
95
+ ],
96
+ });
97
+ }
98
+ catch {
99
+ }
100
+ try {
101
+ await deps.mkdir(configDir, { recursive: true });
102
+ await deps.writeFile(configPath, CONFIG_FILE_TEMPLATE);
103
+ return (0, cli_result_js_1.success)({
104
+ message: `Config file written to ${configPath}`,
105
+ details: [
106
+ 'Edit the file to set your preferred defaults.',
107
+ 'Environment variables always override values in this file.',
108
+ 'Sensitive values (tokens, keys) must still be set as environment variables.',
109
+ ],
110
+ suggestions: [
111
+ `Edit: ${configPath}`,
112
+ 'Documentation: workrail docs configuration',
113
+ ],
114
+ });
115
+ }
116
+ catch (error) {
117
+ return (0, cli_result_js_1.failure)(`Failed to write config file: ${error instanceof Error ? error.message : String(error)}`, {
118
+ suggestions: ['Check that you have write permissions to your home directory'],
119
+ });
120
+ }
121
+ }
package/dist/cli.js CHANGED
@@ -26,7 +26,19 @@ program
26
26
  program
27
27
  .command('init')
28
28
  .description('Initialize user workflow directory with sample workflows')
29
- .action(async () => {
29
+ .option('--config', 'Write a ~/.workrail/config.json template instead of initializing workflows')
30
+ .action(async (options) => {
31
+ if (options.config) {
32
+ const result = await (0, index_js_1.executeInitConfigCommand)({
33
+ mkdir: (p, opts) => fs_1.default.promises.mkdir(p, opts),
34
+ readFile: (p) => fs_1.default.promises.readFile(p, 'utf-8'),
35
+ writeFile: (p, content) => fs_1.default.promises.writeFile(p, content, 'utf-8'),
36
+ homedir: os_1.default.homedir,
37
+ joinPath: path_1.default.join,
38
+ });
39
+ (0, interpret_result_js_1.interpretCliResultWithoutDI)(result);
40
+ return;
41
+ }
30
42
  const result = await (0, index_js_1.executeInitCommand)({
31
43
  mkdir: (p, opts) => fs_1.default.promises.mkdir(p, opts),
32
44
  readdir: (p) => fs_1.default.promises.readdir(p),
@@ -0,0 +1,8 @@
1
+ import type { Result } from '../runtime/result.js';
2
+ export type ConfigFileError = {
3
+ readonly _tag: 'ConfigFileError';
4
+ readonly message: string;
5
+ readonly cause?: unknown;
6
+ };
7
+ export declare function ensureWorkrailConfigFile(): void;
8
+ export declare function loadWorkrailConfigFile(): Result<Record<string, string>, ConfigFileError>;
@@ -0,0 +1,141 @@
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.ensureWorkrailConfigFile = ensureWorkrailConfigFile;
37
+ exports.loadWorkrailConfigFile = loadWorkrailConfigFile;
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const fs = __importStar(require("fs"));
41
+ const zod_1 = require("zod");
42
+ const result_js_1 = require("../runtime/result.js");
43
+ const ALLOWED_CONFIG_FILE_KEYS = new Set([
44
+ 'CACHE_TTL',
45
+ 'WORKRAIL_WORKFLOWS_DIR',
46
+ 'WORKRAIL_DISABLE_UNIFIED_DASHBOARD',
47
+ 'WORKRAIL_DISABLE_AUTO_OPEN',
48
+ 'WORKRAIL_DASHBOARD_PORT',
49
+ 'WORKRAIL_DEV',
50
+ 'WORKRAIL_ENABLE_SESSION_TOOLS',
51
+ 'WORKRAIL_ENABLE_EXPERIMENTAL_WORKFLOWS',
52
+ 'WORKRAIL_VERBOSE_LOGGING',
53
+ 'WORKRAIL_ENABLE_AGENTIC_ROUTINES',
54
+ 'WORKRAIL_ENABLE_LEAN_WORKFLOWS',
55
+ 'WORKRAIL_AUTHORITATIVE_DESCRIPTIONS',
56
+ 'WORKRAIL_ENABLE_V2_TOOLS',
57
+ 'WORKRAIL_CLEAN_RESPONSE_FORMAT',
58
+ 'WORKFLOW_STORAGE_PATH',
59
+ 'WORKFLOW_GIT_REPOS',
60
+ 'WORKFLOW_GIT_REPO_URL',
61
+ 'WORKFLOW_GIT_REPO_BRANCH',
62
+ 'WORKFLOW_GIT_SYNC_INTERVAL',
63
+ 'WORKRAIL_LOG_LEVEL',
64
+ 'WORKRAIL_LOG_FORMAT',
65
+ 'WORKRAIL_DATA_DIR',
66
+ 'WORKRAIL_CACHE_DIR',
67
+ 'WORKRAIL_JSON_RESPONSES',
68
+ ]);
69
+ const ConfigFileSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.string());
70
+ const CONFIG_FILE_TEMPLATE = `{
71
+ "_comment": "WorkRail configuration. Values here are defaults; process.env always wins.",
72
+ "_docs": "https://github.com/exaudeus/workrail/blob/main/docs/configuration.md",
73
+
74
+ "CACHE_TTL": "300000",
75
+ "WORKRAIL_ENABLE_SESSION_TOOLS": "true",
76
+ "WORKRAIL_ENABLE_AGENTIC_ROUTINES": "true",
77
+ "WORKRAIL_ENABLE_V2_TOOLS": "true",
78
+ "WORKRAIL_ENABLE_LEAN_WORKFLOWS": "false",
79
+ "WORKRAIL_AUTHORITATIVE_DESCRIPTIONS": "false",
80
+ "WORKRAIL_CLEAN_RESPONSE_FORMAT": "false",
81
+ "WORKRAIL_VERBOSE_LOGGING": "false",
82
+
83
+ "WORKFLOW_STORAGE_PATH": "",
84
+ "WORKFLOW_GIT_REPOS": "",
85
+
86
+ "WORKRAIL_LOG_LEVEL": "SILENT",
87
+ "WORKRAIL_LOG_FORMAT": "human"
88
+ }
89
+ `;
90
+ function ensureWorkrailConfigFile() {
91
+ if (process.env['VITEST'])
92
+ return;
93
+ const configPath = path.join(os.homedir(), '.workrail', 'config.json');
94
+ try {
95
+ fs.accessSync(configPath);
96
+ }
97
+ catch {
98
+ try {
99
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
100
+ fs.writeFileSync(configPath, CONFIG_FILE_TEMPLATE, 'utf-8');
101
+ }
102
+ catch {
103
+ }
104
+ }
105
+ }
106
+ function loadWorkrailConfigFile() {
107
+ const configPath = path.join(os.homedir(), '.workrail', 'config.json');
108
+ let rawContent;
109
+ try {
110
+ rawContent = fs.readFileSync(configPath, 'utf-8');
111
+ }
112
+ catch (e) {
113
+ const code = e.code;
114
+ if (code !== 'ENOENT') {
115
+ console.warn(`[WorkRail] Could not read config file at ${configPath}: ${e.message}`);
116
+ }
117
+ return (0, result_js_1.ok)({});
118
+ }
119
+ let parsed;
120
+ try {
121
+ parsed = JSON.parse(rawContent);
122
+ }
123
+ catch {
124
+ console.warn(`[WorkRail] config file at ${configPath} contains invalid JSON -- ignoring it. Fix or regenerate with "workrail init --config".`);
125
+ return (0, result_js_1.ok)({});
126
+ }
127
+ const result = ConfigFileSchema.safeParse(parsed);
128
+ if (!result.success) {
129
+ console.warn(`[WorkRail] config file at ${configPath} has an unexpected shape -- ignoring it. Expected a flat JSON object with string values.`);
130
+ return (0, result_js_1.ok)({});
131
+ }
132
+ const validated = {};
133
+ for (const [key, value] of Object.entries(result.data)) {
134
+ if (!ALLOWED_CONFIG_FILE_KEYS.has(key)) {
135
+ console.warn(`[WorkRail] config file: unknown key "${key}" -- ignored. See "workrail init --config" for supported keys.`);
136
+ continue;
137
+ }
138
+ validated[key] = value;
139
+ }
140
+ return (0, result_js_1.ok)(validated);
141
+ }
@@ -77,6 +77,14 @@ exports.FEATURE_FLAG_DEFINITIONS = [
77
77
  since: '0.11.0',
78
78
  stable: false,
79
79
  },
80
+ {
81
+ key: 'devMode',
82
+ envVar: 'WORKRAIL_DEV',
83
+ defaultValue: false,
84
+ description: 'Enable development features: staleness visibility for all workflow categories, structured tool-call timing on stderr, and /api/v2/perf/tool-calls endpoint',
85
+ since: '0.11.0',
86
+ stable: false,
87
+ },
80
88
  ];
81
89
  function parseBoolean(value, defaultValue) {
82
90
  if (value === undefined) {