@jungjaehoon/mama-os 0.18.2 → 0.19.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 (171) hide show
  1. package/dist/agent/agent-loop.d.ts +25 -0
  2. package/dist/agent/agent-loop.d.ts.map +1 -1
  3. package/dist/agent/agent-loop.js +67 -14
  4. package/dist/agent/agent-loop.js.map +1 -1
  5. package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
  6. package/dist/agent/code-act/host-bridge.js +98 -0
  7. package/dist/agent/code-act/host-bridge.js.map +1 -1
  8. package/dist/agent/code-act/type-definition-generator.d.ts.map +1 -1
  9. package/dist/agent/code-act/type-definition-generator.js +0 -1
  10. package/dist/agent/code-act/type-definition-generator.js.map +1 -1
  11. package/dist/agent/gateway-tool-executor.d.ts +36 -1
  12. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  13. package/dist/agent/gateway-tool-executor.js +938 -54
  14. package/dist/agent/gateway-tool-executor.js.map +1 -1
  15. package/dist/agent/gateway-tools.md +9 -0
  16. package/dist/agent/managed-agent-runtime-sync.d.ts +36 -0
  17. package/dist/agent/managed-agent-runtime-sync.d.ts.map +1 -0
  18. package/dist/agent/managed-agent-runtime-sync.js +207 -0
  19. package/dist/agent/managed-agent-runtime-sync.js.map +1 -0
  20. package/dist/agent/managed-agent-validation.d.ts +4 -0
  21. package/dist/agent/managed-agent-validation.d.ts.map +1 -0
  22. package/dist/agent/managed-agent-validation.js +84 -0
  23. package/dist/agent/managed-agent-validation.js.map +1 -0
  24. package/dist/agent/os-agent-capabilities.md +400 -0
  25. package/dist/agent/skill-loader.d.ts +2 -0
  26. package/dist/agent/skill-loader.d.ts.map +1 -1
  27. package/dist/agent/skill-loader.js +28 -0
  28. package/dist/agent/skill-loader.js.map +1 -1
  29. package/dist/agent/tool-registry.d.ts.map +1 -1
  30. package/dist/agent/tool-registry.js +66 -0
  31. package/dist/agent/tool-registry.js.map +1 -1
  32. package/dist/agent/types.d.ts +2 -1
  33. package/dist/agent/types.d.ts.map +1 -1
  34. package/dist/agent/types.js.map +1 -1
  35. package/dist/api/agent-handler.d.ts +34 -0
  36. package/dist/api/agent-handler.d.ts.map +1 -0
  37. package/dist/api/agent-handler.js +216 -0
  38. package/dist/api/agent-handler.js.map +1 -0
  39. package/dist/api/graph-api-types.d.ts +4 -0
  40. package/dist/api/graph-api-types.d.ts.map +1 -1
  41. package/dist/api/graph-api.d.ts +2 -2
  42. package/dist/api/graph-api.d.ts.map +1 -1
  43. package/dist/api/graph-api.js +480 -51
  44. package/dist/api/graph-api.js.map +1 -1
  45. package/dist/api/index.d.ts.map +1 -1
  46. package/dist/api/index.js +4 -0
  47. package/dist/api/index.js.map +1 -1
  48. package/dist/api/token-handler.d.ts +1 -0
  49. package/dist/api/token-handler.d.ts.map +1 -1
  50. package/dist/api/token-handler.js +4 -3
  51. package/dist/api/token-handler.js.map +1 -1
  52. package/dist/api/ui-command-handler.d.ts +48 -0
  53. package/dist/api/ui-command-handler.d.ts.map +1 -0
  54. package/dist/api/ui-command-handler.js +160 -0
  55. package/dist/api/ui-command-handler.js.map +1 -0
  56. package/dist/cli/commands/start.d.ts.map +1 -1
  57. package/dist/cli/commands/start.js +127 -1
  58. package/dist/cli/commands/start.js.map +1 -1
  59. package/dist/cli/config/config-manager.d.ts.map +1 -1
  60. package/dist/cli/config/config-manager.js +16 -31
  61. package/dist/cli/config/config-manager.js.map +1 -1
  62. package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
  63. package/dist/cli/runtime/agent-loop-init.js +31 -7
  64. package/dist/cli/runtime/agent-loop-init.js.map +1 -1
  65. package/dist/cli/runtime/api-routes-init.d.ts +3 -0
  66. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  67. package/dist/cli/runtime/api-routes-init.js +283 -34
  68. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  69. package/dist/cli/runtime/gateway-init.d.ts +2 -1
  70. package/dist/cli/runtime/gateway-init.d.ts.map +1 -1
  71. package/dist/cli/runtime/gateway-init.js +5 -1
  72. package/dist/cli/runtime/gateway-init.js.map +1 -1
  73. package/dist/connectors/framework/raw-store.d.ts +4 -0
  74. package/dist/connectors/framework/raw-store.d.ts.map +1 -1
  75. package/dist/connectors/framework/raw-store.js +33 -10
  76. package/dist/connectors/framework/raw-store.js.map +1 -1
  77. package/dist/db/agent-store.d.ts +115 -0
  78. package/dist/db/agent-store.d.ts.map +1 -0
  79. package/dist/db/agent-store.js +248 -0
  80. package/dist/db/agent-store.js.map +1 -0
  81. package/dist/db/migrations/agent-activity-validation-columns.d.ts +3 -0
  82. package/dist/db/migrations/agent-activity-validation-columns.d.ts.map +1 -0
  83. package/dist/db/migrations/agent-activity-validation-columns.js +22 -0
  84. package/dist/db/migrations/agent-activity-validation-columns.js.map +1 -0
  85. package/dist/db/migrations/agent-metrics-response-avg.d.ts +3 -0
  86. package/dist/db/migrations/agent-metrics-response-avg.d.ts.map +1 -0
  87. package/dist/db/migrations/agent-metrics-response-avg.js +19 -0
  88. package/dist/db/migrations/agent-metrics-response-avg.js.map +1 -0
  89. package/dist/db/migrations/agent-store-tables.d.ts +3 -0
  90. package/dist/db/migrations/agent-store-tables.d.ts.map +1 -0
  91. package/dist/db/migrations/agent-store-tables.js +59 -0
  92. package/dist/db/migrations/agent-store-tables.js.map +1 -0
  93. package/dist/db/migrations/token-usage-agent-version.d.ts +3 -0
  94. package/dist/db/migrations/token-usage-agent-version.d.ts.map +1 -0
  95. package/dist/db/migrations/token-usage-agent-version.js +16 -0
  96. package/dist/db/migrations/token-usage-agent-version.js.map +1 -0
  97. package/dist/db/migrations/validation-session-tables.d.ts +3 -0
  98. package/dist/db/migrations/validation-session-tables.d.ts.map +1 -0
  99. package/dist/db/migrations/validation-session-tables.js +59 -0
  100. package/dist/db/migrations/validation-session-tables.js.map +1 -0
  101. package/dist/gateways/message-router.d.ts +10 -0
  102. package/dist/gateways/message-router.d.ts.map +1 -1
  103. package/dist/gateways/message-router.js +188 -14
  104. package/dist/gateways/message-router.js.map +1 -1
  105. package/dist/gateways/types.d.ts +1 -1
  106. package/dist/gateways/types.d.ts.map +1 -1
  107. package/dist/multi-agent/agent-process-manager.js +1 -1
  108. package/dist/multi-agent/agent-process-manager.js.map +1 -1
  109. package/dist/multi-agent/conductor-persona.d.ts +13 -0
  110. package/dist/multi-agent/conductor-persona.d.ts.map +1 -0
  111. package/dist/multi-agent/conductor-persona.js +157 -0
  112. package/dist/multi-agent/conductor-persona.js.map +1 -0
  113. package/dist/multi-agent/dashboard-agent-persona.d.ts +1 -1
  114. package/dist/multi-agent/dashboard-agent-persona.d.ts.map +1 -1
  115. package/dist/multi-agent/dashboard-agent-persona.js +7 -3
  116. package/dist/multi-agent/dashboard-agent-persona.js.map +1 -1
  117. package/dist/multi-agent/delegation-manager.d.ts +5 -0
  118. package/dist/multi-agent/delegation-manager.d.ts.map +1 -1
  119. package/dist/multi-agent/delegation-manager.js +37 -0
  120. package/dist/multi-agent/delegation-manager.js.map +1 -1
  121. package/dist/multi-agent/ultrawork.d.ts +3 -0
  122. package/dist/multi-agent/ultrawork.d.ts.map +1 -1
  123. package/dist/multi-agent/ultrawork.js +9 -0
  124. package/dist/multi-agent/ultrawork.js.map +1 -1
  125. package/dist/validation/session-service.d.ts +72 -0
  126. package/dist/validation/session-service.d.ts.map +1 -0
  127. package/dist/validation/session-service.js +298 -0
  128. package/dist/validation/session-service.js.map +1 -0
  129. package/dist/validation/store.d.ts +25 -0
  130. package/dist/validation/store.d.ts.map +1 -0
  131. package/dist/validation/store.js +200 -0
  132. package/dist/validation/store.js.map +1 -0
  133. package/dist/validation/types.d.ts +119 -0
  134. package/dist/validation/types.d.ts.map +1 -0
  135. package/dist/validation/types.js +57 -0
  136. package/dist/validation/types.js.map +1 -0
  137. package/package.json +3 -3
  138. package/public/viewer/js/modules/agents.js +1148 -0
  139. package/public/viewer/js/modules/chat.js +20 -11
  140. package/public/viewer/js/modules/connector-feed.js +35 -0
  141. package/public/viewer/js/modules/dashboard.js +49 -0
  142. package/public/viewer/js/modules/memory.js +32 -0
  143. package/public/viewer/js/modules/settings.js +34 -79
  144. package/public/viewer/js/modules/wiki.js +59 -4
  145. package/public/viewer/js/utils/api.js +70 -0
  146. package/public/viewer/js/utils/dom.js +3 -0
  147. package/public/viewer/js/utils/ui-commands.js +93 -0
  148. package/public/viewer/log-viewer.html +2 -2
  149. package/public/viewer/src/modules/agents.ts +1299 -0
  150. package/public/viewer/src/modules/chat.ts +23 -14
  151. package/public/viewer/src/modules/connector-feed.ts +35 -0
  152. package/public/viewer/src/modules/dashboard.ts +50 -0
  153. package/public/viewer/src/modules/memory.ts +31 -0
  154. package/public/viewer/src/modules/settings.ts +36 -96
  155. package/public/viewer/src/modules/wiki.ts +73 -6
  156. package/public/viewer/src/types/global.d.ts +0 -9
  157. package/public/viewer/src/utils/api.ts +156 -2
  158. package/public/viewer/src/utils/dom.ts +6 -1
  159. package/public/viewer/src/utils/ui-commands.ts +118 -0
  160. package/public/viewer/viewer.css +105 -10
  161. package/public/viewer/viewer.html +1868 -777
  162. package/scripts/generate-gateway-tools.ts +5 -1
  163. package/public/viewer/js/modules/playground.js +0 -148
  164. package/public/viewer/js/modules/skills.js +0 -451
  165. package/public/viewer/src/modules/playground.ts +0 -173
  166. package/public/viewer/src/modules/skills.ts +0 -491
  167. package/templates/playgrounds/cron-workflow-lab.html +0 -1601
  168. package/templates/playgrounds/mama-log-viewer.html +0 -1341
  169. package/templates/playgrounds/skill-lab-playground.html +0 -1625
  170. package/templates/playgrounds/wave-visualizer.html +0 -694
  171. package/templates/skills/playground.md +0 -197
