@bpinhosilva/agent-orchestrator 1.1.2 → 1.2.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 (95) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +7 -0
  3. package/dist/cli/commands/run.command.js +20 -1
  4. package/dist/cli/commands/setup.command.js +13 -0
  5. package/dist/cli/constants.js +2 -1
  6. package/dist/cli/env.js +2 -0
  7. package/dist/cli/preload.js +12 -0
  8. package/dist/cli/process-manager.js +33 -29
  9. package/dist/cli/setup/index.js +24 -0
  10. package/dist/cli/setup/validators.js +14 -0
  11. package/dist/common/postman/postman-conversion.js +57 -0
  12. package/dist/config/env.validation.js +6 -0
  13. package/dist/config/runtime-logger.js +214 -0
  14. package/dist/main.js +8 -0
  15. package/dist/tasks/recurrent-task-scheduler.service.js +3 -4
  16. package/dist/ui/assets/{AgentFleet-BarfEwcK.js → AgentFleet-B8p5vbKK.js} +1 -1
  17. package/dist/ui/assets/{AttachmentItem-Cerhbyya.js → AttachmentItem-C_uUACPz.js} +1 -1
  18. package/dist/ui/assets/{ConfirmDialog-y3vTcqFR.js → ConfirmDialog-BltdtevE.js} +1 -1
  19. package/dist/ui/assets/{CreateRecurrentTaskModal-BgfnJ1n8.js → CreateRecurrentTaskModal-CGSdgNAf.js} +1 -1
  20. package/dist/ui/assets/{CreateTaskModal-BH9lOLYT.js → CreateTaskModal-XNvIWKz3.js} +1 -1
  21. package/dist/ui/assets/{ExecLogModal-ShacCOOV.js → ExecLogModal-BYTGF04P.js} +1 -1
  22. package/dist/ui/assets/{MarkdownField--1gpyapw.js → MarkdownField-DR6tHsei.js} +1 -1
  23. package/dist/ui/assets/{Profile-qnuRN5Qv.js → Profile-ByioVLM8.js} +1 -1
  24. package/dist/ui/assets/{ProjectDetail-ZYQG9yn2.js → ProjectDetail-dwZDzb0m.js} +1 -1
  25. package/dist/ui/assets/{Providers-DR7srJHq.js → Providers-JAClEFt0.js} +1 -1
  26. package/dist/ui/assets/{Scheduler-Bs3DC0re.js → Scheduler-C1HQNxQ6.js} +2 -2
  27. package/dist/ui/assets/{Settings-C3ad44vP.js → Settings-Cjn2xdo1.js} +1 -1
  28. package/dist/ui/assets/{TaskDetail-D-f_Ao2a.js → TaskDetail-Blphf9tw.js} +1 -1
  29. package/dist/ui/assets/{TaskExecutions-BeodRIz1.js → TaskExecutions-ydR4KPCY.js} +3 -3
  30. package/dist/ui/assets/{TaskManager-BA2Y31ut.js → TaskManager-DrFbuAwN.js} +3 -3
  31. package/dist/ui/assets/{UserDetail-BvWm-U4y.js → UserDetail-mWjJPTzO.js} +1 -1
  32. package/dist/ui/assets/{Users-CQNcXLyV.js → Users-FaX0thJx.js} +1 -1
  33. package/dist/ui/assets/{activity-CmAajKmR.js → activity-B_2QSywM.js} +1 -1
  34. package/dist/ui/assets/{agents-b8btewki.js → agents-YQJ7JNfI.js} +1 -1
  35. package/dist/ui/assets/{auth-dROenHXk.js → auth-Lh2N3iYN.js} +1 -1
  36. package/dist/ui/assets/{bot-QVmnxPf4.js → bot-DadO9w3D.js} +1 -1
  37. package/dist/ui/assets/{box-Bw_pyYzU.js → box-CFDcDxHv.js} +1 -1
  38. package/dist/ui/assets/{brain-C6vlLcg3.js → brain-DWTpby6b.js} +1 -1
  39. package/dist/ui/assets/{briefcase-DtHlat45.js → briefcase-mFWNV6w9.js} +1 -1
  40. package/dist/ui/assets/check-BiORptL3.js +1 -0
  41. package/dist/ui/assets/chevron-down-BUOg3vvd.js +1 -0
  42. package/dist/ui/assets/chevron-left-B0NmQyxK.js +1 -0
  43. package/dist/ui/assets/chevron-right-Bm_d1KKk.js +1 -0
  44. package/dist/ui/assets/{circle-alert-pUx6yhcF.js → circle-alert-sCcf7ANF.js} +1 -1
  45. package/dist/ui/assets/circle-check-Jz8IOE4h.js +1 -0
  46. package/dist/ui/assets/client-B553SX3l.js +9 -0
  47. package/dist/ui/assets/clock-CSNFJjo4.js +1 -0
  48. package/dist/ui/assets/{cpu-8Sbc9Dk0.js → cpu-C2_GFeSH.js} +1 -1
  49. package/dist/ui/assets/{database-D6O4hvbm.js → database-zXW48-_N.js} +1 -1
  50. package/dist/ui/assets/{download-DHcd5oLD.js → download-DZdOdijy.js} +1 -1
  51. package/dist/ui/assets/{eye-Cc-vy4Zu.js → eye-CjrFwZG_.js} +1 -1
  52. package/dist/ui/assets/{file-text-DWBSXx5l.js → file-text-CX_Yqfjf.js} +1 -1
  53. package/dist/ui/assets/{index-BvUv9Lzq.js → index-IcLlFUZs.js} +3 -3
  54. package/dist/ui/assets/{info-1jA6X-_e.js → info-DGHChFI6.js} +1 -1
  55. package/dist/ui/assets/{layers-BcHXXg-4.js → layers-CVf8_Byb.js} +1 -1
  56. package/dist/ui/assets/loader-circle-CB6PQwdc.js +1 -0
  57. package/dist/ui/assets/{panels-top-left-Vk8FzAxf.js → panels-top-left-Cp169WzB.js} +1 -1
  58. package/dist/ui/assets/{paperclip-DWJjaP8B.js → paperclip-5Y367YTj.js} +1 -1
  59. package/dist/ui/assets/plus-CkteVqhM.js +1 -0
  60. package/dist/ui/assets/{projects-CUOthD8N.js → projects-DoalTI7c.js} +1 -1
  61. package/dist/ui/assets/{providers-CNrSTcdc.js → providers-AawjMy3x.js} +1 -1
  62. package/dist/ui/assets/{recurrent-tasks-CKT47N2l.js → recurrent-tasks-7thk45mk.js} +1 -1
  63. package/dist/ui/assets/{refresh-cw-DQW6jR7M.js → refresh-cw-BgDsJOC_.js} +1 -1
  64. package/dist/ui/assets/{rocket-CMSwBl_C.js → rocket-B9Iexkcw.js} +1 -1
  65. package/dist/ui/assets/{save-DycJmlX0.js → save-Cmqi1IkN.js} +1 -1
  66. package/dist/ui/assets/{send-BZjwSrJW.js → send-iK1X7WSe.js} +1 -1
  67. package/dist/ui/assets/{server-CJE2K1Vt.js → server-BBmM-vrg.js} +1 -1
  68. package/dist/ui/assets/{settings-at7JqFrc.js → settings-BpKLGxm8.js} +1 -1
  69. package/dist/ui/assets/{shield-alert-CP5GVXxa.js → shield-alert-Do_Sg9SX.js} +1 -1
  70. package/dist/ui/assets/{shield-check-Cr4SHjdR.js → shield-check-YKs9kUNq.js} +1 -1
  71. package/dist/ui/assets/{sparkles-v57Nc-z-.js → sparkles-krUpxie4.js} +1 -1
  72. package/dist/ui/assets/{taskFormSchemas-dl1t1OyX.js → taskFormSchemas-B6YlRxrx.js} +1 -1
  73. package/dist/ui/assets/{tasks-DEUQCNoH.js → tasks-DHsBE-ea.js} +1 -1
  74. package/dist/ui/assets/{terminal-a6AV2mPO.js → terminal-B6iyNuAm.js} +1 -1
  75. package/dist/ui/assets/{trash-2-DJpTXgEP.js → trash-2-CphQfk35.js} +1 -1
  76. package/dist/ui/assets/{trending-up-DlrXEXE6.js → trending-up-ClGbkbN9.js} +1 -1
  77. package/dist/ui/assets/user-Brcjlps5.js +1 -0
  78. package/dist/ui/assets/{user-plus-nDkwsBT1.js → user-plus-0Y0M1GKi.js} +1 -1
  79. package/dist/ui/assets/{users-C6WiDiuN.js → users-BcnlUG5N.js} +1 -1
  80. package/dist/ui/assets/{users-Ef6Sh2iR.js → users-tdyaGZbx.js} +1 -1
  81. package/dist/ui/assets/x-CCZGYGDC.js +1 -0
  82. package/dist/ui/assets/{zap-BmhZqgvm.js → zap-BDAytXMX.js} +1 -1
  83. package/dist/ui/index.html +17 -17
  84. package/package.json +9 -4
  85. package/dist/ui/assets/check-DTei8Qlg.js +0 -1
  86. package/dist/ui/assets/chevron-down-LTeQHW9O.js +0 -1
  87. package/dist/ui/assets/chevron-left-CJc_eQid.js +0 -1
  88. package/dist/ui/assets/chevron-right-G5-HRo_C.js +0 -1
  89. package/dist/ui/assets/circle-check-B9kyRki5.js +0 -1
  90. package/dist/ui/assets/client-BGOBXkO8.js +0 -9
  91. package/dist/ui/assets/clock-BY4rrypg.js +0 -1
  92. package/dist/ui/assets/loader-circle-Cl6yIkPK.js +0 -1
  93. package/dist/ui/assets/plus-Dx6G8SOZ.js +0 -1
  94. package/dist/ui/assets/user--KOoEB9m.js +0 -1
  95. package/dist/ui/assets/x-wDy15QMx.js +0 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ # [1.2.0](https://github.com/bpinhosilva/agent-orchestrator/compare/v1.1.3...v1.2.0) (2026-05-17)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **tests:** move startServer tests to isolated spec using jest.mock ([8ede64a](https://github.com/bpinhosilva/agent-orchestrator/commit/8ede64a8e68175bbbe6ed3c076b682cd8412c25e))