@@ -10,11 +10,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  return (mod && mod.__esModule) ? mod : { "default": mod };
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.VIEWER_JS_PATH = exports.VIEWER_CSS_PATH = exports.VIEWER_HTML_PATH = exports.DEFAULT_GRAPH_LIMIT = void 0;
13
+ exports.VIEWER_CSS_PATH = exports.VIEWER_HTML_PATH = exports.DEFAULT_GRAPH_LIMIT = void 0;
14
14
  exports.createGraphHandler = createGraphHandler;
15
15
  exports.mapDecisionRowToGraphNode = mapDecisionRowToGraphNode;
16
16
  exports.parseGraphLimit = parseGraphLimit;
17
17
  exports.buildGraphMeta = buildGraphMeta;
18
+ exports.validateConfigUpdate = validateConfigUpdate;
18
19
  exports.getAllNodes = getAllNodes;
19
20
  exports.getAllEdges = getAllEdges;
20
21
  exports.getAllCheckpoints = getAllCheckpoints;
@@ -26,6 +27,9 @@ const path_1 = __importDefault(require("path"));
26
27
  const os_1 = __importDefault(require("os"));
27
28
  const js_yaml_1 = __importDefault(require("js-yaml"));
28
29
  const auth_middleware_js_1 = require("./auth-middleware.js");
30
+ const agent_handler_js_1 = require("./agent-handler.js");
31
+ const ui_command_handler_js_1 = require("./ui-command-handler.js");
32
+ const store_js_1 = require("../validation/store.js");
29
33
  // mama-core is pure JS with no .d.ts — require + any is intentional
30
34
  // eslint-disable-next-line @typescript-eslint/no-require-imports
31
35
  const { getAdapter, initDB, vectorSearch } = require('@jungjaehoon/mama-core/memory-store');
@@ -62,8 +66,6 @@ const VIEWER_HTML_PATH = path_1.default.join(VIEWER_DIR, 'viewer.html');
62
66
  exports.VIEWER_HTML_PATH = VIEWER_HTML_PATH;
63
67
  const VIEWER_CSS_PATH = path_1.default.join(VIEWER_DIR, 'viewer.css');
64
68
  exports.VIEWER_CSS_PATH = VIEWER_CSS_PATH;
65
- const VIEWER_JS_PATH = path_1.default.join(VIEWER_DIR, 'viewer.js');
66
- exports.VIEWER_JS_PATH = VIEWER_JS_PATH;
67
69
  const SW_JS_PATH = path_1.default.join(VIEWER_DIR, 'sw.js');
68
70
  const MANIFEST_JSON_PATH = path_1.default.join(VIEWER_DIR, 'manifest.json');
69
71
  const VIEWER_ICON_DIR = path_1.default.join(VIEWER_DIR, 'icons');
@@ -76,6 +78,14 @@ const SIMILAR_QUERY_DECISION_CHARS = 400;
76
78
  // Model pattern helpers (used in multiple validation functions)
77
79
  const isClaudeModel = (model) => /^claude-/i.test(model);
78
80
  const isCodexModel = (model) => /^(gpt-|o\d|codex)/i.test(model);
81
+ const supportedManagedBackends = ['claude', 'codex', 'codex-mcp', 'gemini'];
82
+ const isCodexFamilyBackend = (backend) => backend === 'codex' || backend === 'codex-mcp';
83
+ const VALIDATION_TRIGGER_TYPES = new Set([
84
+ 'agent_test',
85
+ 'delegate_run',
86
+ 'system_run',
87
+ 'audit',
88
+ ]);
79
89
  const isOpus46Model = (model) => /^claude-opus-4-6(?:$|-)/i.test(model) || model.toLowerCase() === 'claude-opus-4-latest';
80
90
  const VALID_EFFORT_LEVELS = new Set(['low', 'medium', 'high', 'max']);
81
91
  // Allowed directories for persona files (security: prevent arbitrary file read)