7
+ * **ui:** add missing test peers ([8c3d1a8](https://github.com/bpinhosilva/agent-orchestrator/commit/8c3d1a8b6941d5eb6e0ca38e7645c7b6b56b314a))
8
+
9
+
10
+ ### Features
11
+
12
+ * **cli:** add runtime log rotation ([7ccd9f2](https://github.com/bpinhosilva/agent-orchestrator/commit/7ccd9f23a19ee8de0d5c000b178f2b4d9e990f08))
13
+
14
+ ## [1.1.3](https://github.com/bpinhosilva/agent-orchestrator/compare/v1.1.2...v1.1.3) (2026-05-02)
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * add UUID support and Postman collection generation ([a4bfd77](https://github.com/bpinhosilva/agent-orchestrator/commit/a4bfd7733d7a28ba3bc06748a6dce1ca27c5c466))
20
+ * update cron job handling to replace inactive jobs for active tasks ([3d01155](https://github.com/bpinhosilva/agent-orchestrator/commit/3d01155f3f8f5be127e2bace79ede889502e4ec8))
21
+
1
22
  ## [1.1.2](https://github.com/bpinhosilva/agent-orchestrator/compare/v1.1.1...v1.1.2) (2026-04-21)
2
23
 
3
24
 
package/README.md CHANGED
@@ -132,6 +132,9 @@ DB_LOGGING=false
132
132
  SERVE_STATIC_UI=true
133
133
  CHECK_PENDING_MIGRATIONS_ON_STARTUP=false
134
134
  LOG_LEVEL=error
135
+ # Optional packaged-runtime / Docker file-rotation settings
136
+ # LOG_ROTATION_MAX_SIZE_MB=10
137
+ # LOG_ROTATION_MAX_FILES=4
135
138
  ```
136
139
 
137
140
  ## Database setup
@@ -215,6 +218,8 @@ agent-orchestrator rotate-secrets
215
218
 
216
219
  When running the packaged app or a production build with static UI enabled, the dashboard is served from `http://localhost:15789` by default.
217
220
 
221
+ By default, the packaged CLI/runtime keeps writing to `${AGENT_ORCHESTRATOR_HOME}/server.log`, rotating the active file at **10 MB** and retaining **4** timestamped archives. You can persist different defaults with `agent-orchestrator setup --log-max-size-mb <mb> --log-max-files <count>` and override them for a single launch with `agent-orchestrator run --log-max-size-mb <mb> --log-max-files <count>`.
222
+
218
223
  ## Docker
219
224
 
220
225
  > For the full Docker guide, see [docs/DOCKER.md](docs/DOCKER.md).
@@ -227,6 +232,8 @@ The repository ships three Compose entrypoints:
227
232
  | `docker-compose.dev.yml` | Development stack with API hot reload and Vite UI dev server |
228
233
  | `docker-compose.test.yml` | Integration stack for migration, CLI/runtime, API, and UI checks |
229
234
 
235
+ Docker keeps stdout/stderr as the primary log stream, so `docker compose logs` continues to work as before. If you also want rotated in-container log files, set `LOG_ROTATION_MAX_SIZE_MB` and `LOG_ROTATION_MAX_FILES`; the API container writes to the system temp directory by default (typically `/tmp/server.log`), or `${AGENT_ORCHESTRATOR_HOME}/server.log` if you set a writable runtime home.
236
+
230
237
  ### Production-style stack
231
238
 
232
239
  **Step 1: Create `.env`** — copy the example in the repo root and fill in your values:
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerRunCommand = registerRunCommand;
4
4
  const utils_1 = require("../utils");
5
+ const validators_1 = require("../setup/validators");
5
6
  const process_manager_1 = require("../process-manager");
6
7
  const migration_state_1 = require("../../database/migration-state");
7
8
  const constants_1 = require("../constants");
@@ -18,6 +19,8 @@ function registerRunCommand(program) {
18
19
  .command('run')
19
20
  .description('Start the orchestrator server in detached mode')
20
21
  .option('--log-level <level>', 'Set the log level (fatal, error, warn, log, debug, verbose)')
22
+ .option('--log-max-size-mb <mb>', 'One-off override: max log file size in MB before rotation', (v) => (0, validators_1.parsePositiveInt)(v) ?? Number.NaN)
23
+ .option('--log-max-files <count>', 'One-off override: max number of rotated log files to keep', (v) => (0, validators_1.parsePositiveInt)(v) ?? Number.NaN)
21
24
  .action(async (...args) => {
22
25
  try {
23
26
  const options = (0, utils_1.resolveActionOptions)(args);
@@ -26,6 +29,18 @@ function registerRunCommand(program) {
26
29
  throw new Error(`Invalid log level "${options.logLevel}". Valid values: ${VALID_LOG_LEVELS.join(', ')}`);
27
30
  }
28
31
  }
32
+ if (options.logMaxSizeMb !== undefined) {
33
+ if (!Number.isInteger(options.logMaxSizeMb) ||
34
+ options.logMaxSizeMb <= 0) {
35
+ throw new Error(`Invalid --log-max-size-mb "${options.logMaxSizeMb}". Must be a positive integer.`);
36
+ }
37
+ }
38
+ if (options.logMaxFiles !== undefined) {
39
+ if (!Number.isInteger(options.logMaxFiles) ||
40
+ options.logMaxFiles <= 0) {
41
+ throw new Error(`Invalid --log-max-files "${options.logMaxFiles}". Must be a positive integer.`);
42
+ }
43
+ }
29
44
  // Check for pending migrations before starting
30
45
  const { hasPending } = await (0, migration_state_1.checkPendingMigrations)({
31
46
  assumePendingOnError: true,
@@ -44,7 +59,11 @@ function registerRunCommand(program) {
44
59
  return;
45
60
  }
46
61
  console.log('Starting Agent Orchestrator in background...');
47
- const { pid, host, port } = (0, process_manager_1.startServer)({ logLevel: options.logLevel });
62
+ const { pid, host, port } = (0, process_manager_1.startServer)({
63
+ logLevel: options.logLevel,
64
+ logMaxSizeMb: options.logMaxSizeMb,
65
+ logMaxFiles: options.logMaxFiles,
66
+ });
48
67
  const survived = await (0, utils_1.verifyServerStartup)(pid);
49
68
  if (!survived) {
50
69
  process.exit(1);
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerSetupCommand = registerSetupCommand;
4
4
  const utils_1 = require("../utils");
5
5
  const index_1 = require("../setup/index");
6
+ const validators_1 = require("../setup/validators");
6
7
  const migration_state_1 = require("../../database/migration-state");
7
8
  function registerSetupCommand(program) {
8
9
  program
@@ -27,10 +28,22 @@ function registerSetupCommand(program) {
27
28
  .option('--admin-email <email>', 'Admin user email for non-interactive setup')
28
29
  .option('--admin-password <password>', 'Admin user password for non-interactive setup')
29
30
  .option('--regenerate-jwt-secret', 'Generate a new JWT secret instead of preserving the existing one')
31
+ .option('--log-max-size-mb <mb>', 'Max log file size in MB before rotation (default: 10)', (v) => (0, validators_1.parsePositiveInt)(v) ?? Number.NaN)
32
+ .option('--log-max-files <count>', 'Max number of rotated log files to keep (default: 4)', (v) => (0, validators_1.parsePositiveInt)(v) ?? Number.NaN)
30
33
  .action(async (...args) => {
31
34
  const opts = (0, utils_1.resolveActionOptions)(args);
32
35
  console.log('Starting setup...');
33
36
  try {
37
+ if (opts.logMaxSizeMb !== undefined) {
38
+ if (!Number.isInteger(opts.logMaxSizeMb) || opts.logMaxSizeMb <= 0) {
39
+ throw new Error(`Invalid --log-max-size-mb "${opts.logMaxSizeMb}". Must be a positive integer.`);
40
+ }
41
+ }
42
+ if (opts.logMaxFiles !== undefined) {
43
+ if (!Number.isInteger(opts.logMaxFiles) || opts.logMaxFiles <= 0) {
44
+ throw new Error(`Invalid --log-max-files "${opts.logMaxFiles}". Must be a positive integer.`);
45
+ }
46
+ }
34
47
  await (0, index_1.handleSetup)(opts);
35
48
  if (opts.yes) {
36
49
  const { hasPending, isEmpty } = await (0, migration_state_1.checkPendingMigrations)({
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.PROCESS_FILE = exports.ENV_PATH = exports.LOG_FILE = exports.PID_FILE = exports.PID_DIR = exports.UI_INDEX_FILE = exports.MAIN_FILE = exports.PACKAGE_JSON_PATH = exports.PACKAGE_ROOT = exports.SUPPORTED_PROVIDERS = void 0;
36
+ exports.PROCESS_FILE = exports.ENV_PATH = exports.LOG_FILE = exports.PID_FILE = exports.PID_DIR = exports.PRELOAD_FILE = exports.UI_INDEX_FILE = exports.MAIN_FILE = exports.PACKAGE_JSON_PATH = exports.PACKAGE_ROOT = exports.SUPPORTED_PROVIDERS = void 0;
37
37
  const os = __importStar(require("os"));
38
38
  const path = __importStar(require("path"));
39
39
  var types_1 = require("./types");
@@ -42,6 +42,7 @@ exports.PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
42
42
  exports.PACKAGE_JSON_PATH = path.join(exports.PACKAGE_ROOT, 'package.json');
43
43
  exports.MAIN_FILE = path.join(exports.PACKAGE_ROOT, 'dist/main.js');
44
44
  exports.UI_INDEX_FILE = path.join(exports.PACKAGE_ROOT, 'dist/ui/index.html');
45
+ exports.PRELOAD_FILE = path.join(exports.PACKAGE_ROOT, 'dist/cli/preload.js');
45
46
  // These are set at process start before any imports, so the env var is always present
46
47
  exports.PID_DIR = process.env.AGENT_ORCHESTRATOR_HOME ??
47
48
  path.join(os.homedir(), '.agent-orchestrator');
package/dist/cli/env.js CHANGED
@@ -139,6 +139,8 @@ function buildEnvContent(currentEnv, basicConfig, databaseUrl, geminiKey, anthro
139
139
  'ANTHROPIC_API_KEY',
140
140
  'OLLAMA_HOST',
141
141
  'OLLAMA_API_KEY',
142
+ 'LOG_ROTATION_MAX_SIZE_MB',
143
+ 'LOG_ROTATION_MAX_FILES',
142
144
  'JWT_SECRET',
143
145
  'JWT_REFRESH_SECRET',
144
146
  ];
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const runtime_paths_1 = require("../config/runtime-paths");
4
+ const runtime_logger_1 = require("../config/runtime-logger");
5
+ (0, runtime_paths_1.loadRuntimeEnv)();
6
+ try {
7
+ (0, runtime_logger_1.initRuntimeLogger)();
8
+ }
9
+ catch (err) {
10
+ (0, runtime_logger_1.persistRuntimeLoggerInitFailure)(String(err));
11
+ process.exit(1);
12
+ }
@@ -231,13 +231,16 @@ function getChildEnvironment(pidDir = constants_1.PID_DIR) {
231
231
  }
232
232
  return childEnv;
233
233
  }
234
- function assertBuildExists(mainFile = constants_1.MAIN_FILE, uiIndexFile = constants_1.UI_INDEX_FILE, fsDep = realFs) {
234
+ function assertBuildExists(mainFile = constants_1.MAIN_FILE, uiIndexFile = constants_1.UI_INDEX_FILE, preloadFile = constants_1.PRELOAD_FILE, fsDep = realFs) {
235
235
  if (!fsDep.existsSync(mainFile)) {
236
236
  throw new Error(`Missing backend build at ${mainFile}. Run "npm run build:all" before using the CLI runtime.`);
237
237
  }
238
238
  if (!fsDep.existsSync(uiIndexFile)) {
239
239
  throw new Error(`Missing UI build at ${uiIndexFile}. Run "npm run build:all" so the packaged CLI starts the full application.`);
240
240
  }
241
+ if (!fsDep.existsSync(preloadFile)) {
242
+ throw new Error(`Missing CLI preload build at ${preloadFile}. Run "npm run build:all" before using the CLI runtime.`);
243
+ }
241
244
  }
242
245
  function isManagedProcess(pid, expected, fsDep = realFs) {
243
246
  if (os.platform() === 'win32') {
@@ -359,7 +362,8 @@ async function stopManagedProcessById(pid, cwd, mainPath, fsDep = realFs) {
359
362
  * that no instance is already running before calling this.
360
363
  */
361
364
  function startServer(options = {}, mainFile = constants_1.MAIN_FILE, packageRoot = constants_1.PACKAGE_ROOT, logFile = constants_1.LOG_FILE, pidDir = constants_1.PID_DIR, envPath = constants_1.ENV_PATH) {
362
- assertBuildExists(mainFile, constants_1.UI_INDEX_FILE);
365
+ const preloadFile = path.join(packageRoot, 'dist/cli/preload.js');
366
+ assertBuildExists(mainFile, constants_1.UI_INDEX_FILE, preloadFile);
363
367
  if (!fs.existsSync(pidDir)) {
364
368
  fs.mkdirSync(pidDir, { recursive: true, mode: 0o700 });
365
369
  }
@@ -367,32 +371,32 @@ function startServer(options = {}, mainFile = constants_1.MAIN_FILE, packageRoot
367
371
  if (options.logLevel) {
368
372
  env.LOG_LEVEL = options.logLevel;
369
373
  }
370
- const logFd = fs.openSync(logFile, 'a');
371
- try {
372
- const child = (0, child_process_1.spawn)('node', [mainFile], {
373
- detached: true,
374
- stdio: ['ignore', logFd, logFd],
375
- cwd: packageRoot,
376
- env,
377
- });
378
- const pid = child.pid;
379
- if (!pid)
380
- throw new Error('Failed to determine spawned process PID.');
381
- const port = getConfiguredPort(envPath);
382
- const host = getConfiguredHost(envPath);
383
- persistProcessMetadata({
384
- pid,
385
- cwd: packageRoot,
386
- mainPath: mainFile,
387
- host,
388
- port,
389
- logFile,
390
- startedAt: new Date().toISOString(),
391
- });
392
- child.unref();
393
- return { pid, host, port };
394
- }
395
- finally {
396
- fs.closeSync(logFd);
374
+ if (options.logMaxSizeMb !== undefined) {
375
+ env.LOG_ROTATION_MAX_SIZE_MB = String(options.logMaxSizeMb);
376
+ }
377
+ if (options.logMaxFiles !== undefined) {
378
+ env.LOG_ROTATION_MAX_FILES = String(options.logMaxFiles);
397
379
  }
380
+ const child = (0, child_process_1.spawn)('node', ['--require', preloadFile, mainFile], {
381
+ detached: true,
382
+ stdio: ['ignore', 'ignore', 'ignore'],
383
+ cwd: packageRoot,
384
+ env,
385
+ });
386
+ const pid = child.pid;
387
+ if (!pid)
388
+ throw new Error('Failed to determine spawned process PID.');
389
+ const port = getConfiguredPort(envPath);
390
+ const host = getConfiguredHost(envPath);
391
+ persistProcessMetadata({
392
+ pid,
393
+ cwd: packageRoot,
394
+ mainPath: mainFile,
395
+ host,
396
+ port,
397
+ logFile,
398
+ startedAt: new Date().toISOString(),
399
+ });
400
+ child.unref();
401
+ return { pid, host, port };
398
402
  }
@@ -40,6 +40,8 @@ const env_1 = require("../env");
40
40
  const host_defaults_1 = require("../../config/host.defaults");
41
41
  const admin_1 = require("./admin");
42
42
  const prompts_1 = require("./prompts");
43
+ const validators_1 = require("./validators");
44
+ const LOG_ROTATION_DEFAULTS = { maxSizeMb: 10, maxFiles: 4 };
43
45
  const SENSITIVE_FLAG_ENV_VARS = [
44
46
  ['geminiKey', 'GEMINI_API_KEY', '--gemini-key'],
45
47
  ['anthropicKey', 'ANTHROPIC_API_KEY', '--anthropic-key'],
@@ -56,8 +58,28 @@ async function handleSetup(opts, fsDep, prompter, dsFactory) {
56
58
  }
57
59
  }
58
60
  const existingEnv = (0, env_1.readEnvFile)(constants_1.ENV_PATH, fsDep);
61
+ if (opts.logMaxSizeMb !== undefined &&
62
+ (!Number.isInteger(opts.logMaxSizeMb) || opts.logMaxSizeMb <= 0)) {
63
+ throw new Error('Invalid logMaxSizeMb. Expected a positive integer.');
64
+ }
65
+ if (opts.logMaxFiles !== undefined &&
66
+ (!Number.isInteger(opts.logMaxFiles) || opts.logMaxFiles <= 0)) {
67
+ throw new Error('Invalid logMaxFiles. Expected a positive integer.');
68
+ }
59
69
  // Resolve a sensitive value: CLI flag > process env var > existing .env value
60
70
  const resolveSecret = (flagValue, envVar, existingKey) => flagValue ?? process.env[envVar] ?? existingEnv[existingKey] ?? '';
71
+ // Resolve a positive-integer config value: flag > existing env > default
72
+ const resolvePositiveInt = (flagValue, existingKey, defaultValue) => {
73
+ if (flagValue !== undefined &&
74
+ Number.isInteger(flagValue) &&
75
+ flagValue > 0) {
76
+ return flagValue;
77
+ }
78
+ const fromEnv = (0, validators_1.parsePositiveInt)(existingEnv[existingKey]);
79
+ return fromEnv ?? defaultValue;
80
+ };
81
+ const logMaxSizeMb = resolvePositiveInt(opts.logMaxSizeMb, 'LOG_ROTATION_MAX_SIZE_MB', LOG_ROTATION_DEFAULTS.maxSizeMb);
82
+ const logMaxFiles = resolvePositiveInt(opts.logMaxFiles, 'LOG_ROTATION_MAX_FILES', LOG_ROTATION_DEFAULTS.maxFiles);
61
83
  let answers;
62
84
  if (opts.yes) {
63
85
  const jwtSecret = opts.regenerateJwtSecret || !existingEnv.JWT_SECRET
@@ -87,6 +109,8 @@ async function handleSetup(opts, fsDep, prompter, dsFactory) {
87
109
  const envContent = (0, env_1.buildEnvContent)({
88
110
  ...existingEnv,
89
111
  SCHEDULER_ENABLED: answers.schedulerEnabled ? 'true' : 'false',
112
+ LOG_ROTATION_MAX_SIZE_MB: String(logMaxSizeMb),
113
+ LOG_ROTATION_MAX_FILES: String(logMaxFiles),
90
114
  }, {
91
115
  host: answers.host,
92
116
  port: answers.port,
@@ -4,6 +4,7 @@ exports.validatePort = validatePort;
4
4
  exports.isValidPostgresConnectionString = isValidPostgresConnectionString;
5
5
  exports.collectProviders = collectProviders;
6
6
  exports.normalizeProviders = normalizeProviders;
7
+ exports.parsePositiveInt = parsePositiveInt;
7
8
  const types_1 = require("../types");
8
9
  function validatePort(port) {
9
10
  const parsedPort = Number(port);
@@ -48,3 +49,16 @@ function normalizeProviders(values = []) {
48
49
  throw new Error(errors.join('\n'));
49
50
  return result;
50
51
  }
52
+ /**
53
+ * Parses a string as a positive integer.
54
+ * Returns the integer when the value is a strict positive integer string,
55
+ * or `undefined` when the input is `undefined`, a decimal, or non-numeric.
56
+ */
57
+ function parsePositiveInt(value) {
58
+ if (value === undefined)
59
+ return undefined;
60
+ if (!/^[1-9]\d*$/.test(value)) {
61
+ return undefined;
62
+ }
63
+ return Number(value);
64
+ }
@@ -0,0 +1,57 @@
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.convertOpenApiToPostman = convertOpenApiToPostman;
37
+ exports.writePostmanCollection = writePostmanCollection;
38
+ const fs = __importStar(require("fs"));
39
+ function convertOpenApiToPostman(openApiJson, convert) {
40
+ return new Promise((resolve, reject) => {
41
+ convert({ type: 'string', data: openApiJson }, { schemaFaker: true }, (error, result) => {
42
+ if (error) {
43
+ reject(error instanceof Error ? error : new Error('Converter failed'));
44
+ return;
45
+ }
46
+ if (!result?.result) {
47
+ reject(new Error(result?.reason ??
48
+ 'Could not convert OpenAPI document to Postman collection'));
49
+ return;
50
+ }
51
+ resolve(result.output?.[0]?.data ?? {});
52
+ });
53
+ });
54
+ }
55
+ function writePostmanCollection(outputPath, collection) {
56
+ fs.writeFileSync(outputPath, JSON.stringify(collection, null, 2));
57
+ }
@@ -53,4 +53,10 @@ exports.envValidationSchema = Joi.object({
53
53
  .default('development'),
54
54
  ALLOWED_ORIGINS: Joi.string().optional(),
55
55
  SCHEDULER_ENABLED: Joi.boolean().default(true),
56
+ LOG_ROTATION_MAX_SIZE_MB: Joi.string()
57
+ .pattern(/^[1-9]\d*$/)
58
+ .optional(),
59
+ LOG_ROTATION_MAX_FILES: Joi.string()
60
+ .pattern(/^[1-9]\d*$/)
61
+ .optional(),
56
62
  });
@@ -0,0 +1,214 @@
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.RotatingFileLogger = exports.DEFAULT_MAX_FILES = exports.DEFAULT_MAX_SIZE_MB = exports.LOG_FILENAME = void 0;
37
+ exports.shouldEnableFileLogging = shouldEnableFileLogging;
38
+ exports.getLogRotationOptions = getLogRotationOptions;
39
+ exports.persistRuntimeLoggerInitFailure = persistRuntimeLoggerInitFailure;
40
+ exports.initRuntimeLogger = initRuntimeLogger;
41
+ exports.teardownRuntimeLogger = teardownRuntimeLogger;
42
+ const fs = __importStar(require("fs"));
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ exports.LOG_FILENAME = 'server.log';
46
+ exports.DEFAULT_MAX_SIZE_MB = 10;
47
+ exports.DEFAULT_MAX_FILES = 4;
48
+ const ARCHIVE_PREFIX = 'server-';
49
+ function parseStrictPositiveInt(value, envName) {
50
+ if (!/^[1-9]\d*$/.test(value)) {
51
+ throw new Error(`Invalid ${envName}: "${value}". Must be a plain positive integer (e.g. 10).`);
52
+ }
53
+ const n = Number(value);
54
+ if (!Number.isInteger(n) || n < 1) {
55
+ throw new Error(`Invalid ${envName}: "${value}". Must be a positive integer >= 1.`);
56
+ }
57
+ return n;
58
+ }
59
+ function shouldEnableFileLogging() {
60
+ if (process.env.AGENT_ORCHESTRATOR_HOME)
61
+ return true;
62
+ if (process.env.LOG_ROTATION_MAX_SIZE_MB !== undefined ||
63
+ process.env.LOG_ROTATION_MAX_FILES !== undefined)
64
+ return true;
65
+ return false;
66
+ }
67
+ function getLogRotationOptions() {
68
+ if (!shouldEnableFileLogging())
69
+ return null;
70
+ const logDir = process.env.AGENT_ORCHESTRATOR_HOME || os.tmpdir();
71
+ const maxSizeMb = process.env.LOG_ROTATION_MAX_SIZE_MB !== undefined
72
+ ? parseStrictPositiveInt(process.env.LOG_ROTATION_MAX_SIZE_MB, 'LOG_ROTATION_MAX_SIZE_MB')
73
+ : exports.DEFAULT_MAX_SIZE_MB;
74
+ const maxFiles = process.env.LOG_ROTATION_MAX_FILES !== undefined
75
+ ? parseStrictPositiveInt(process.env.LOG_ROTATION_MAX_FILES, 'LOG_ROTATION_MAX_FILES')
76
+ : exports.DEFAULT_MAX_FILES;
77
+ return { logDir, maxSizeMb, maxFiles };
78
+ }
79
+ function persistRuntimeLoggerInitFailure(message, fsDep = fs) {
80
+ if (!shouldEnableFileLogging()) {
81
+ return null;
82
+ }
83
+ const logDir = process.env.AGENT_ORCHESTRATOR_HOME || os.tmpdir();
84
+ fsDep.mkdirSync(logDir, { recursive: true, mode: 0o700 });
85
+ const logFilePath = path.join(logDir, exports.LOG_FILENAME);
86
+ fsDep.appendFileSync(logFilePath, `[preload] Runtime logger initialization failed: ${message}\n`);
87
+ return logFilePath;
88
+ }
89
+ class RotatingFileLogger {
90
+ logFile;
91
+ maxSizeBytes;
92
+ maxFiles;
93
+ fsDep;
94
+ getNow;
95
+ originalStdoutWrite = null;
96
+ originalStderrWrite = null;
97
+ active = false;
98
+ constructor(options) {
99
+ this.logFile = path.join(options.logDir, exports.LOG_FILENAME);
100
+ this.maxSizeBytes = options.maxSizeMb * 1024 * 1024;
101
+ this.maxFiles = options.maxFiles;
102
+ this.fsDep = options.fsDep ?? fs;
103
+ this.getNow = options.getNow ?? (() => new Date());
104
+ }
105
+ get logFilePath() {
106
+ return this.logFile;
107
+ }
108
+ init() {
109
+ if (this.active)
110
+ return;
111
+ const dir = path.dirname(this.logFile);
112
+ if (!this.fsDep.existsSync(dir)) {
113
+ this.fsDep.mkdirSync(dir, { recursive: true, mode: 0o700 });
114
+ }
115
+ this.originalStdoutWrite = process.stdout.write;
116
+ this.originalStderrWrite = process.stderr.write;
117
+ process.stdout.write = makeTeePatch(this.originalStdoutWrite, (chunk) => this.writeToFile(chunk));
118
+ process.stderr.write = makeTeePatch(this.originalStderrWrite, (chunk) => this.writeToFile(chunk));
119
+ this.active = true;
120
+ }
121
+ writeToFile(chunk) {
122
+ try {
123
+ const incomingBytes = typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.byteLength;
124
+ this.rotateIfNeeded(incomingBytes);
125
+ this.fsDep.appendFileSync(this.logFile, chunk);
126
+ }
127
+ catch {
128
+ }
129
+ }
130
+ rotateIfNeeded(incomingBytes = 0) {
131
+ try {
132
+ if (!this.fsDep.existsSync(this.logFile))
133
+ return;
134
+ const stat = this.fsDep.statSync(this.logFile);
135
+ if (stat.size + incomingBytes < this.maxSizeBytes)
136
+ return;
137
+ }
138
+ catch {
139
+ return;
140
+ }
141
+ const ts = this.getNow().toISOString().replace(/[:.]/g, '-');
142
+ const dir = path.dirname(this.logFile);
143
+ const rotated = path.join(dir, `${ARCHIVE_PREFIX}${ts}.log`);
144
+ try {
145
+ this.fsDep.renameSync(this.logFile, rotated);
146
+ }
147
+ catch {
148
+ return;
149
+ }
150
+ this.pruneOldArchives();
151
+ }
152
+ pruneOldArchives() {
153
+ const dir = path.dirname(this.logFile);
154
+ let archives;
155
+ try {
156
+ archives = this.fsDep
157
+ .readdirSync(dir)
158
+ .filter((f) => f.startsWith(ARCHIVE_PREFIX) &&
159
+ f.endsWith('.log') &&
160
+ f !== exports.LOG_FILENAME)
161
+ .sort();
162
+ }
163
+ catch {
164
+ return;
165
+ }
166
+ while (archives.length > this.maxFiles) {
167
+ const oldest = archives.shift();
168
+ try {
169
+ this.fsDep.unlinkSync(path.join(dir, oldest));
170
+ }
171
+ catch {
172
+ }
173
+ }
174
+ }
175
+ teardown() {
176
+ if (!this.active)
177
+ return;
178
+ if (this.originalStdoutWrite) {
179
+ process.stdout.write = this.originalStdoutWrite;
180
+ }
181
+ if (this.originalStderrWrite) {
182
+ process.stderr.write = this.originalStderrWrite;
183
+ }
184
+ this.active = false;
185
+ }
186
+ }
187
+ exports.RotatingFileLogger = RotatingFileLogger;
188
+ function makeTeePatch(original, writeToFile) {
189
+ const orig = original;
190
+ return function (chunk, encodingOrCb, cb) {
191
+ writeToFile(chunk);
192
+ if (typeof encodingOrCb === 'function') {
193
+ return orig.call(this, chunk, encodingOrCb);
194
+ }
195
+ return orig.call(this, chunk, encodingOrCb, cb);
196
+ };
197
+ }
198
+ let activeLogger = null;
199
+ function initRuntimeLogger() {
200
+ if (activeLogger)
201
+ return activeLogger;
202
+ const opts = getLogRotationOptions();
203
+ if (!opts)
204
+ return null;
205
+ activeLogger = new RotatingFileLogger(opts);
206
+ activeLogger.init();
207
+ return activeLogger;
208
+ }
209
+ function teardownRuntimeLogger() {
210
+ if (activeLogger) {
211
+ activeLogger.teardown();
212
+ activeLogger = null;
213
+ }
214
+ }
package/dist/main.js CHANGED
@@ -15,6 +15,7 @@ const migration_state_1 = require("./database/migration-state");
15
15
  const port_defaults_1 = require("./config/port.defaults");
16
16
  const host_defaults_1 = require("./config/host.defaults");
17
17
  const runtime_paths_1 = require("./config/runtime-paths");
18
+ const runtime_logger_1 = require("./config/runtime-logger");
18
19
  const logger = new common_1.Logger('Bootstrap');
19
20
  (0, runtime_paths_1.loadRuntimeEnv)();
20
21
  async function ensureDatabaseIsReadyForStartup() {
@@ -38,6 +39,13 @@ async function ensureDatabaseIsReadyForStartup() {
38
39
  ].join(' '));
39
40
  }
40
41
  async function bootstrap() {
42
+ try {
43
+ (0, runtime_logger_1.initRuntimeLogger)();
44
+ }
45
+ catch (err) {
46
+ (0, runtime_logger_1.persistRuntimeLoggerInitFailure)(String(err));
47
+ throw err;
48
+ }
41
49
  await ensureDatabaseIsReadyForStartup();
42
50
  const nodeEnv = process.env.NODE_ENV || 'development';
43
51
  const defaultLevels = nodeEnv === 'production'
@@ -103,11 +103,10 @@ let RecurrentTaskSchedulerService = RecurrentTaskSchedulerService_1 = class Recu
103
103
  if (name.startsWith('recurrent-task-')) {
104
104
  const taskId = name.replace('recurrent-task-', '');
105
105
  const task = activeTasks.find((t) => t.id === taskId);
106
- const jobWithCron = job;
106
+ const jobIsInactive = !job.isActive;
107
107
  if (!activeTaskIds.has(taskId) ||
108
- (task &&
109
- jobWithCron.cronTime &&
110
- jobWithCron.cronTime.source !== task.cronExpression)) {
108
+ jobIsInactive ||
109
+ (task && job.cronTime && job.cronTime.source !== task.cronExpression)) {
111
110
  void job.stop();
112
111
  this.schedulerRegistry.deleteCronJob(name);
113
112
  }