@@ -313,9 +323,6 @@ function handleViewerRequest(_req, res) {
313
323
  function handleCssRequest(_req, res) {
314
324
  serveStaticFile(res, VIEWER_CSS_PATH, 'text/css');
315
325
  }
316
- function handleJsRequest(_req, res) {
317
- serveStaticFile(res, VIEWER_JS_PATH, 'application/javascript');
318
- }
319
326
  async function handleGraphRequest(_req, res, params) {
320
327
  const startTime = Date.now();
321
328
  try {
@@ -450,6 +457,28 @@ function readBody(req) {
450
457
  req.on('error', reject);
451
458
  });
452
459
  }
460
+ async function readBodyOrRespond(req, res) {
461
+ try {
462
+ return await readBody(req);
463
+ }
464
+ catch (error) {
465
+ const message = error instanceof Error ? error.message : String(error);
466
+ const statusCode = message === 'Invalid JSON'
467
+ ? 400
468
+ : message.includes('Request body too large (max 1MB)')
469
+ ? 413
470
+ : 500;
471
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
472
+ res.end(JSON.stringify({
473
+ error: message === 'Invalid JSON'
474
+ ? 'Invalid JSON'
475
+ : message.includes('Request body too large (max 1MB)')
476
+ ? 'Request body too large (max 1MB)'
477
+ : message,
478
+ }));
479
+ return null;
480
+ }
481
+ }
453
482
  async function handleUpdateRequest(req, res) {
454
483
  try {
455
484
  const body = await readBody(req);
@@ -783,6 +812,20 @@ async function handleCheckpointsRequest(_req, res) {
783
812
  }
784
813
  }
785
814
  function createGraphHandler(options = {}) {
815
+ const parsePositiveInt = (value, fallback, max) => {
816
+ const parsed = Number.parseInt(value ?? '', 10);
817
+ if (!Number.isFinite(parsed) || parsed < 1) {
818
+ return fallback;
819
+ }
820
+ return Math.min(parsed, max);
821
+ };
822
+ const parseValidationTriggerType = (value) => {
823
+ if (!value) {
824
+ return null;
825
+ }
826
+ const normalized = value.trim().toLowerCase();
827
+ return VALIDATION_TRIGGER_TYPES.has(normalized) ? normalized : null;
828
+ };
786
829
  return async function graphHandler(req, res) {
787
830
  if (!req.url) {
788
831
  res.writeHead(400, { 'Content-Type': 'text/plain' });
@@ -826,16 +869,6 @@ function createGraphHandler(options = {}) {
826
869
  handleCssRequest(req, res);
827
870
  return true;
828
871
  }
829
- // Route: GET/HEAD /viewer.css - serve stylesheet (legacy path)
830
- if (pathname === '/viewer.css' && (req.method === 'GET' || req.method === 'HEAD')) {
831
- handleCssRequest(req, res);
832
- return true;
833
- }
834
- // Route: GET/HEAD /viewer.js - serve JavaScript
835
- if (pathname === '/viewer.js' && (req.method === 'GET' || req.method === 'HEAD')) {
836
- handleJsRequest(req, res);
837
- return true;
838
- }
839
872
  // Route: GET/HEAD /sw.js - serve Service Worker
840
873
  if (pathname === '/sw.js' && (req.method === 'GET' || req.method === 'HEAD')) {
841
874
  serveStaticFile(res, SW_JS_PATH, 'application/javascript');
@@ -851,11 +884,6 @@ function createGraphHandler(options = {}) {
851
884
  serveStaticFile(res, MANIFEST_JSON_PATH, 'application/json');
852
885
  return true;
853
886
  }
854
- // Route: GET/HEAD /manifest.json - legacy or custom host compatibility
855
- if (pathname === '/manifest.json' && (req.method === 'GET' || req.method === 'HEAD')) {
856
- serveStaticFile(res, MANIFEST_JSON_PATH, 'application/json');
857
- return true;
858
- }
859
887
  // Route: GET/HEAD /favicon.ico - serve favicon
860
888
  if (pathname === '/favicon.ico' && (req.method === 'GET' || req.method === 'HEAD')) {
861
889
  serveStaticFile(res, VIEWER_FAVICON_PATH, 'image/x-icon');
@@ -897,24 +925,6 @@ function createGraphHandler(options = {}) {
897
925
  serveStaticFile(res, filePath, 'application/javascript');
898
926
  return true;
899
927
  }
900
- // Route: GET/HEAD /js/utils/*.js - serve utility modules (legacy path)
901
- if (pathname.startsWith('/js/utils/') &&
902
- pathname.endsWith('.js') &&
903
- (req.method === 'GET' || req.method === 'HEAD')) {
904
- const fileName = pathname.split('/').pop();
905
- const filePath = path_1.default.join(VIEWER_DIR, 'js', 'utils', fileName);
906
- serveStaticFile(res, filePath, 'application/javascript');
907
- return true;
908
- }
909
- // Route: GET/HEAD /js/modules/*.js - serve feature modules (legacy path)
910
- if (pathname.startsWith('/js/modules/') &&
911
- pathname.endsWith('.js') &&
912
- (req.method === 'GET' || req.method === 'HEAD')) {
913
- const fileName = pathname.split('/').pop();
914
- const filePath = path_1.default.join(VIEWER_DIR, 'js', 'modules', fileName);
915
- serveStaticFile(res, filePath, 'application/javascript');
916
- return true;
917
- }
918
928
  // ── Auth gate: all routes below require authentication ──
919
929
  // Static assets (viewer, css, js, icons) are served above without auth.
920
930
  // All data API routes below must pass isAuthenticated().
@@ -1269,6 +1279,425 @@ function createGraphHandler(options = {}) {
1269
1279
  await handleExportRequest(req, res, params);
1270
1280
  return true;
1271
1281
  }
1282
+ // ── UI Command API (SmartStore bidirectional communication) ──
1283
+ // Route: GET /api/ui/commands — viewer polls for pending commands
1284
+ if (pathname === '/api/ui/commands' && req.method === 'GET') {
1285
+ if (options.uiCommandQueue) {
1286
+ (0, ui_command_handler_js_1.handleGetUICommands)(res, options.uiCommandQueue);
1287
+ }
1288
+ else {
1289
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1290
+ res.end(JSON.stringify({ commands: [] }));
1291
+ }
1292
+ return true;
1293
+ }
1294
+ // Route: GET /api/ui/page-context — agent reads current viewer state
1295
+ if (pathname === '/api/ui/page-context' && req.method === 'GET') {
1296
+ if (options.uiCommandQueue) {
1297
+ const { handleGetPageContext } = await import('./ui-command-handler.js');
1298
+ handleGetPageContext(res, options.uiCommandQueue);
1299
+ }
1300
+ else {
1301
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1302
+ res.end(JSON.stringify({ success: true, context: null }));
1303
+ }
1304
+ return true;
1305
+ }
1306
+ // Route: POST /api/ui/page-context — viewer reports current page state
1307
+ if (pathname === '/api/ui/page-context' && req.method === 'POST') {
1308
+ if (options.uiCommandQueue) {
1309
+ const body = await readBodyOrRespond(req, res);
1310
+ if (!body) {
1311
+ return true;
1312
+ }
1313
+ (0, ui_command_handler_js_1.handlePostPageContext)(res, body, options.uiCommandQueue);
1314
+ }
1315
+ else {
1316
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1317
+ res.end(JSON.stringify({ success: true }));
1318
+ }
1319
+ return true;
1320
+ }
1321
+ // Route: POST /api/ui/commands — agent pushes UI commands
1322
+ if (pathname === '/api/ui/commands' && req.method === 'POST') {
1323
+ if (options.uiCommandQueue) {
1324
+ const body = await readBodyOrRespond(req, res);
1325
+ if (!body) {
1326
+ return true;
1327
+ }
1328
+ (0, ui_command_handler_js_1.handlePostUICommand)(res, body, options.uiCommandQueue);
1329
+ }
1330
+ else {
1331
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1332
+ res.end(JSON.stringify({ success: true }));
1333
+ }
1334
+ return true;
1335
+ }
1336
+ // Route: POST /api/ui/commands/ack — viewer acknowledges applied commands
1337
+ if (pathname === '/api/ui/commands/ack' && req.method === 'POST') {
1338
+ if (options.uiCommandQueue) {
1339
+ const body = await readBodyOrRespond(req, res);
1340
+ if (!body) {
1341
+ return true;
1342
+ }
1343
+ const { handlePostUICommandAck } = await import('./ui-command-handler.js');
1344
+ handlePostUICommandAck(res, body, options.uiCommandQueue);
1345
+ }
1346
+ else {
1347
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1348
+ res.end(JSON.stringify({ success: true, acknowledged: 0 }));
1349
+ }
1350
+ return true;
1351
+ }
1352
+ // ── Agent Management API (Managed Agents pattern) ──
1353
+ // Route: GET /api/agents/:id/versions/:v1/compare/:v2 — before/after (must be before /api/agents/:id)
1354
+ if (pathname.match(/^\/api\/agents\/[^/]+\/versions\/\d+\/compare\/\d+$/) &&
1355
+ req.method === 'GET') {
1356
+ const parts = pathname.split('/');
1357
+ const agentId = decodeURIComponent(parts[3]);
1358
+ const v1 = parseInt(parts[5], 10);
1359
+ const v2 = parseInt(parts[7], 10);
1360
+ if (options.sessionsDb) {
1361
+ (0, agent_handler_js_1.handleCompareVersions)(res, agentId, v1, v2, options.sessionsDb);
1362
+ }
1363
+ else {
1364
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1365
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1366
+ }
1367
+ return true;
1368
+ }
1369
+ // Route: GET /api/agents/:id/versions — version history
1370
+ if (pathname.match(/^\/api\/agents\/[^/]+\/versions$/) && req.method === 'GET') {
1371
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1372
+ if (options.sessionsDb) {
1373
+ (0, agent_handler_js_1.handleListVersions)(res, agentId, options.sessionsDb);
1374
+ }
1375
+ else {
1376
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1377
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1378
+ }
1379
+ return true;
1380
+ }
1381
+ // Route: GET /api/agents/:id/metrics?from=&to= — metrics for period
1382
+ if (pathname.match(/^\/api\/agents\/[^/]+\/metrics$/) && req.method === 'GET') {
1383
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1384
+ // Parse query params from raw URL
1385
+ const rawUrl = req.url || pathname;
1386
+ const qIdx = rawUrl.indexOf('?');
1387
+ const params = qIdx >= 0 ? new URLSearchParams(rawUrl.slice(qIdx)) : new URLSearchParams();
1388
+ const from = params.get('from') || '2020-01-01';
1389
+ const to = params.get('to') || '2099-12-31';
1390
+ if (options.sessionsDb) {
1391
+ (0, agent_handler_js_1.handleGetAgentMetrics)(res, agentId, from, to, options.sessionsDb);
1392
+ }
1393
+ else {
1394
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1395
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1396
+ }
1397
+ return true;
1398
+ }
1399
+ // Route: GET /api/agents/:id/activity?limit= — activity log
1400
+ if (pathname.match(/^\/api\/agents\/[^/]+\/activity$/) && req.method === 'GET') {
1401
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1402
+ const rawUrl = req.url || pathname;
1403
+ const qIdx = rawUrl.indexOf('?');
1404
+ const params = qIdx >= 0 ? new URLSearchParams(rawUrl.slice(qIdx)) : new URLSearchParams();
1405
+ const limit = parsePositiveInt(params.get('limit'), 20, 100);
1406
+ if (options.sessionsDb) {
1407
+ (0, agent_handler_js_1.handleGetAgentActivity)(res, agentId, options.sessionsDb, limit);
1408
+ }
1409
+ else {
1410
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1411
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1412
+ }
1413
+ return true;
1414
+ }
1415
+ // Route: POST /api/agents/:id/archive — archive agent
1416
+ if (pathname.match(/^\/api\/agents\/[^/]+\/archive$/) && req.method === 'POST') {
1417
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1418
+ if (options.sessionsDb) {
1419
+ (0, agent_handler_js_1.handleArchiveAgent)(res, agentId, options.sessionsDb);
1420
+ }
1421
+ else {
1422
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1423
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1424
+ }
1425
+ return true;
1426
+ }
1427
+ // ── Validation API ──────────────────────────────────────────────────
1428
+ // Route: GET /api/agents/:id/validation/summary?trigger_type=
1429
+ if (pathname.match(/^\/api\/agents\/[^/]+\/validation\/summary$/) && req.method === 'GET') {
1430
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1431
+ if (options.sessionsDb) {
1432
+ const triggerType = parseValidationTriggerType(params.get('trigger_type'));
1433
+ if (!triggerType) {
1434
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1435
+ res.end(JSON.stringify({ error: 'trigger_type required' }));
1436
+ return true;
1437
+ }
1438
+ const summary = (0, store_js_1.getValidationSummary)(options.sessionsDb, agentId, triggerType);
1439
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1440
+ res.end(JSON.stringify({ summary }));
1441
+ }
1442
+ else {
1443
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1444
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1445
+ }
1446
+ return true;
1447
+ }
1448
+ // Route: GET /api/agents/:id/validation/history?trigger_type=
1449
+ if (pathname.match(/^\/api\/agents\/[^/]+\/validation\/history$/) && req.method === 'GET') {
1450
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1451
+ if (options.sessionsDb) {
1452
+ const rawUrl = req.url || pathname;
1453
+ const qIdx = rawUrl.indexOf('?');
1454
+ const params = qIdx >= 0 ? new URLSearchParams(rawUrl.slice(qIdx)) : new URLSearchParams();
1455
+ const limit = parsePositiveInt(params.get('limit'), 50, 200);
1456
+ const triggerType = parseValidationTriggerType(params.get('trigger_type'));
1457
+ if (!triggerType) {
1458
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1459
+ res.end(JSON.stringify({ error: 'trigger_type required' }));
1460
+ return true;
1461
+ }
1462
+ const history = (0, store_js_1.listValidationHistory)(options.sessionsDb, agentId, limit, triggerType);
1463
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1464
+ res.end(JSON.stringify({ history }));
1465
+ }
1466
+ else {
1467
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1468
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1469
+ }
1470
+ return true;
1471
+ }
1472
+ // Route: GET /api/validation-sessions/:id
1473
+ if (pathname.match(/^\/api\/validation-sessions\/[^/]+$/) && req.method === 'GET') {
1474
+ const sessionId = decodeURIComponent(pathname.split('/')[3]);
1475
+ if (options.sessionsDb) {
1476
+ const detail = (0, store_js_1.getValidationSessionDetail)(options.sessionsDb, sessionId);
1477
+ if (!detail) {
1478
+ res.writeHead(404, { 'Content-Type': 'application/json' });
1479
+ res.end(JSON.stringify({ error: 'Session not found' }));
1480
+ }
1481
+ else {
1482
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1483
+ res.end(JSON.stringify(detail));
1484
+ }
1485
+ }
1486
+ else {
1487
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1488
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1489
+ }
1490
+ return true;
1491
+ }
1492
+ // Route: POST /api/agents/:id/validation/approve
1493
+ if (pathname.match(/^\/api\/agents\/[^/]+\/validation\/approve$/) && req.method === 'POST') {
1494
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1495
+ if (options.sessionsDb) {
1496
+ const rawUrl = req.url || pathname;
1497
+ const qIdx = rawUrl.indexOf('?');
1498
+ const params = qIdx >= 0 ? new URLSearchParams(rawUrl.slice(qIdx)) : new URLSearchParams();
1499
+ let sessionId = params.get('session_id');
1500
+ if (!sessionId) {
1501
+ const body = await readBodyOrRespond(req, res);
1502
+ if (!body) {
1503
+ return true;
1504
+ }
1505
+ sessionId = body?.session_id;
1506
+ }
1507
+ if (!sessionId) {
1508
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1509
+ res.end(JSON.stringify({ error: 'session_id required' }));
1510
+ }
1511
+ else {
1512
+ const detail = (0, store_js_1.getValidationSessionDetail)(options.sessionsDb, sessionId);
1513
+ if (!detail) {
1514
+ res.writeHead(404, { 'Content-Type': 'application/json' });
1515
+ res.end(JSON.stringify({ error: 'Session not found' }));
1516
+ return true;
1517
+ }
1518
+ if (detail.session.agent_id !== agentId) {
1519
+ res.writeHead(403, { 'Content-Type': 'application/json' });
1520
+ res.end(JSON.stringify({ error: 'session does not belong to requested agent' }));
1521
+ return true;
1522
+ }
1523
+ try {
1524
+ (0, store_js_1.approveValidationSession)(options.sessionsDb, sessionId);
1525
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1526
+ res.end(JSON.stringify({ success: true, approved_session: sessionId }));
1527
+ }
1528
+ catch (error) {
1529
+ const message = error instanceof Error ? error.message : String(error);
1530
+ const statusCode = /not found|required|invalid/i.test(message) ? 400 : 500;
1531
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
1532
+ res.end(JSON.stringify({ error: message }));
1533
+ }
1534
+ }
1535
+ }
1536
+ else {
1537
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1538
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1539
+ }
1540
+ return true;
1541
+ }
1542
+ // Route: GET /api/agents/:id/validation/compare?session=X&baseline=approved
1543
+ if (pathname.match(/^\/api\/agents\/[^/]+\/validation\/compare$/) && req.method === 'GET') {
1544
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1545
+ if (options.sessionsDb) {
1546
+ const rawUrl = req.url || pathname;
1547
+ const qIdx = rawUrl.indexOf('?');
1548
+ const params = qIdx >= 0 ? new URLSearchParams(rawUrl.slice(qIdx)) : new URLSearchParams();
1549
+ const sessionId = params.get('session');
1550
+ const baselineMode = params.get('baseline') || 'approved';
1551
+ if (!sessionId) {
1552
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1553
+ res.end(JSON.stringify({ error: 'session query parameter required' }));
1554
+ }
1555
+ else {
1556
+ const current = (0, store_js_1.getValidationSessionDetail)(options.sessionsDb, sessionId);
1557
+ if (!current) {
1558
+ res.writeHead(404, { 'Content-Type': 'application/json' });
1559
+ res.end(JSON.stringify({ error: 'Session not found' }));
1560
+ }
1561
+ else if (current.session.agent_id !== agentId) {
1562
+ res.writeHead(403, { 'Content-Type': 'application/json' });
1563
+ res.end(JSON.stringify({ error: 'session does not belong to requested agent' }));
1564
+ }
1565
+ else {
1566
+ let baselineSessionId = null;
1567
+ if (baselineMode === 'approved') {
1568
+ const state = (0, store_js_1.getAgentValidationState)(options.sessionsDb, agentId, current.session.trigger_type);
1569
+ baselineSessionId = state?.approved_session_id ?? null;
1570
+ }
1571
+ else {
1572
+ baselineSessionId = baselineMode;
1573
+ }
1574
+ const baseline = baselineSessionId
1575
+ ? (0, store_js_1.getValidationSessionDetail)(options.sessionsDb, baselineSessionId)
1576
+ : null;
1577
+ if (baselineMode !== 'approved' && baselineSessionId && !baseline) {
1578
+ res.writeHead(404, { 'Content-Type': 'application/json' });
1579
+ res.end(JSON.stringify({ error: 'baseline session not found' }));
1580
+ return true;
1581
+ }
1582
+ if (baseline && baseline.session.agent_id !== agentId) {
1583
+ res.writeHead(403, { 'Content-Type': 'application/json' });
1584
+ res.end(JSON.stringify({ error: 'baseline session does not belong to requested agent' }));
1585
+ return true;
1586
+ }
1587
+ if (baseline && baseline.session.trigger_type !== current.session.trigger_type) {
1588
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1589
+ res.end(JSON.stringify({
1590
+ error: 'baseline session trigger_type does not match current session',
1591
+ }));
1592
+ return true;
1593
+ }
1594
+ const baselineMetrics = new Map((baseline?.metrics ?? []).map((m) => [
1595
+ m.name,
1596
+ m.value,
1597
+ ]));
1598
+ const deltas = current.metrics.map((m) => {
1599
+ const bVal = baselineMetrics.get(m.name);
1600
+ return {
1601
+ name: m.name,
1602
+ current: m.value,
1603
+ baseline: bVal ?? null,
1604
+ delta: bVal !== undefined ? m.value - bVal : null,
1605
+ direction: m.direction,
1606
+ };
1607
+ });
1608
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1609
+ res.end(JSON.stringify({ current, baseline, deltas }));
1610
+ }
1611
+ }
1612
+ }
1613
+ else {
1614
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1615
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1616
+ }
1617
+ return true;
1618
+ }
1619
+ // Route: GET /api/agents/activity-summary — aggregated activity with alerts
1620
+ if (pathname === '/api/agents/activity-summary' && req.method === 'GET') {
1621
+ const rawUrl = req.url || pathname;
1622
+ const qIdx = rawUrl.indexOf('?');
1623
+ const params = qIdx >= 0 ? new URLSearchParams(rawUrl.slice(qIdx)) : new URLSearchParams();
1624
+ const since = params.get('since') || new Date(Date.now() - 86400000).toISOString().slice(0, 10);
1625
+ if (options.sessionsDb) {
1626
+ (0, agent_handler_js_1.handleGetActivitySummary)(res, options.sessionsDb, since);
1627
+ }
1628
+ else {
1629
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1630
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1631
+ }
1632
+ return true;
1633
+ }
1634
+ // Route: GET /api/agents — list all agents
1635
+ if (pathname === '/api/agents' && req.method === 'GET') {
1636
+ const config = loadMAMAConfig();
1637
+ if (options.sessionsDb) {
1638
+ (0, agent_handler_js_1.handleGetAgents)(res, config, options.sessionsDb);
1639
+ }
1640
+ else {
1641
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1642
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1643
+ }
1644
+ return true;
1645
+ }
1646
+ // Route: POST /api/agents — create new agent
1647
+ if (pathname === '/api/agents' && req.method === 'POST') {
1648
+ const body = await readBodyOrRespond(req, res);
1649
+ if (!body) {
1650
+ return true;
1651
+ }
1652
+ if (options.sessionsDb) {
1653
+ await (0, agent_handler_js_1.handleCreateAgent)(res, body, options.sessionsDb, {
1654
+ loadConfig: async () => loadMAMAConfig(),
1655
+ saveConfig: async (config) => saveMAMAConfig(config),
1656
+ applyMultiAgentConfig: options.applyMultiAgentConfig ?? null,
1657
+ restartMultiAgentAgent: options.restartMultiAgentAgent ?? null,
1658
+ });
1659
+ }
1660
+ else {
1661
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1662
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1663
+ }
1664
+ return true;
1665
+ }
1666
+ // Route: GET /api/agents/:id — single agent detail (must be after /api/agents/:id/*)
1667
+ if (pathname.match(/^\/api\/agents\/[^/]+$/) && req.method === 'GET') {
1668
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1669
+ const config = loadMAMAConfig();
1670
+ if (options.sessionsDb) {
1671
+ (0, agent_handler_js_1.handleGetAgent)(res, agentId, config, options.sessionsDb);
1672
+ }
1673
+ else {
1674
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1675
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1676
+ }
1677
+ return true;
1678
+ }
1679
+ // Route: POST /api/agents/:id — update agent (Managed Agents: version required)
1680
+ if (pathname.match(/^\/api\/agents\/[^/]+$/) && req.method === 'POST') {
1681
+ const agentId = decodeURIComponent(pathname.split('/')[3]);
1682
+ const body = await readBodyOrRespond(req, res);
1683
+ if (!body) {
1684
+ return true;
1685
+ }
1686
+ if (options.sessionsDb) {
1687
+ await (0, agent_handler_js_1.handleUpdateAgent)(res, agentId, body, options.sessionsDb, {
1688
+ loadConfig: async () => loadMAMAConfig(),
1689
+ saveConfig: async (config) => saveMAMAConfig(config),
1690
+ applyMultiAgentConfig: options.applyMultiAgentConfig ?? null,
1691
+ restartMultiAgentAgent: options.restartMultiAgentAgent ?? null,
1692
+ });
1693
+ }
1694
+ else {
1695
+ res.writeHead(503, { 'Content-Type': 'application/json' });
1696
+ res.end(JSON.stringify({ error: 'Sessions DB not available' }));
1697
+ }
1698
+ return true;
1699
+ }
1700
+ // ── Legacy Multi-Agent API (redirect-compatible) ──
1272
1701
  // Route: GET /api/multi-agent/status - get multi-agent system status
1273
1702
  if (pathname === '/api/multi-agent/status' && req.method === 'GET') {
1274
1703
  await handleMultiAgentStatusRequest(req, res, options);
@@ -1968,8 +2397,8 @@ function validateConfigUpdate(config) {
1968
2397
  errors.push('timeout must be at least 1000ms');
1969
2398
  }
1970
2399
  if (config.agent.backend &&
1971
- !['claude', 'codex-mcp'].includes(String(config.agent.backend).toLowerCase())) {
1972
- errors.push('agent.backend must be "claude" or "codex-mcp"');
2400
+ !supportedManagedBackends.includes(String(config.agent.backend).toLowerCase())) {
2401
+ errors.push('agent.backend must be "claude", "codex", "codex-mcp", or "gemini"');
1973
2402
  }
1974
2403
  if (config.agent.backend && config.agent.model && typeof config.agent.model === 'string') {
1975
2404
  const backend = String(config.agent.backend).toLowerCase();
@@ -1977,8 +2406,8 @@ function validateConfigUpdate(config) {
1977
2406
  if (backend === 'claude' && !isClaudeModel(model)) {
1978
2407
  errors.push('agent.model must be a Claude model when agent.backend is "claude"');
1979
2408
  }
1980
- if (backend === 'codex-mcp' && !isCodexModel(model)) {
1981
- errors.push('agent.model must be a Codex/OpenAI model when agent.backend is "codex-mcp"');
2409
+ if (isCodexFamilyBackend(backend) && !isCodexModel(model)) {
2410
+ errors.push(`agent.model must be a Codex/OpenAI model when agent.backend is "${backend}"`);
1982
2411
  }
1983
2412
  }
1984
2413
  }
@@ -2002,16 +2431,16 @@ function validateConfigUpdate(config) {
2002
2431
  const modelRaw = cfg.model;
2003
2432
  if (backendRaw !== undefined) {
2004
2433
  const backend = String(backendRaw).toLowerCase();
2005
- if (!['claude', 'codex-mcp'].includes(backend)) {
2006
- errors.push(`multi_agent.agents.${agentId}.backend must be "claude" or "codex-mcp"`);
2434
+ if (!supportedManagedBackends.includes(backend)) {
2435
+ errors.push(`multi_agent.agents.${agentId}.backend must be "claude", "codex", "codex-mcp", or "gemini"`);
2007
2436
  continue;
2008
2437
  }
2009
2438
  if (typeof modelRaw === 'string' && modelRaw.trim()) {
2010
2439
  if (backend === 'claude' && !isClaudeModel(modelRaw)) {
2011
2440
  errors.push(`multi_agent.agents.${agentId}.model must be a Claude model when backend is "claude"`);
2012
2441
  }
2013
- if (backend === 'codex-mcp' && !isCodexModel(modelRaw)) {
2014
- errors.push(`multi_agent.agents.${agentId}.model must be a Codex/OpenAI model when backend is "codex-mcp"`);
2442
+ if (isCodexFamilyBackend(backend) && !isCodexModel(modelRaw)) {
2443
+ errors.push(`multi_agent.agents.${agentId}.model must be a Codex/OpenAI model when backend is "${backend}"`);
2015
2444
  }
2016
2445
  }
2017
2446
  }
@@ -2318,8 +2747,8 @@ async function handleMultiAgentUpdateAgentRequest(req, res, pathname, options =
2318
2747
  }
2319
2748
  if (body.backend !== undefined &&
2320
2749
  (typeof body.backend !== 'string' ||
2321
- !['claude', 'codex-mcp'].includes(String(body.backend).toLowerCase()))) {
2322
- validationErrors.push('backend must be "claude" or "codex-mcp"');
2750
+ !supportedManagedBackends.includes(String(body.backend).toLowerCase()))) {
2751
+ validationErrors.push('backend must be "claude", "codex", "codex-mcp", or "gemini"');
2323
2752
  }
2324
2753
  const nextBackend = (body.backend !== undefined ? String(body.backend).toLowerCase() : currentAgent.backend);
2325
2754
  const nextModel = (body.model !== undefined ? body.model : currentAgent.model);
@@ -2327,8 +2756,8 @@ async function handleMultiAgentUpdateAgentRequest(req, res, pathname, options =
2327
2756
  if (nextBackend === 'claude' && !isClaudeModel(nextModel)) {
2328
2757
  validationErrors.push('model must be a Claude model when backend is "claude"');
2329
2758
  }
2330
- if (nextBackend === 'codex-mcp' && !isCodexModel(nextModel)) {
2331
- validationErrors.push('model must be a Codex/OpenAI model when backend is "codex-mcp"');
2759
+ if (isCodexFamilyBackend(nextBackend) && !isCodexModel(nextModel)) {
2760
+ validationErrors.push(`model must be a Codex/OpenAI model when backend is "${nextBackend}"`);
2332
2761
  }
2333
2762
  }
2334
2763
  if (body.effort !== undefined) {