@probelabs/visor 0.1.180-ee → 0.1.181-ee

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 (109) hide show
  1. package/dist/agent-protocol/track-execution.d.ts +1 -1
  2. package/dist/agent-protocol/track-execution.d.ts.map +1 -1
  3. package/dist/ai-review-service.d.ts +17 -1
  4. package/dist/ai-review-service.d.ts.map +1 -1
  5. package/dist/cli-main.d.ts.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/docs/commands.md +34 -1
  9. package/dist/email/polling-runner.d.ts +3 -1
  10. package/dist/email/polling-runner.d.ts.map +1 -1
  11. package/dist/frontends/github-frontend.d.ts.map +1 -1
  12. package/dist/index.js +1555 -752
  13. package/dist/mcp-server.d.ts +50 -6
  14. package/dist/mcp-server.d.ts.map +1 -1
  15. package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
  16. package/dist/providers/workflow-check-provider.d.ts.map +1 -1
  17. package/dist/runners/mcp-server-runner.d.ts +66 -0
  18. package/dist/runners/mcp-server-runner.d.ts.map +1 -0
  19. package/dist/runners/runner-factory.d.ts +12 -0
  20. package/dist/runners/runner-factory.d.ts.map +1 -0
  21. package/dist/runners/runner-host.d.ts +30 -0
  22. package/dist/runners/runner-host.d.ts.map +1 -0
  23. package/dist/runners/runner.d.ts +21 -0
  24. package/dist/runners/runner.d.ts.map +1 -0
  25. package/dist/runners/telemetry-init.d.ts +10 -0
  26. package/dist/runners/telemetry-init.d.ts.map +1 -0
  27. package/dist/sdk/{a2a-frontend-6FNPD53G.mjs → a2a-frontend-BDACLGMA.mjs} +2 -2
  28. package/dist/sdk/{check-provider-registry-Z4MVXFLJ.mjs → check-provider-registry-4YKTEDKF.mjs} +8 -8
  29. package/dist/sdk/{check-provider-registry-T4HIUBMT.mjs → check-provider-registry-Z7EHECLC.mjs} +7 -7
  30. package/dist/sdk/{chunk-NBUN22ZG.mjs → chunk-3JFK6KCD.mjs} +4 -10
  31. package/dist/sdk/chunk-3JFK6KCD.mjs.map +1 -0
  32. package/dist/sdk/{chunk-STAAKOPU.mjs → chunk-6HLDM4OB.mjs} +218 -48
  33. package/dist/sdk/chunk-6HLDM4OB.mjs.map +1 -0
  34. package/dist/sdk/{chunk-F25U4YWJ.mjs → chunk-7XRSCOKE.mjs} +3 -3
  35. package/dist/sdk/{chunk-ANCIFGQH.mjs.map → chunk-7XRSCOKE.mjs.map} +1 -1
  36. package/dist/sdk/{chunk-MWUQFSEK.mjs → chunk-7YZSSO4X.mjs} +213 -43
  37. package/dist/sdk/chunk-7YZSSO4X.mjs.map +1 -0
  38. package/dist/sdk/{chunk-3ZKBUWDB.mjs → chunk-KBGQJKIZ.mjs} +3 -3
  39. package/dist/sdk/{chunk-5DQY4LTK.mjs → chunk-U6K5SK7X.mjs} +2 -2
  40. package/dist/sdk/{chunk-KAVOGMLR.mjs → chunk-VMVIM4JB.mjs} +2 -1
  41. package/dist/sdk/{chunk-KAVOGMLR.mjs.map → chunk-VMVIM4JB.mjs.map} +1 -1
  42. package/dist/sdk/{config-ZZKC47SV.mjs → config-TSA5FUOM.mjs} +2 -2
  43. package/dist/sdk/dist-3UGGEZB3.mjs +8008 -0
  44. package/dist/sdk/dist-3UGGEZB3.mjs.map +1 -0
  45. package/dist/sdk/email-frontend-WSNADJPI.mjs +701 -0
  46. package/dist/sdk/email-frontend-WSNADJPI.mjs.map +1 -0
  47. package/dist/sdk/{failure-condition-evaluator-WNCCIP6N.mjs → failure-condition-evaluator-FFWJRAEQ.mjs} +3 -3
  48. package/dist/sdk/{github-frontend-QVMVUL3Y.mjs → github-frontend-KGV2R5Z6.mjs} +4 -4
  49. package/dist/sdk/github-frontend-KGV2R5Z6.mjs.map +1 -0
  50. package/dist/sdk/{host-5TJBWGGH.mjs → host-6QBBL3QW.mjs} +3 -3
  51. package/dist/sdk/{host-RVLIZMTE.mjs → host-CVH2CSHM.mjs} +4 -4
  52. package/dist/sdk/{routing-5DHNS7IW.mjs → routing-XALEDC2G.mjs} +4 -4
  53. package/dist/sdk/{schedule-tool-25CFKVOI.mjs → schedule-tool-5XBASQVY.mjs} +7 -7
  54. package/dist/sdk/{schedule-tool-UGWYLGJK.mjs → schedule-tool-ADUXTCY7.mjs} +8 -8
  55. package/dist/sdk/{schedule-tool-handler-VWBX4JEF.mjs → schedule-tool-handler-GBIJS376.mjs} +7 -7
  56. package/dist/sdk/{schedule-tool-handler-WK22RO3M.mjs → schedule-tool-handler-N7UNABOA.mjs} +8 -8
  57. package/dist/sdk/sdk.js +792 -6339
  58. package/dist/sdk/sdk.js.map +1 -1
  59. package/dist/sdk/sdk.mjs +7 -7
  60. package/dist/sdk/{trace-helpers-GCLQ3YKO.mjs → trace-helpers-3FFAI7X3.mjs} +2 -2
  61. package/dist/sdk/{track-execution-GCGJKMAO.mjs → track-execution-AMQQNXKE.mjs} +1 -1
  62. package/dist/sdk/track-execution-AMQQNXKE.mjs.map +1 -0
  63. package/dist/sdk/{utcp-check-provider-HT2MA5SS.mjs → utcp-check-provider-JLIYF5HH.mjs} +2 -2
  64. package/dist/sdk/{workflow-check-provider-I732XE7D.mjs → workflow-check-provider-3IXIADSV.mjs} +7 -7
  65. package/dist/sdk/{workflow-check-provider-T4PFK2EG.mjs → workflow-check-provider-SRIMWKLQ.mjs} +8 -8
  66. package/dist/slack/socket-runner.d.ts +3 -1
  67. package/dist/slack/socket-runner.d.ts.map +1 -1
  68. package/dist/state-machine/dispatch/template-renderer.d.ts.map +1 -1
  69. package/dist/state-machine/runner.d.ts.map +1 -1
  70. package/dist/teams/webhook-runner.d.ts +3 -1
  71. package/dist/teams/webhook-runner.d.ts.map +1 -1
  72. package/dist/telegram/polling-runner.d.ts +3 -1
  73. package/dist/telegram/polling-runner.d.ts.map +1 -1
  74. package/dist/types/cli.d.ts +6 -0
  75. package/dist/types/cli.d.ts.map +1 -1
  76. package/dist/whatsapp/webhook-runner.d.ts +3 -1
  77. package/dist/whatsapp/webhook-runner.d.ts.map +1 -1
  78. package/package.json +4 -10
  79. package/dist/sdk/chunk-ANCIFGQH.mjs +0 -825
  80. package/dist/sdk/chunk-F25U4YWJ.mjs.map +0 -1
  81. package/dist/sdk/chunk-MWUQFSEK.mjs.map +0 -1
  82. package/dist/sdk/chunk-NBUN22ZG.mjs.map +0 -1
  83. package/dist/sdk/chunk-STAAKOPU.mjs.map +0 -1
  84. package/dist/sdk/dist-X4HSBKSA.mjs +0 -5716
  85. package/dist/sdk/dist-X4HSBKSA.mjs.map +0 -1
  86. package/dist/sdk/dist-XF2FTM6E.mjs +0 -5716
  87. package/dist/sdk/dist-XF2FTM6E.mjs.map +0 -1
  88. package/dist/sdk/github-frontend-QVMVUL3Y.mjs.map +0 -1
  89. package/dist/sdk/track-execution-GCGJKMAO.mjs.map +0 -1
  90. package/dist/sdk/utcp-check-provider-6YTBN5WQ.mjs +0 -16
  91. package/dist/sdk/workflow-check-provider-T4PFK2EG.mjs.map +0 -1
  92. /package/dist/sdk/{a2a-frontend-6FNPD53G.mjs.map → a2a-frontend-BDACLGMA.mjs.map} +0 -0
  93. /package/dist/sdk/{check-provider-registry-T4HIUBMT.mjs.map → check-provider-registry-4YKTEDKF.mjs.map} +0 -0
  94. /package/dist/sdk/{check-provider-registry-Z4MVXFLJ.mjs.map → check-provider-registry-Z7EHECLC.mjs.map} +0 -0
  95. /package/dist/sdk/{chunk-3ZKBUWDB.mjs.map → chunk-KBGQJKIZ.mjs.map} +0 -0
  96. /package/dist/sdk/{chunk-5DQY4LTK.mjs.map → chunk-U6K5SK7X.mjs.map} +0 -0
  97. /package/dist/sdk/{config-ZZKC47SV.mjs.map → config-TSA5FUOM.mjs.map} +0 -0
  98. /package/dist/sdk/{failure-condition-evaluator-WNCCIP6N.mjs.map → failure-condition-evaluator-FFWJRAEQ.mjs.map} +0 -0
  99. /package/dist/sdk/{host-5TJBWGGH.mjs.map → host-6QBBL3QW.mjs.map} +0 -0
  100. /package/dist/sdk/{host-RVLIZMTE.mjs.map → host-CVH2CSHM.mjs.map} +0 -0
  101. /package/dist/sdk/{routing-5DHNS7IW.mjs.map → routing-XALEDC2G.mjs.map} +0 -0
  102. /package/dist/sdk/{schedule-tool-25CFKVOI.mjs.map → schedule-tool-5XBASQVY.mjs.map} +0 -0
  103. /package/dist/sdk/{schedule-tool-UGWYLGJK.mjs.map → schedule-tool-ADUXTCY7.mjs.map} +0 -0
  104. /package/dist/sdk/{schedule-tool-handler-VWBX4JEF.mjs.map → schedule-tool-handler-GBIJS376.mjs.map} +0 -0
  105. /package/dist/sdk/{schedule-tool-handler-WK22RO3M.mjs.map → schedule-tool-handler-N7UNABOA.mjs.map} +0 -0
  106. /package/dist/sdk/{trace-helpers-GCLQ3YKO.mjs.map → trace-helpers-3FFAI7X3.mjs.map} +0 -0
  107. /package/dist/sdk/{utcp-check-provider-6YTBN5WQ.mjs.map → utcp-check-provider-JLIYF5HH.mjs.map} +0 -0
  108. /package/dist/sdk/{utcp-check-provider-HT2MA5SS.mjs.map → workflow-check-provider-3IXIADSV.mjs.map} +0 -0
  109. /package/dist/sdk/{workflow-check-provider-I732XE7D.mjs.map → workflow-check-provider-SRIMWKLQ.mjs.map} +0 -0
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- process.env.VISOR_VERSION = '0.1.180';
3
- process.env.PROBE_VERSION = '0.6.0-rc296';
4
- process.env.VISOR_COMMIT_SHA = '0e962afd62b2d4691e358227ba6b4b585884e526';
5
- process.env.VISOR_COMMIT_SHORT = '0e962af';
2
+ process.env.VISOR_VERSION = '0.1.181';
3
+ process.env.PROBE_VERSION = '0.6.0-rc297';
4
+ process.env.VISOR_COMMIT_SHA = 'c204877cf5806cf0582397a486599ba83a8e45a0';
5
+ process.env.VISOR_COMMIT_SHORT = 'c204877';
6
6
  /******/ (() => { // webpackBootstrap
7
7
  /******/ var __webpack_modules__ = ({
8
8
 
@@ -294716,6 +294716,17 @@ const PROBE_GRACEFUL_MARGIN_MS = 90_000;
294716
294716
  * as-is because there isn't enough room for a meaningful margin.
294717
294717
  */
294718
294718
  const MIN_TIMEOUT_FOR_MARGIN_MS = PROBE_GRACEFUL_MARGIN_MS + 30_000; // 120 000
294719
+ /**
294720
+ * Lightweight callback bridge for dynamically extending a withTimeout deadline.
294721
+ * Created per-call, wired to ProbeAgent's `timeout.extended` event so the
294722
+ * parent's Promise.race deadline tracks the child's negotiated extensions.
294723
+ */
294724
+ class TimeoutExtender {
294725
+ _listener;
294726
+ extend(extraMs) {
294727
+ this._listener?.(extraMs);
294728
+ }
294729
+ }
294719
294730
  /**
294720
294731
  * Helper function to log debug messages using the centralized logger
294721
294732
  */
@@ -295118,9 +295129,14 @@ class AIReviewService {
295118
295129
  }
295119
295130
  }
295120
295131
  try {
295121
- const call = this.callProbeAgent(prompt, schema, debugInfo, checkName, sessionId);
295132
+ // Create an extender so withTimeout can be dynamically extended when the
295133
+ // agent's negotiated timeout observer grants more time (timeout.extended event).
295134
+ const extender = new TimeoutExtender();
295135
+ const call = this.callProbeAgent(prompt, schema, debugInfo, checkName, sessionId, extender);
295122
295136
  const timeoutMs = Math.max(0, this.config.timeout || 0);
295123
- const { response, effectiveSchema, sessionId: usedSessionId, } = timeoutMs > 0 ? await this.withTimeout(call, timeoutMs, 'AI review') : await call;
295137
+ const { response, effectiveSchema, sessionId: usedSessionId, } = timeoutMs > 0
295138
+ ? await this.withTimeout(call, timeoutMs, 'AI review', sessionId, extender)
295139
+ : await call;
295124
295140
  const processingTime = Date.now() - startTime;
295125
295141
  if (debugInfo) {
295126
295142
  debugInfo.rawResponse = response;
@@ -295250,10 +295266,14 @@ class AIReviewService {
295250
295266
  };
295251
295267
  }
295252
295268
  try {
295269
+ // Create an extender so withTimeout tracks agent's negotiated extensions
295270
+ const extender = new TimeoutExtender();
295253
295271
  // Use the determined agent (cloned or original)
295254
- const call = this.callProbeAgentWithExistingSession(agentToUse, prompt, schema, debugInfo, checkName);
295272
+ const call = this.callProbeAgentWithExistingSession(agentToUse, prompt, schema, debugInfo, checkName, extender);
295255
295273
  const timeoutMs = Math.max(0, this.config.timeout || 0);
295256
- const { response, effectiveSchema } = timeoutMs > 0 ? await this.withTimeout(call, timeoutMs, 'AI review (session)') : await call;
295274
+ const { response, effectiveSchema } = timeoutMs > 0
295275
+ ? await this.withTimeout(call, timeoutMs, 'AI review (session)', currentSessionId, extender)
295276
+ : await call;
295257
295277
  const processingTime = Date.now() - startTime;
295258
295278
  if (debugInfo) {
295259
295279
  debugInfo.rawResponse = response;
@@ -295299,19 +295319,110 @@ class AIReviewService {
295299
295319
  }
295300
295320
  }
295301
295321
  /**
295302
- * Promise timeout helper that rejects after ms if unresolved
295322
+ * Promise timeout helper that rejects after ms if unresolved.
295323
+ * When a sessionId is provided, triggers graceful wind-down on the
295324
+ * ProbeAgent before rejecting, giving it a chance to summarize.
295325
+ *
295326
+ * When an extender is provided, the deadline can be dynamically pushed
295327
+ * forward (e.g. when the agent's negotiated timeout observer grants an
295328
+ * extension and emits a `timeout.extended` event).
295303
295329
  */
295304
- async withTimeout(p, ms, label = 'operation') {
295330
+ async withTimeout(p, ms, label = 'operation', sessionId, extender) {
295305
295331
  let timer;
295332
+ const startTime = Date.now();
295333
+ let deadlineMs = ms; // total ms from startTime
295306
295334
  try {
295307
295335
  const timeout = new Promise((_, reject) => {
295308
- timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
295336
+ const scheduleTimer = () => {
295337
+ if (timer)
295338
+ clearTimeout(timer);
295339
+ const remaining = deadlineMs - (Date.now() - startTime);
295340
+ if (remaining <= 0) {
295341
+ fireTimeout(reject);
295342
+ return;
295343
+ }
295344
+ timer = setTimeout(() => fireTimeout(reject), remaining);
295345
+ };
295346
+ const fireTimeout = (rej) => {
295347
+ // Signal the agent to wind down before the hard kill.
295348
+ // This gives the agent a chance to produce partial results
295349
+ // in its bonus wind-down steps before we reject the promise.
295350
+ if (sessionId) {
295351
+ try {
295352
+ const agent = this.sessionRegistry.getSession(sessionId);
295353
+ if (agent && typeof agent.triggerGracefulWindDown === 'function') {
295354
+ agent.triggerGracefulWindDown();
295355
+ }
295356
+ }
295357
+ catch {
295358
+ // Best-effort: don't let registry errors block the timeout
295359
+ }
295360
+ }
295361
+ rej(new Error(`${label} timed out after ${deadlineMs}ms`));
295362
+ };
295363
+ // Allow the deadline to be extended dynamically
295364
+ if (extender) {
295365
+ extender._listener = (extraMs) => {
295366
+ deadlineMs += extraMs;
295367
+ try {
295368
+ const { addEvent } = __nccwpck_require__(75338);
295369
+ addEvent('visor.timeout_extended', {
295370
+ extra_ms: extraMs,
295371
+ new_deadline_ms: deadlineMs,
295372
+ elapsed_ms: Date.now() - startTime,
295373
+ });
295374
+ }
295375
+ catch { }
295376
+ log(`⏱️ Timeout extended by ${Math.round(extraMs / 60000)}min → new deadline ${Math.round(deadlineMs / 60000)}min`);
295377
+ scheduleTimer(); // Reset the timer with the new deadline
295378
+ };
295379
+ }
295380
+ scheduleTimer();
295309
295381
  });
295310
295382
  return (await Promise.race([p, timeout]));
295311
295383
  }
295312
295384
  finally {
295313
295385
  if (timer)
295314
295386
  clearTimeout(timer);
295387
+ if (extender)
295388
+ extender._listener = undefined;
295389
+ }
295390
+ }
295391
+ /**
295392
+ * Wire ProbeAgent's timeout events to a TimeoutExtender so the parent's
295393
+ * withTimeout deadline dynamically tracks the agent's negotiated extensions.
295394
+ *
295395
+ * - `timeout.extended`: observer granted more time → push deadline forward
295396
+ * - `timeout.windingDown`: observer declined → agent is finishing, log it
295397
+ *
295398
+ * Safe to call on older Probe versions that don't emit these events.
295399
+ */
295400
+ wireTimeoutEvents(agent, extender) {
295401
+ try {
295402
+ const events = agent.events;
295403
+ if (!events || typeof events.on !== 'function')
295404
+ return;
295405
+ if (extender) {
295406
+ events.on('timeout.extended', (data) => {
295407
+ log(`⏱️ Agent extended timeout: +${Math.round(data.grantedMs / 60000)}min (${data.reason || 'work in progress'})`);
295408
+ extender.extend(data.grantedMs);
295409
+ });
295410
+ }
295411
+ events.on('timeout.windingDown', (data) => {
295412
+ log(`⏱️ Agent winding down: ${data.reason || 'observer declined'} (extensions used: ${data.extensionsUsed ?? 0}, total extra: ${Math.round((data.totalExtraTimeMs ?? 0) / 60000)}min)`);
295413
+ try {
295414
+ const { addEvent } = __nccwpck_require__(75338);
295415
+ addEvent('visor.agent_winding_down', {
295416
+ reason: data.reason || 'observer declined',
295417
+ extensions_used: data.extensionsUsed ?? 0,
295418
+ total_extra_time_ms: data.totalExtraTimeMs ?? 0,
295419
+ });
295420
+ }
295421
+ catch { }
295422
+ });
295423
+ }
295424
+ catch {
295425
+ // Best-effort: older Probe versions may not have events
295315
295426
  }
295316
295427
  }
295317
295428
  /**
@@ -295905,7 +296016,7 @@ ${this.escapeXml(processedFallbackDiff)}
295905
296016
  /**
295906
296017
  * Call ProbeAgent with an existing session
295907
296018
  */
295908
- async callProbeAgentWithExistingSession(agent, prompt, schema, debugInfo, _checkName) {
296019
+ async callProbeAgentWithExistingSession(agent, prompt, schema, debugInfo, _checkName, extender) {
295909
296020
  // Handle mock model/provider for testing
295910
296021
  if (this.config.model === 'mock' || this.config.provider === 'mock') {
295911
296022
  log('🎭 Using mock AI model/provider for testing (session reuse)');
@@ -295925,7 +296036,10 @@ ${this.escapeXml(processedFallbackDiff)}
295925
296036
  if (reuseAiTimeout > 0) {
295926
296037
  agent.maxOperationTimeout = reuseAiTimeout;
295927
296038
  }
295928
- // Update negotiated timeout / graceful stop options on session reuse
296039
+ // Update negotiated timeout / graceful stop options on session reuse.
296040
+ // Budget/per-request values are passed through without capping — the agent's
296041
+ // observer now emits `timeout.extended` events and the parent dynamically
296042
+ // extends its withTimeout deadline to stay in sync (Probe #524).
295929
296043
  if (this.config.timeoutBehavior) {
295930
296044
  agent.timeoutBehavior = this.config.timeoutBehavior;
295931
296045
  }
@@ -295941,6 +296055,8 @@ ${this.escapeXml(processedFallbackDiff)}
295941
296055
  if (this.config.gracefulStopDeadline !== undefined) {
295942
296056
  agent.gracefulStopDeadline = this.config.gracefulStopDeadline;
295943
296057
  }
296058
+ // Wire agent timeout events to the extender so withTimeout tracks extensions
296059
+ this.wireTimeoutEvents(agent, extender);
295944
296060
  try {
295945
296061
  log('🚀 Calling existing ProbeAgent with answer()...');
295946
296062
  // Load and pass the actual schema content if provided (skip for plain schema)
@@ -296255,7 +296371,7 @@ ${'='.repeat(60)}
296255
296371
  /**
296256
296372
  * Call ProbeAgent SDK with built-in schema validation
296257
296373
  */
296258
- async callProbeAgent(prompt, schema, debugInfo, _checkName, providedSessionId) {
296374
+ async callProbeAgent(prompt, schema, debugInfo, _checkName, providedSessionId, extender) {
296259
296375
  // Derive a stable session ID for this call so the engine can reuse it later
296260
296376
  const sessionId = providedSessionId ||
296261
296377
  (() => {
@@ -296393,7 +296509,10 @@ ${'='.repeat(60)}
296393
296509
  if (aiTimeout > 0) {
296394
296510
  options.maxOperationTimeout = aiTimeout;
296395
296511
  }
296396
- // Pass negotiated timeout / graceful stop options to ProbeAgent
296512
+ // Pass negotiated timeout / graceful stop options to ProbeAgent.
296513
+ // Budget/per-request values are passed through without capping — the
296514
+ // agent's observer emits `timeout.extended` events and withTimeout
296515
+ // dynamically extends its deadline to stay in sync (Probe #524).
296397
296516
  if (this.config.timeoutBehavior) {
296398
296517
  options.timeoutBehavior = this.config.timeoutBehavior;
296399
296518
  }
@@ -296527,6 +296646,8 @@ ${'='.repeat(60)}
296527
296646
  if (typeof agent.initialize === 'function') {
296528
296647
  await agent.initialize();
296529
296648
  }
296649
+ // Wire agent timeout events to the extender so withTimeout tracks extensions
296650
+ this.wireTimeoutEvents(agent, extender);
296530
296651
  log('🚀 Calling ProbeAgent...');
296531
296652
  // Load and pass the actual schema content if provided (skip for plain schema)
296532
296653
  let schemaString = undefined;
@@ -298574,6 +298695,13 @@ async function main() {
298574
298695
  configPath: getArg('--config'),
298575
298696
  toolName: getArg('--mcp-tool-name'),
298576
298697
  toolDescription: getArg('--mcp-tool-description'),
298698
+ transport: getArg('--transport') || 'stdio',
298699
+ port: getArg('--port') ? Number(getArg('--port')) : 8080,
298700
+ host: getArg('--host') || '0.0.0.0',
298701
+ authToken: getArg('--auth-token'),
298702
+ authTokenEnv: getArg('--auth-token-env'),
298703
+ tlsCert: getArg('--tls-cert'),
298704
+ tlsKey: getArg('--tls-key'),
298577
298705
  };
298578
298706
  const { startMcpServer } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(93143)));
298579
298707
  await startMcpServer(mcpOptions);
@@ -298999,111 +299127,38 @@ async function main() {
298999
299127
  logger_1.logger.warn(`[TaskTracking] Failed to initialize task store: ${err instanceof Error ? err.message : err}`);
299000
299128
  }
299001
299129
  }
299002
- // ---- A2A Agent Protocol Server ----
299003
- let a2aFrontendInstance = null;
299004
- let sharedEngine = null;
299005
- if (options.a2a || config.agent_protocol?.enabled) {
299006
- const { StateMachineExecutionEngine: SMEngine } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(39004)));
299007
- const { A2AFrontend } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(75887)));
299008
- const { EventBus } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(83864)));
299009
- const agentConfig = config.agent_protocol;
299010
- if (!agentConfig) {
299011
- console.error('Error: agent_protocol configuration is required for --a2a mode');
299012
- process.exit(1);
299130
+ // ---- Parallel Runner Mode (--slack, --telegram, --a2a, --mcp, etc.) ----
299131
+ const requestedRunners = [];
299132
+ if (options.a2a || config.agent_protocol?.enabled)
299133
+ requestedRunners.push('a2a');
299134
+ if (options.slack)
299135
+ requestedRunners.push('slack');
299136
+ if (options.mcp)
299137
+ requestedRunners.push('mcp');
299138
+ if (options.telegram)
299139
+ requestedRunners.push('telegram');
299140
+ if (options.email)
299141
+ requestedRunners.push('email');
299142
+ if (options.whatsapp)
299143
+ requestedRunners.push('whatsapp');
299144
+ if (options.teams)
299145
+ requestedRunners.push('teams');
299146
+ if (requestedRunners.length > 0) {
299147
+ const { RunnerHost } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(89736)));
299148
+ const { createRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(11866)));
299149
+ const { initTelemetryFromConfig } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(84763)));
299150
+ await initTelemetryFromConfig(config);
299151
+ const engine = new state_machine_execution_engine_1.StateMachineExecutionEngine();
299152
+ const host = new RunnerHost();
299153
+ for (const name of requestedRunners) {
299154
+ host.addRunner(await createRunner(name, engine, config, options));
299013
299155
  }
299014
- const engine = new SMEngine();
299015
- const frontend = new A2AFrontend(agentConfig, sharedTaskStore ?? undefined);
299016
- frontend.setEngine(engine);
299017
- frontend.setVisorConfig(config);
299018
- const eventBus = new EventBus();
299019
- const ctx = {
299020
- eventBus,
299021
- logger: logger_1.logger,
299022
- config,
299023
- run: { runId: crypto.randomUUID() },
299024
- engine,
299025
- visorConfig: config,
299026
- };
299027
- await frontend.start(ctx);
299028
- const port = agentConfig.port ?? 9000;
299029
- const host = agentConfig.host ?? '0.0.0.0';
299030
- console.log(`A2A server running on ${host}:${port}`);
299031
- if (options.slack === true) {
299032
- // Both --a2a and --slack: keep references and fall through to Slack startup
299033
- a2aFrontendInstance = frontend;
299034
- sharedEngine = engine;
299035
- }
299036
- else {
299037
- // Standalone --a2a mode: block here with shutdown handlers
299038
- let shuttingDown = false;
299039
- const onShutdown = async (sig) => {
299040
- if (shuttingDown) {
299041
- process.exit(1);
299042
- return;
299043
- }
299044
- shuttingDown = true;
299045
- logger_1.logger.info(`[A2A] Received ${sig}, shutting down gracefully...`);
299046
- const forceTimer = setTimeout(() => {
299047
- logger_1.logger.error('[A2A] Shutdown timed out, forcing exit');
299048
- process.exit(1);
299049
- }, 5000);
299050
- forceTimer.unref();
299051
- try {
299052
- await frontend.stop();
299053
- }
299054
- catch { }
299055
- process.exit(0);
299056
- };
299057
- process.on('SIGINT', sig => {
299058
- onShutdown(sig);
299059
- });
299060
- process.on('SIGTERM', sig => {
299061
- onShutdown(sig);
299062
- });
299063
- process.stdin.resume();
299064
- return;
299065
- }
299066
- }
299067
- // Socket Mode runner: visor --slack [--config file]
299068
- if (options.slack === true) {
299069
- const { SlackSocketRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(45278)));
299070
- const engine = sharedEngine ?? new state_machine_execution_engine_1.StateMachineExecutionEngine();
299071
- const slackAny = config.slack || {};
299072
- const endpoint = slackAny.endpoint || '/bots/slack/support';
299073
- const mentions = slackAny.mentions || 'direct';
299074
- const threads = slackAny.threads || 'any';
299075
- const allow = Array.isArray(slackAny.channel_allowlist) ? slackAny.channel_allowlist : [];
299076
- const appToken = slackAny.app_token || process.env.SLACK_APP_TOKEN;
299077
- // Initialize telemetry for Slack mode (normally done later for CLI runs).
299078
- if (config?.telemetry) {
299079
- const t = config.telemetry;
299080
- await (0, opentelemetry_1.initTelemetry)({
299081
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true' || !!t?.enabled,
299082
- sink: process.env.VISOR_TELEMETRY_SINK || t?.sink || 'file',
299083
- file: { dir: process.env.VISOR_TRACE_DIR || t?.file?.dir, ndjson: !!t?.file?.ndjson },
299084
- autoInstrument: !!t?.tracing?.auto_instrumentations,
299085
- traceReport: !!t?.tracing?.trace_report?.enabled,
299086
- });
299087
- }
299088
- else {
299089
- await (0, opentelemetry_1.initTelemetry)({
299090
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true',
299091
- sink: process.env.VISOR_TELEMETRY_SINK || 'file',
299092
- file: { dir: process.env.VISOR_TRACE_DIR },
299093
- });
299094
- }
299095
- const runner = new SlackSocketRunner(engine, config, {
299096
- appToken,
299097
- endpoint,
299098
- mentions,
299099
- threads,
299100
- channel_allowlist: allow,
299101
- });
299102
299156
  if (sharedTaskStore)
299103
- runner.setTaskStore(sharedTaskStore, options.configPath);
299104
- await runner.start();
299105
- console.log('✅ Slack Socket Mode is running. Press Ctrl+C to exit.');
299106
- // Start config file watcher if --watch is enabled
299157
+ host.setTaskStore(sharedTaskStore, options.configPath);
299158
+ await host.startAll();
299159
+ const names = requestedRunners.join(', ');
299160
+ console.log(`✅ Runner(s) started: ${names}. Press Ctrl+C to exit.`);
299161
+ // Config watcher (shared, broadcasts to all runners)
299107
299162
  let configWatcher;
299108
299163
  let configWatchStore;
299109
299164
  if (options.watch) {
@@ -299123,7 +299178,7 @@ async function main() {
299123
299178
  snapshotStore: watchStore,
299124
299179
  onSwap: newConfig => {
299125
299180
  config = newConfig;
299126
- runner.updateConfig(newConfig);
299181
+ host.broadcastConfigUpdate(newConfig);
299127
299182
  logger_1.logger.info('[Watch] Config updated');
299128
299183
  },
299129
299184
  });
@@ -299134,23 +299189,20 @@ async function main() {
299134
299189
  logger_1.logger.info('Config watching enabled');
299135
299190
  }
299136
299191
  catch (watchErr) {
299137
- logger_1.logger.warn(`Config watch setup failed (Slack mode continues without it): ${watchErr}`);
299192
+ logger_1.logger.warn(`Config watch setup failed (runners continue without it): ${watchErr}`);
299138
299193
  }
299139
299194
  }
299140
- // Graceful shutdown: notify active threads before exiting
299195
+ // Unified graceful shutdown
299141
299196
  let shuttingDown = false;
299142
299197
  const onShutdown = async (sig) => {
299143
299198
  if (shuttingDown) {
299144
- // Second signal — force exit immediately
299145
- logger_1.logger.warn(`[Slack] Received ${sig} again, forcing exit`);
299146
299199
  process.exit(1);
299147
299200
  return;
299148
299201
  }
299149
299202
  shuttingDown = true;
299150
- logger_1.logger.info(`[Slack] Received ${sig}, shutting down gracefully…`);
299151
- // Force exit after 5s if graceful shutdown hangs
299203
+ logger_1.logger.info(`[RunnerHost] Received ${sig}, shutting down gracefully…`);
299152
299204
  const forceTimer = setTimeout(() => {
299153
- logger_1.logger.error('[Slack] Graceful shutdown timed out after 5s, forcing exit');
299205
+ logger_1.logger.error('[RunnerHost] Shutdown timed out after 5s, forcing exit');
299154
299206
  process.exit(1);
299155
299207
  }, 5000);
299156
299208
  forceTimer.unref();
@@ -299159,15 +299211,7 @@ async function main() {
299159
299211
  configWatcher.stop();
299160
299212
  if (configWatchStore)
299161
299213
  configWatchStore.shutdown().catch(() => { });
299162
- if (a2aFrontendInstance) {
299163
- try {
299164
- await a2aFrontendInstance.stop();
299165
- }
299166
- catch {
299167
- /* ignore */
299168
- }
299169
- }
299170
- await runner.stop();
299214
+ await host.stopAll();
299171
299215
  if (sharedTaskStore) {
299172
299216
  try {
299173
299217
  await sharedTaskStore.shutdown();
@@ -299176,444 +299220,8 @@ async function main() {
299176
299220
  }
299177
299221
  }
299178
299222
  catch (err) {
299179
- logger_1.logger.warn(`[Slack] Error during shutdown: ${err}`);
299180
- }
299181
- process.exit(0);
299182
- };
299183
- process.on('SIGINT', sig => {
299184
- onShutdown(sig);
299185
- });
299186
- process.on('SIGTERM', sig => {
299187
- onShutdown(sig);
299188
- });
299189
- process.stdin.resume();
299190
- return;
299191
- }
299192
- // Long-polling runner: visor --telegram [--config file]
299193
- if (options.telegram === true) {
299194
- const { TelegramPollingRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(33519)));
299195
- const engine = sharedEngine ?? new state_machine_execution_engine_1.StateMachineExecutionEngine();
299196
- const telegramAny = config.telegram || {};
299197
- // Initialize telemetry for Telegram mode
299198
- if (config?.telemetry) {
299199
- const t = config.telemetry;
299200
- await (0, opentelemetry_1.initTelemetry)({
299201
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true' || !!t?.enabled,
299202
- sink: process.env.VISOR_TELEMETRY_SINK || t?.sink || 'file',
299203
- file: { dir: process.env.VISOR_TRACE_DIR || t?.file?.dir, ndjson: !!t?.file?.ndjson },
299204
- autoInstrument: !!t?.tracing?.auto_instrumentations,
299205
- traceReport: !!t?.tracing?.trace_report?.enabled,
299206
- });
299207
- }
299208
- else {
299209
- await (0, opentelemetry_1.initTelemetry)({
299210
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true',
299211
- sink: process.env.VISOR_TELEMETRY_SINK || 'file',
299212
- file: { dir: process.env.VISOR_TRACE_DIR },
299213
- });
299214
- }
299215
- const runner = new TelegramPollingRunner(engine, config, {
299216
- botToken: telegramAny.bot_token || process.env.TELEGRAM_BOT_TOKEN,
299217
- pollingTimeout: telegramAny.polling_timeout || 30,
299218
- chatAllowlist: telegramAny.chat_allowlist,
299219
- requireMention: telegramAny.require_mention ?? true,
299220
- workflow: telegramAny.workflow,
299221
- });
299222
- if (sharedTaskStore)
299223
- runner.setTaskStore(sharedTaskStore, options.configPath);
299224
- await runner.start();
299225
- console.log('✅ Telegram long-polling is running. Press Ctrl+C to exit.');
299226
- // Config watcher (same pattern as Slack)
299227
- let configWatcher;
299228
- let configWatchStore;
299229
- if (options.watch) {
299230
- if (!options.configPath) {
299231
- console.error('❌ --watch requires --config <path>');
299232
- process.exit(1);
299233
- }
299234
- try {
299235
- const { ConfigSnapshotStore } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(23147)));
299236
- const { ConfigReloader } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(75773)));
299237
- const { ConfigWatcher } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(39995)));
299238
- const watchStore = new ConfigSnapshotStore();
299239
- await watchStore.initialize();
299240
- const reloader = new ConfigReloader({
299241
- configPath: options.configPath,
299242
- configManager,
299243
- snapshotStore: watchStore,
299244
- onSwap: newConfig => {
299245
- config = newConfig;
299246
- runner.updateConfig(newConfig);
299247
- logger_1.logger.info('[Watch] Config updated');
299248
- },
299249
- });
299250
- const watcher = new ConfigWatcher(options.configPath, reloader);
299251
- watcher.start();
299252
- configWatcher = watcher;
299253
- configWatchStore = watchStore;
299254
- logger_1.logger.info('Config watching enabled');
299255
- }
299256
- catch (watchErr) {
299257
- logger_1.logger.warn(`Config watch setup failed (Telegram mode continues without it): ${watchErr}`);
299258
- }
299259
- }
299260
- // Graceful shutdown
299261
- let shuttingDown = false;
299262
- const onShutdown = async (sig) => {
299263
- if (shuttingDown) {
299264
- process.exit(1);
299265
- return;
299223
+ logger_1.logger.warn(`[RunnerHost] Error during shutdown: ${err}`);
299266
299224
  }
299267
- shuttingDown = true;
299268
- logger_1.logger.info(`[Telegram] Received ${sig}, shutting down gracefully…`);
299269
- const forceTimer = setTimeout(() => {
299270
- logger_1.logger.error('[Telegram] Shutdown timed out, forcing exit');
299271
- process.exit(1);
299272
- }, 5000);
299273
- forceTimer.unref();
299274
- try {
299275
- if (configWatcher)
299276
- configWatcher.stop();
299277
- if (configWatchStore)
299278
- configWatchStore.shutdown().catch(() => { });
299279
- await runner.stop();
299280
- if (sharedTaskStore) {
299281
- try {
299282
- await sharedTaskStore.shutdown();
299283
- }
299284
- catch { }
299285
- }
299286
- }
299287
- catch { }
299288
- process.exit(0);
299289
- };
299290
- process.on('SIGINT', sig => {
299291
- onShutdown(sig);
299292
- });
299293
- process.on('SIGTERM', sig => {
299294
- onShutdown(sig);
299295
- });
299296
- process.stdin.resume();
299297
- return;
299298
- }
299299
- // Email polling runner: visor --email [--config file]
299300
- if (options.email === true) {
299301
- const { EmailPollingRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(66550)));
299302
- const engine = sharedEngine ?? new state_machine_execution_engine_1.StateMachineExecutionEngine();
299303
- const emailAny = config.email || {};
299304
- // Initialize telemetry for Email mode
299305
- if (config?.telemetry) {
299306
- const t = config.telemetry;
299307
- await (0, opentelemetry_1.initTelemetry)({
299308
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true' || !!t?.enabled,
299309
- sink: process.env.VISOR_TELEMETRY_SINK || t?.sink || 'file',
299310
- file: { dir: process.env.VISOR_TRACE_DIR || t?.file?.dir, ndjson: !!t?.file?.ndjson },
299311
- autoInstrument: !!t?.tracing?.auto_instrumentations,
299312
- traceReport: !!t?.tracing?.trace_report?.enabled,
299313
- });
299314
- }
299315
- else {
299316
- await (0, opentelemetry_1.initTelemetry)({
299317
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true',
299318
- sink: process.env.VISOR_TELEMETRY_SINK || 'file',
299319
- file: { dir: process.env.VISOR_TRACE_DIR },
299320
- });
299321
- }
299322
- const runner = new EmailPollingRunner(engine, config, {
299323
- receive: emailAny.receive || {},
299324
- send: emailAny.send || {},
299325
- allowlist: emailAny.allowlist,
299326
- workflow: emailAny.workflow,
299327
- });
299328
- if (sharedTaskStore)
299329
- runner.setTaskStore(sharedTaskStore, options.configPath);
299330
- await runner.start();
299331
- const receiveType = emailAny.receive?.type || 'imap';
299332
- console.log(`✅ Email runner (${receiveType}) is running. Press Ctrl+C to exit.`);
299333
- // Config watcher (same pattern as Telegram)
299334
- let configWatcher;
299335
- let configWatchStore;
299336
- if (options.watch) {
299337
- if (!options.configPath) {
299338
- console.error('❌ --watch requires --config <path>');
299339
- process.exit(1);
299340
- }
299341
- try {
299342
- const { ConfigSnapshotStore } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(23147)));
299343
- const { ConfigReloader } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(75773)));
299344
- const { ConfigWatcher } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(39995)));
299345
- const watchStore = new ConfigSnapshotStore();
299346
- await watchStore.initialize();
299347
- const reloader = new ConfigReloader({
299348
- configPath: options.configPath,
299349
- configManager,
299350
- snapshotStore: watchStore,
299351
- onSwap: newConfig => {
299352
- config = newConfig;
299353
- runner.updateConfig(newConfig);
299354
- logger_1.logger.info('[Watch] Config updated');
299355
- },
299356
- });
299357
- const watcher = new ConfigWatcher(options.configPath, reloader);
299358
- watcher.start();
299359
- configWatcher = watcher;
299360
- configWatchStore = watchStore;
299361
- logger_1.logger.info('Config watching enabled');
299362
- }
299363
- catch (watchErr) {
299364
- logger_1.logger.warn(`Config watch setup failed (Email mode continues without it): ${watchErr}`);
299365
- }
299366
- }
299367
- // Graceful shutdown
299368
- let shuttingDown = false;
299369
- const onShutdown = async (sig) => {
299370
- if (shuttingDown) {
299371
- process.exit(1);
299372
- return;
299373
- }
299374
- shuttingDown = true;
299375
- logger_1.logger.info(`[Email] Received ${sig}, shutting down gracefully…`);
299376
- const forceTimer = setTimeout(() => {
299377
- logger_1.logger.error('[Email] Shutdown timed out, forcing exit');
299378
- process.exit(1);
299379
- }, 5000);
299380
- forceTimer.unref();
299381
- try {
299382
- if (configWatcher)
299383
- configWatcher.stop();
299384
- if (configWatchStore)
299385
- configWatchStore.shutdown().catch(() => { });
299386
- await runner.stop();
299387
- if (sharedTaskStore) {
299388
- try {
299389
- await sharedTaskStore.shutdown();
299390
- }
299391
- catch { }
299392
- }
299393
- }
299394
- catch { }
299395
- process.exit(0);
299396
- };
299397
- process.on('SIGINT', sig => {
299398
- onShutdown(sig);
299399
- });
299400
- process.on('SIGTERM', sig => {
299401
- onShutdown(sig);
299402
- });
299403
- process.stdin.resume();
299404
- return;
299405
- }
299406
- // WhatsApp webhook runner: visor --whatsapp [--config file]
299407
- if (options.whatsapp === true) {
299408
- const { WhatsAppWebhookRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(21718)));
299409
- const engine = sharedEngine ?? new state_machine_execution_engine_1.StateMachineExecutionEngine();
299410
- const waAny = config.whatsapp || {};
299411
- // Initialize telemetry for WhatsApp mode
299412
- if (config?.telemetry) {
299413
- const t = config.telemetry;
299414
- await (0, opentelemetry_1.initTelemetry)({
299415
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true' || !!t?.enabled,
299416
- sink: process.env.VISOR_TELEMETRY_SINK || t?.sink || 'file',
299417
- file: { dir: process.env.VISOR_TRACE_DIR || t?.file?.dir, ndjson: !!t?.file?.ndjson },
299418
- autoInstrument: !!t?.tracing?.auto_instrumentations,
299419
- traceReport: !!t?.tracing?.trace_report?.enabled,
299420
- });
299421
- }
299422
- else {
299423
- await (0, opentelemetry_1.initTelemetry)({
299424
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true',
299425
- sink: process.env.VISOR_TELEMETRY_SINK || 'file',
299426
- file: { dir: process.env.VISOR_TRACE_DIR },
299427
- });
299428
- }
299429
- const runner = new WhatsAppWebhookRunner(engine, config, {
299430
- accessToken: waAny.access_token || process.env.WHATSAPP_ACCESS_TOKEN,
299431
- phoneNumberId: waAny.phone_number_id || process.env.WHATSAPP_PHONE_NUMBER_ID,
299432
- appSecret: waAny.app_secret || process.env.WHATSAPP_APP_SECRET,
299433
- verifyToken: waAny.verify_token || process.env.WHATSAPP_VERIFY_TOKEN,
299434
- apiVersion: waAny.api_version || 'v21.0',
299435
- port: waAny.port || parseInt(process.env.WHATSAPP_WEBHOOK_PORT || '8443'),
299436
- host: waAny.host || '0.0.0.0',
299437
- phoneAllowlist: waAny.phone_allowlist,
299438
- workflow: waAny.workflow,
299439
- });
299440
- if (sharedTaskStore)
299441
- runner.setTaskStore(sharedTaskStore, options.configPath);
299442
- await runner.start();
299443
- const port = waAny.port || parseInt(process.env.WHATSAPP_WEBHOOK_PORT || '8443');
299444
- console.log(`✅ WhatsApp webhook server running on port ${port}. Press Ctrl+C to exit.`);
299445
- // Config watcher (same pattern as Telegram/Email)
299446
- let configWatcher;
299447
- let configWatchStore;
299448
- if (options.watch) {
299449
- if (!options.configPath) {
299450
- console.error('❌ --watch requires --config <path>');
299451
- process.exit(1);
299452
- }
299453
- try {
299454
- const { ConfigSnapshotStore } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(23147)));
299455
- const { ConfigReloader } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(75773)));
299456
- const { ConfigWatcher } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(39995)));
299457
- const watchStore = new ConfigSnapshotStore();
299458
- await watchStore.initialize();
299459
- const reloader = new ConfigReloader({
299460
- configPath: options.configPath,
299461
- configManager,
299462
- snapshotStore: watchStore,
299463
- onSwap: newConfig => {
299464
- config = newConfig;
299465
- runner.updateConfig(newConfig);
299466
- logger_1.logger.info('[Watch] Config updated');
299467
- },
299468
- });
299469
- const watcher = new ConfigWatcher(options.configPath, reloader);
299470
- watcher.start();
299471
- configWatcher = watcher;
299472
- configWatchStore = watchStore;
299473
- logger_1.logger.info('Config watching enabled');
299474
- }
299475
- catch (watchErr) {
299476
- logger_1.logger.warn(`Config watch setup failed (WhatsApp mode continues without it): ${watchErr}`);
299477
- }
299478
- }
299479
- // Graceful shutdown
299480
- let shuttingDown = false;
299481
- const onShutdown = async (sig) => {
299482
- if (shuttingDown) {
299483
- process.exit(1);
299484
- return;
299485
- }
299486
- shuttingDown = true;
299487
- logger_1.logger.info(`[WhatsApp] Received ${sig}, shutting down gracefully…`);
299488
- const forceTimer = setTimeout(() => {
299489
- logger_1.logger.error('[WhatsApp] Shutdown timed out, forcing exit');
299490
- process.exit(1);
299491
- }, 5000);
299492
- forceTimer.unref();
299493
- try {
299494
- if (configWatcher)
299495
- configWatcher.stop();
299496
- if (configWatchStore)
299497
- configWatchStore.shutdown().catch(() => { });
299498
- await runner.stop();
299499
- if (sharedTaskStore) {
299500
- try {
299501
- await sharedTaskStore.shutdown();
299502
- }
299503
- catch { }
299504
- }
299505
- }
299506
- catch { }
299507
- process.exit(0);
299508
- };
299509
- process.on('SIGINT', sig => {
299510
- onShutdown(sig);
299511
- });
299512
- process.on('SIGTERM', sig => {
299513
- onShutdown(sig);
299514
- });
299515
- process.stdin.resume();
299516
- return;
299517
- }
299518
- // ── Microsoft Teams webhook runner ──────────────────────────────
299519
- if (options.teams === true) {
299520
- const { TeamsWebhookRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(86710)));
299521
- const engine = sharedEngine ?? new state_machine_execution_engine_1.StateMachineExecutionEngine();
299522
- const teamsAny = config.teams || {};
299523
- // Initialize telemetry for Teams mode
299524
- if (config?.telemetry) {
299525
- const t = config.telemetry;
299526
- await (0, opentelemetry_1.initTelemetry)({
299527
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true' || !!t?.enabled,
299528
- sink: process.env.VISOR_TELEMETRY_SINK || t?.sink || 'file',
299529
- file: { dir: process.env.VISOR_TRACE_DIR || t?.file?.dir, ndjson: !!t?.file?.ndjson },
299530
- autoInstrument: !!t?.tracing?.auto_instrumentations,
299531
- traceReport: !!t?.tracing?.trace_report?.enabled,
299532
- });
299533
- }
299534
- else {
299535
- await (0, opentelemetry_1.initTelemetry)({
299536
- enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true',
299537
- sink: process.env.VISOR_TELEMETRY_SINK || 'file',
299538
- file: { dir: process.env.VISOR_TRACE_DIR },
299539
- });
299540
- }
299541
- const runner = new TeamsWebhookRunner(engine, config, {
299542
- appId: teamsAny.app_id || process.env.TEAMS_APP_ID,
299543
- appPassword: teamsAny.app_password || process.env.TEAMS_APP_PASSWORD,
299544
- tenantId: teamsAny.tenant_id || process.env.TEAMS_TENANT_ID,
299545
- port: teamsAny.port || parseInt(process.env.TEAMS_WEBHOOK_PORT || '3978'),
299546
- host: teamsAny.host || '0.0.0.0',
299547
- userAllowlist: teamsAny.user_allowlist,
299548
- workflow: teamsAny.workflow,
299549
- });
299550
- if (sharedTaskStore)
299551
- runner.setTaskStore(sharedTaskStore, options.configPath);
299552
- await runner.start();
299553
- const port = teamsAny.port || parseInt(process.env.TEAMS_WEBHOOK_PORT || '3978');
299554
- console.log(`✅ Teams webhook server running on port ${port}. Press Ctrl+C to exit.`);
299555
- // Config watcher (same pattern as WhatsApp/Telegram/Email)
299556
- let configWatcher;
299557
- let configWatchStore;
299558
- if (options.watch) {
299559
- if (!options.configPath) {
299560
- console.error('❌ --watch requires --config <path>');
299561
- process.exit(1);
299562
- }
299563
- try {
299564
- const { ConfigSnapshotStore } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(23147)));
299565
- const { ConfigReloader } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(75773)));
299566
- const { ConfigWatcher } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(39995)));
299567
- const watchStore = new ConfigSnapshotStore();
299568
- await watchStore.initialize();
299569
- const reloader = new ConfigReloader({
299570
- configPath: options.configPath,
299571
- configManager,
299572
- snapshotStore: watchStore,
299573
- onSwap: newConfig => {
299574
- config = newConfig;
299575
- runner.updateConfig(newConfig);
299576
- logger_1.logger.info('[Watch] Config updated');
299577
- },
299578
- });
299579
- const watcher = new ConfigWatcher(options.configPath, reloader);
299580
- watcher.start();
299581
- configWatcher = watcher;
299582
- configWatchStore = watchStore;
299583
- logger_1.logger.info('Config watching enabled');
299584
- }
299585
- catch (watchErr) {
299586
- logger_1.logger.warn(`Config watch setup failed (Teams mode continues without it): ${watchErr}`);
299587
- }
299588
- }
299589
- // Graceful shutdown
299590
- let shuttingDown = false;
299591
- const onShutdown = async (sig) => {
299592
- if (shuttingDown) {
299593
- process.exit(1);
299594
- return;
299595
- }
299596
- shuttingDown = true;
299597
- logger_1.logger.info(`[Teams] Received ${sig}, shutting down gracefully…`);
299598
- const forceTimer = setTimeout(() => {
299599
- logger_1.logger.error('[Teams] Shutdown timed out, forcing exit');
299600
- process.exit(1);
299601
- }, 5000);
299602
- forceTimer.unref();
299603
- try {
299604
- if (configWatcher)
299605
- configWatcher.stop();
299606
- if (configWatchStore)
299607
- configWatchStore.shutdown().catch(() => { });
299608
- await runner.stop();
299609
- if (sharedTaskStore) {
299610
- try {
299611
- await sharedTaskStore.shutdown();
299612
- }
299613
- catch { }
299614
- }
299615
- }
299616
- catch { }
299617
299225
  process.exit(0);
299618
299226
  };
299619
299227
  process.on('SIGINT', sig => {
@@ -300502,6 +300110,9 @@ class CLI {
300502
300110
  .option('--whatsapp', 'Enable WhatsApp webhook runner (uses WHATSAPP_ACCESS_TOKEN)')
300503
300111
  .option('--teams', 'Enable Microsoft Teams webhook runner (uses TEAMS_APP_ID)')
300504
300112
  .option('--a2a', 'Enable A2A Agent Protocol server mode')
300113
+ .option('--mcp', 'Enable MCP HTTP server runner (composable, requires --mcp-auth-token)')
300114
+ .option('--mcp-port <port>', 'Port for MCP HTTP server (default: 8080)', value => parseInt(value, 10))
300115
+ .option('--mcp-auth-token <token>', 'Bearer token for MCP HTTP server authentication')
300505
300116
  .option('-c, --check <type>', 'Specify check type (can be used multiple times)', this.collectChecks, [])
300506
300117
  .option('-o, --output <format>', 'Output format (table, json, markdown, sarif)', 'table')
300507
300118
  .option('--output-file <path>', 'Write formatted output to a file instead of stdout')
@@ -300639,6 +300250,9 @@ class CLI {
300639
300250
  whatsapp: Boolean(options.whatsapp),
300640
300251
  teams: Boolean(options.teams),
300641
300252
  a2a: Boolean(options.a2a),
300253
+ mcp: Boolean(options.mcp),
300254
+ mcpPort: options.mcpPort,
300255
+ mcpAuthToken: options.mcpAuthToken,
300642
300256
  tui: Boolean(options.tui),
300643
300257
  keepWorkspace: Boolean(options.keepWorkspace),
300644
300258
  workspacePath: options.workspacePath,
@@ -300784,7 +300398,9 @@ Examples:
300784
300398
  visor --check all --fail-fast --output json # Stop on first failure
300785
300399
  visor --tags local,fast --output table # Run checks tagged as 'local' or 'fast'
300786
300400
  visor --exclude-tags slow,experimental --output json # Skip checks tagged as 'slow' or 'experimental'
300787
- visor --tags security --exclude-tags slow # Run security checks but skip slow ones`;
300401
+ visor --tags security --exclude-tags slow # Run security checks but skip slow ones
300402
+ visor --slack --mcp --mcp-auth-token secret # Run Slack + MCP HTTP in parallel
300403
+ visor --slack --a2a --telegram # Run multiple frontends in parallel`;
300788
300404
  }
300789
300405
  /**
300790
300406
  * Display help
@@ -302566,6 +302182,7 @@ class ConfigManager {
302566
302182
  const allowedTopLevelKeys = new Set([
302567
302183
  'tests',
302568
302184
  'slack',
302185
+ 'mcp_server',
302569
302186
  'sandboxes',
302570
302187
  'sandbox',
302571
302188
  'sandbox_defaults',
@@ -305334,6 +304951,7 @@ const adapter_1 = __nccwpck_require__(8611);
305334
304951
  const crypto_1 = __nccwpck_require__(76982);
305335
304952
  const workspace_manager_1 = __nccwpck_require__(6134);
305336
304953
  class EmailPollingRunner {
304954
+ name = 'email';
305337
304955
  client;
305338
304956
  adapter;
305339
304957
  engine;
@@ -308595,9 +308213,12 @@ class GitHubFrontend {
308595
308213
  ? failureResults.some((r) => r && r.failed)
308596
308214
  : false;
308597
308215
  const group = this.getGroupForCheck(ctx, ev.checkId);
308598
- // Extract text from JSON-like content if template didn't unwrap it properly
308216
+ // Extract text from JSON-like content if template didn't unwrap it properly.
308217
+ // Fall back to output.text / output.response when template rendering failed
308218
+ // (e.g., template file not found in ncc bundle) — this is the primary path
308219
+ // for schema-based checks like comment-assistant / issue-assistant.
308599
308220
  const rawContent = ev?.result?.content;
308600
- const extractedContent = (0, json_text_extractor_1.extractTextFromJson)(rawContent);
308221
+ const extractedContent = (0, json_text_extractor_1.extractTextFromJson)(rawContent) ?? (0, json_text_extractor_1.extractTextFromJson)(ev?.result?.output);
308601
308222
  this.upsertSectionState(group, ev.checkId, {
308602
308223
  status: 'completed',
308603
308224
  conclusion: failed ? 'failure' : 'success',
@@ -318484,13 +318105,13 @@ function configureLoggerFromCli(options) {
318484
318105
  * MCP Server for Visor
318485
318106
  *
318486
318107
  * Exposes Visor workflows as MCP tools, allowing Claude Code and other
318487
- * MCP clients to execute workflows programmatically via stdio transport.
318108
+ * MCP clients to execute workflows programmatically via stdio or HTTP transport.
318488
318109
  *
318489
318110
  * Usage:
318490
- * # Generic mode - workflow is a tool parameter
318111
+ * # Generic mode - workflow is a tool parameter (stdio)
318491
318112
  * visor mcp-server
318492
318113
  *
318493
- * # Fixed workflow mode - workflow is pre-configured
318114
+ * # Fixed workflow mode - workflow is pre-configured (stdio)
318494
318115
  * visor mcp-server --config defaults/code-review.yaml
318495
318116
  *
318496
318117
  * # Custom tool name and description
@@ -318498,6 +318119,17 @@ function configureLoggerFromCli(options) {
318498
318119
  * --mcp-tool-name "code_review" \
318499
318120
  * --mcp-tool-description "Run a code review for current uncommitted changes"
318500
318121
  *
318122
+ * # Remote HTTP mode with Bearer token auth
318123
+ * visor mcp-server --transport http --port 8080 --auth-token "my-secret"
318124
+ *
318125
+ * # HTTP with token from environment variable
318126
+ * VISOR_MCP_TOKEN=secret visor mcp-server --transport http --auth-token-env VISOR_MCP_TOKEN
318127
+ *
318128
+ * # HTTPS with TLS certificates
318129
+ * visor mcp-server --transport http --port 443 \
318130
+ * --tls-cert /path/to/cert.pem --tls-key /path/to/key.pem \
318131
+ * --auth-token-env VISOR_MCP_TOKEN
318132
+ *
318501
318133
  * Claude Code config example (~/.claude/claude_desktop_config.json):
318502
318134
  * {
318503
318135
  * "mcpServers": {
@@ -318547,13 +318179,19 @@ exports.resolveWorkflowPath = resolveWorkflowPath;
318547
318179
  exports.formatResults = formatResults;
318548
318180
  exports.executeWorkflow = executeWorkflow;
318549
318181
  exports.executeFixedWorkflow = executeFixedWorkflow;
318182
+ exports.validateBearerToken = validateBearerToken;
318183
+ exports.createHttpMcpServer = createHttpMcpServer;
318550
318184
  exports.startMcpServer = startMcpServer;
318551
318185
  const mcp_js_1 = __nccwpck_require__(23886);
318552
318186
  const stdio_js_1 = __nccwpck_require__(64239);
318187
+ const streamableHttp_js_1 = __nccwpck_require__(1048);
318553
318188
  const zod_1 = __nccwpck_require__(50924);
318554
318189
  const sdk_1 = __nccwpck_require__(1091);
318555
318190
  const path = __importStar(__nccwpck_require__(16928));
318556
318191
  const fs = __importStar(__nccwpck_require__(79896));
318192
+ const http = __importStar(__nccwpck_require__(58611));
318193
+ const https = __importStar(__nccwpck_require__(65692));
318194
+ const crypto = __importStar(__nccwpck_require__(76982));
318557
318195
  /**
318558
318196
  * Available default workflows bundled with Visor.
318559
318197
  */
@@ -318563,12 +318201,31 @@ exports.DEFAULT_WORKFLOWS = [
318563
318201
  'task-refinement',
318564
318202
  'code-refiner',
318565
318203
  ];
318204
+ /**
318205
+ * Resolve the Visor version from package.json or environment.
318206
+ */
318207
+ function getVisorVersion() {
318208
+ if (process.env.VISOR_VERSION)
318209
+ return process.env.VISOR_VERSION;
318210
+ try {
318211
+ const pkgPath = path.join(__dirname, '..', 'package.json');
318212
+ if (fs.existsSync(pkgPath)) {
318213
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
318214
+ if (pkg.version)
318215
+ return pkg.version;
318216
+ }
318217
+ }
318218
+ catch { }
318219
+ return 'unknown';
318220
+ }
318566
318221
  /**
318567
318222
  * Server metadata for MCP protocol.
318568
318223
  */
318569
318224
  exports.SERVER_INFO = {
318570
318225
  name: 'visor',
318571
- version: '1.0.0',
318226
+ get version() {
318227
+ return getVisorVersion();
318228
+ },
318572
318229
  description: 'Visor is an AI-powered code review and workflow automation tool. ' +
318573
318230
  'It analyzes code for security vulnerabilities, performance issues, architectural problems, ' +
318574
318231
  'and style violations. Visor can also orchestrate complex multi-step workflows with AI agents, ' +
@@ -318882,15 +318539,233 @@ async function executeFixedWorkflow(args, workflowPath) {
318882
318539
  };
318883
318540
  }
318884
318541
  }
318542
+ /**
318543
+ * Validate a Bearer token from an HTTP request using timing-safe comparison.
318544
+ */
318545
+ function validateBearerToken(req, expectedToken) {
318546
+ const header = req.headers['authorization'];
318547
+ if (!header || !header.startsWith('Bearer '))
318548
+ return false;
318549
+ const token = header.slice(7);
318550
+ if (token.length !== expectedToken.length)
318551
+ return false;
318552
+ try {
318553
+ return crypto.timingSafeEqual(Buffer.from(token), Buffer.from(expectedToken));
318554
+ }
318555
+ catch {
318556
+ return false;
318557
+ }
318558
+ }
318559
+ /**
318560
+ * Read and parse JSON body from an HTTP request.
318561
+ */
318562
+ function readBody(req) {
318563
+ return new Promise((resolve, reject) => {
318564
+ let data = '';
318565
+ req.on('data', (chunk) => (data += chunk));
318566
+ req.on('end', () => {
318567
+ if (!data.trim()) {
318568
+ resolve(undefined);
318569
+ return;
318570
+ }
318571
+ try {
318572
+ resolve(JSON.parse(data));
318573
+ }
318574
+ catch {
318575
+ reject(new Error('Invalid JSON'));
318576
+ }
318577
+ });
318578
+ req.on('error', reject);
318579
+ });
318580
+ }
318581
+ /**
318582
+ * Create and start an MCP HTTP/HTTPS server, returning a handle for lifecycle control.
318583
+ *
318584
+ * Unlike startHttpMcpServer (used internally by startMcpServer), this function
318585
+ * does NOT register process signal handlers — the caller is responsible for
318586
+ * calling handle.close() during shutdown.
318587
+ */
318588
+ async function createHttpMcpServer(options) {
318589
+ // If fixed workflow mode, validate config path at startup
318590
+ let resolvedWorkflowPath;
318591
+ if (options.configPath) {
318592
+ resolvedWorkflowPath = resolveWorkflowPath(options.configPath);
318593
+ }
318594
+ const server = new mcp_js_1.McpServer({ name: exports.SERVER_INFO.name, version: exports.SERVER_INFO.version }, { capabilities: { tools: {} } });
318595
+ const toolName = options.toolName || 'run_workflow';
318596
+ const toolDescription = options.toolDescription || exports.RUN_WORKFLOW_DESCRIPTION;
318597
+ if (resolvedWorkflowPath) {
318598
+ server.tool(toolName, toolDescription, {
318599
+ message: exports.FixedWorkflowSchema.shape.message,
318600
+ checks: exports.FixedWorkflowSchema.shape.checks,
318601
+ format: exports.FixedWorkflowSchema.shape.format,
318602
+ }, async (args) => executeFixedWorkflow(args, resolvedWorkflowPath));
318603
+ }
318604
+ else {
318605
+ server.tool(toolName, toolDescription, {
318606
+ workflow: exports.RunWorkflowSchema.shape.workflow,
318607
+ message: exports.RunWorkflowSchema.shape.message,
318608
+ checks: exports.RunWorkflowSchema.shape.checks,
318609
+ format: exports.RunWorkflowSchema.shape.format,
318610
+ }, async (args) => executeWorkflow(args));
318611
+ }
318612
+ return startHttpMcpServerInternal(server, options);
318613
+ }
318614
+ /**
318615
+ * Start the MCP server over HTTP/HTTPS with StreamableHTTPServerTransport.
318616
+ * Returns a handle for non-blocking shutdown.
318617
+ */
318618
+ async function startHttpMcpServerInternal(server, options) {
318619
+ const port = options.port || 8080;
318620
+ const host = options.host || '0.0.0.0';
318621
+ // Resolve auth token
318622
+ const authToken = options.authToken || (options.authTokenEnv ? process.env[options.authTokenEnv] : undefined);
318623
+ if (!authToken) {
318624
+ throw new Error('HTTP transport requires --auth-token or --auth-token-env for security. ' +
318625
+ 'Refusing to start an unauthenticated remote MCP endpoint.');
318626
+ }
318627
+ // Per-session transport map
318628
+ const transports = new Map();
318629
+ const handleRequest = async (req, res) => {
318630
+ try {
318631
+ await handleRequestInner(req, res);
318632
+ }
318633
+ catch (err) {
318634
+ console.error(`[MCP] Request handler error: ${err}`);
318635
+ if (!res.headersSent) {
318636
+ res.writeHead(500, { 'Content-Type': 'application/json' });
318637
+ res.end(JSON.stringify({ error: 'Internal server error' }));
318638
+ }
318639
+ }
318640
+ };
318641
+ const handleRequestInner = async (req, res) => {
318642
+ // CORS preflight
318643
+ if (req.method === 'OPTIONS') {
318644
+ res.writeHead(204, {
318645
+ 'Access-Control-Allow-Origin': '*',
318646
+ 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
318647
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, mcp-session-id',
318648
+ });
318649
+ res.end();
318650
+ return;
318651
+ }
318652
+ // Auth check on all requests (per MCP spec → OAuth 2.1 § 5.3 → RFC 6750 § 3)
318653
+ if (!validateBearerToken(req, authToken)) {
318654
+ const hasAuthHeader = !!req.headers['authorization'];
318655
+ // RFC 6750 §3.1: invalid_token when a token is present but wrong;
318656
+ // omit error param when no credentials are provided at all.
318657
+ const wwwAuth = hasAuthHeader
318658
+ ? 'Bearer error="invalid_token", error_description="The access token is invalid or expired"'
318659
+ : 'Bearer';
318660
+ res.writeHead(401, {
318661
+ 'WWW-Authenticate': wwwAuth,
318662
+ 'Content-Type': 'application/json',
318663
+ });
318664
+ res.end(JSON.stringify({
318665
+ error: hasAuthHeader ? 'invalid_token' : 'unauthorized',
318666
+ error_description: hasAuthHeader
318667
+ ? 'The access token is invalid or expired'
318668
+ : 'Authentication required. Provide a Bearer token in the Authorization header.',
318669
+ }));
318670
+ return;
318671
+ }
318672
+ // Only serve /mcp endpoint
318673
+ const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
318674
+ if (url.pathname !== '/mcp') {
318675
+ res.writeHead(404);
318676
+ res.end('Not found');
318677
+ return;
318678
+ }
318679
+ const sessionId = req.headers['mcp-session-id'];
318680
+ if (req.method === 'POST' && !sessionId) {
318681
+ // New session — sessionId is assigned during handleRequest (not connect)
318682
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
318683
+ sessionIdGenerator: () => crypto.randomUUID(),
318684
+ });
318685
+ transport.onclose = () => {
318686
+ if (transport.sessionId) {
318687
+ console.error(`[MCP] Session closed: ${transport.sessionId}`);
318688
+ transports.delete(transport.sessionId);
318689
+ }
318690
+ };
318691
+ await server.connect(transport);
318692
+ const body = await readBody(req);
318693
+ await transport.handleRequest(req, res, body);
318694
+ if (transport.sessionId) {
318695
+ transports.set(transport.sessionId, transport);
318696
+ console.error(`[MCP] New session created: ${transport.sessionId}`);
318697
+ }
318698
+ return;
318699
+ }
318700
+ if (sessionId) {
318701
+ const transport = transports.get(sessionId);
318702
+ if (!transport) {
318703
+ console.error(`[MCP] Session ${sessionId} not found, active: [${[...transports.keys()].join(', ')}]`);
318704
+ res.writeHead(404, { 'Content-Type': 'application/json' });
318705
+ res.end(JSON.stringify({ error: 'Session not found' }));
318706
+ return;
318707
+ }
318708
+ if (req.method === 'DELETE') {
318709
+ await transport.handleRequest(req, res);
318710
+ return;
318711
+ }
318712
+ const body = await readBody(req);
318713
+ await transport.handleRequest(req, res, body);
318714
+ return;
318715
+ }
318716
+ // GET without session (standalone SSE stream)
318717
+ if (req.method === 'GET') {
318718
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
318719
+ sessionIdGenerator: () => crypto.randomUUID(),
318720
+ });
318721
+ transport.onclose = () => {
318722
+ if (transport.sessionId)
318723
+ transports.delete(transport.sessionId);
318724
+ };
318725
+ await server.connect(transport);
318726
+ if (transport.sessionId)
318727
+ transports.set(transport.sessionId, transport);
318728
+ await transport.handleRequest(req, res);
318729
+ return;
318730
+ }
318731
+ res.writeHead(405);
318732
+ res.end('Method not allowed');
318733
+ };
318734
+ // Create HTTP or HTTPS server
318735
+ let httpServer;
318736
+ if (options.tlsCert && options.tlsKey) {
318737
+ const cert = fs.readFileSync(options.tlsCert);
318738
+ const key = fs.readFileSync(options.tlsKey);
318739
+ httpServer = https.createServer({ cert, key }, handleRequest);
318740
+ console.error(`Visor MCP server (HTTPS) listening on ${host}:${port}`);
318741
+ }
318742
+ else {
318743
+ httpServer = http.createServer(handleRequest);
318744
+ console.error(`Visor MCP server (HTTP) listening on ${host}:${port}`);
318745
+ if (host !== '127.0.0.1' && host !== 'localhost') {
318746
+ console.error('⚠️ WARNING: Running without TLS on a non-localhost address. Use --tls-cert and --tls-key for production.');
318747
+ }
318748
+ }
318749
+ httpServer.listen(port, host);
318750
+ return {
318751
+ close() {
318752
+ console.error('Shutting down MCP server...');
318753
+ for (const transport of transports.values()) {
318754
+ transport.close();
318755
+ }
318756
+ httpServer.close();
318757
+ },
318758
+ };
318759
+ }
318885
318760
  /**
318886
318761
  * Start the MCP server with Visor tools.
318887
318762
  *
318888
318763
  * The server exposes the following tools:
318889
318764
  * - run_workflow (or custom name): Execute a Visor workflow and return results
318890
318765
  *
318891
- * Communication is via stdio transport (stdin/stdout for JSON-RPC messages).
318766
+ * Supports stdio (default) and HTTP transports.
318892
318767
  *
318893
- * @param options - Optional configuration for fixed workflow mode
318768
+ * @param options - Optional configuration for fixed workflow mode and transport
318894
318769
  */
318895
318770
  async function startMcpServer(options = {}) {
318896
318771
  try {
@@ -318934,9 +318809,18 @@ async function startMcpServer(options = {}) {
318934
318809
  });
318935
318810
  console.error('Visor MCP server started');
318936
318811
  }
318937
- // Connect via stdio transport
318938
- const transport = new stdio_js_1.StdioServerTransport();
318939
- await server.connect(transport);
318812
+ // Connect via selected transport
318813
+ if (options.transport === 'http') {
318814
+ const handle = await startHttpMcpServerInternal(server, options);
318815
+ // Register signal handlers for standalone mode (visor mcp-server subcommand)
318816
+ const shutdown = () => handle.close();
318817
+ process.on('SIGINT', shutdown);
318818
+ process.on('SIGTERM', shutdown);
318819
+ }
318820
+ else {
318821
+ const transport = new stdio_js_1.StdioServerTransport();
318822
+ await server.connect(transport);
318823
+ }
318940
318824
  }
318941
318825
  catch (error) {
318942
318826
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -331230,6 +331114,16 @@ class CustomToolsSSEServer {
331230
331114
  const registry = SessionRegistry.getInstance();
331231
331115
  const activeIds = registry.getActiveSessionIds();
331232
331116
  for (const sid of activeIds) {
331117
+ // Skip the parent/caller session — it must wait for child tool
331118
+ // responses before winding down, otherwise it has empty history
331119
+ // and produces a generic timeout message instead of summarizing
331120
+ // the actual work done by child workflows.
331121
+ if (sid === this.sessionId) {
331122
+ if (this.debug) {
331123
+ logger_1.logger.debug(`[CustomToolsSSEServer:${this.sessionId}] Skipping parent session ${sid} during graceful stop`);
331124
+ }
331125
+ continue;
331126
+ }
331233
331127
  const agent = registry.getSession(sid);
331234
331128
  if (agent && typeof agent.triggerGracefulWindDown === 'function') {
331235
331129
  agent.triggerGracefulWindDown();
@@ -333544,6 +333438,36 @@ class WorkflowCheckProvider extends check_provider_interface_1.CheckProvider {
333544
333438
  }
333545
333439
  }
333546
333440
  }
333441
+ // Resolve `extends` on inline array items (e.g. skills defined directly in args).
333442
+ // loadConfig() already handles extends internally, but inline arrays bypass it.
333443
+ // This allows skills to use `extends: "visor://skills/..."` without loadConfig.
333444
+ for (const [key, value] of Object.entries(inputs)) {
333445
+ if (!Array.isArray(value))
333446
+ continue;
333447
+ let resolved = false;
333448
+ const resolvedArray = value.map((item) => {
333449
+ if (item &&
333450
+ typeof item === 'object' &&
333451
+ !Array.isArray(item) &&
333452
+ typeof item.extends === 'string') {
333453
+ try {
333454
+ const baseConfig = loadConfig(item.extends);
333455
+ const overrideObj = { ...item };
333456
+ delete overrideObj.extends;
333457
+ resolved = true;
333458
+ return deepMerge(baseConfig, overrideObj);
333459
+ }
333460
+ catch (err) {
333461
+ logger_1.logger.warn(`[WorkflowProvider] Failed to resolve extends '${item.extends}' in input '${key}': ${err}`);
333462
+ return item;
333463
+ }
333464
+ }
333465
+ return item;
333466
+ });
333467
+ if (resolved) {
333468
+ inputs[key] = resolvedArray;
333469
+ }
333470
+ }
333547
333471
  // Debug: log all input keys and types for troubleshooting
333548
333472
  const inputSummary = Object.entries(inputs)
333549
333473
  .map(([k, v]) => `${k}:${Array.isArray(v) ? `array[${v.length}]` : typeof v}`)
@@ -334720,6 +334644,768 @@ class PRReviewer {
334720
334644
  exports.PRReviewer = PRReviewer;
334721
334645
 
334722
334646
 
334647
+ /***/ }),
334648
+
334649
+ /***/ 31458:
334650
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
334651
+
334652
+ "use strict";
334653
+
334654
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
334655
+ if (k2 === undefined) k2 = k;
334656
+ var desc = Object.getOwnPropertyDescriptor(m, k);
334657
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
334658
+ desc = { enumerable: true, get: function() { return m[k]; } };
334659
+ }
334660
+ Object.defineProperty(o, k2, desc);
334661
+ }) : (function(o, m, k, k2) {
334662
+ if (k2 === undefined) k2 = k;
334663
+ o[k2] = m[k];
334664
+ }));
334665
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
334666
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
334667
+ }) : function(o, v) {
334668
+ o["default"] = v;
334669
+ });
334670
+ var __importStar = (this && this.__importStar) || (function () {
334671
+ var ownKeys = function(o) {
334672
+ ownKeys = Object.getOwnPropertyNames || function (o) {
334673
+ var ar = [];
334674
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
334675
+ return ar;
334676
+ };
334677
+ return ownKeys(o);
334678
+ };
334679
+ return function (mod) {
334680
+ if (mod && mod.__esModule) return mod;
334681
+ var result = {};
334682
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
334683
+ __setModuleDefault(result, mod);
334684
+ return result;
334685
+ };
334686
+ })();
334687
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
334688
+ exports.McpServerRunner = void 0;
334689
+ const mcp_js_1 = __nccwpck_require__(23886);
334690
+ const streamableHttp_js_1 = __nccwpck_require__(1048);
334691
+ const zod_1 = __nccwpck_require__(50924);
334692
+ const http = __importStar(__nccwpck_require__(58611));
334693
+ const https = __importStar(__nccwpck_require__(65692));
334694
+ const fs = __importStar(__nccwpck_require__(79896));
334695
+ const crypto = __importStar(__nccwpck_require__(76982));
334696
+ const mcp_server_1 = __nccwpck_require__(93143);
334697
+ const trace_helpers_1 = __nccwpck_require__(75338);
334698
+ /**
334699
+ * Read and parse JSON body from an HTTP request.
334700
+ */
334701
+ function readBody(req) {
334702
+ return new Promise((resolve, reject) => {
334703
+ let data = '';
334704
+ req.on('data', (chunk) => (data += chunk));
334705
+ req.on('end', () => {
334706
+ if (!data.trim()) {
334707
+ resolve(undefined);
334708
+ return;
334709
+ }
334710
+ try {
334711
+ resolve(JSON.parse(data));
334712
+ }
334713
+ catch {
334714
+ reject(new Error('Invalid JSON'));
334715
+ }
334716
+ });
334717
+ req.on('error', reject);
334718
+ });
334719
+ }
334720
+ /**
334721
+ * MCP Frontend Runner — exposes the Visor assistant as an MCP tool.
334722
+ *
334723
+ * Unlike the standalone `visor mcp-server` (which calls runChecks from the SDK),
334724
+ * this runner dispatches messages through the StateMachineExecutionEngine just like
334725
+ * the Slack/Telegram runners do. This means `{{ conversation.current.text }}` works,
334726
+ * the full workflow config is used, and responses flow back through the engine.
334727
+ */
334728
+ class McpServerRunner {
334729
+ engine;
334730
+ cfg;
334731
+ options;
334732
+ name = 'mcp';
334733
+ httpServer = null;
334734
+ transports = new Map();
334735
+ taskStore;
334736
+ configPath;
334737
+ constructor(engine, cfg, options) {
334738
+ this.engine = engine;
334739
+ this.cfg = cfg;
334740
+ this.options = options;
334741
+ }
334742
+ async start() {
334743
+ const port = this.options.port || 8080;
334744
+ const host = this.options.host || '0.0.0.0';
334745
+ // Resolve auth token
334746
+ const authToken = this.options.authToken ||
334747
+ (this.options.authTokenEnv ? process.env[this.options.authTokenEnv] : undefined);
334748
+ if (!authToken) {
334749
+ throw new Error('MCP HTTP transport requires an auth token (--mcp-auth-token or VISOR_MCP_AUTH_TOKEN). ' +
334750
+ 'Refusing to start an unauthenticated remote MCP endpoint.');
334751
+ }
334752
+ // Create MCP server with send_message tool
334753
+ const mcpServer = new mcp_js_1.McpServer({ name: mcp_server_1.SERVER_INFO.name, version: mcp_server_1.SERVER_INFO.version }, { capabilities: { tools: {} } });
334754
+ const toolName = this.options.toolName || 'send_message';
334755
+ const toolDescription = this.options.toolDescription ||
334756
+ 'Send a message to the Visor assistant. The assistant will process your message ' +
334757
+ 'through the configured workflow and return a response. Use this for conversations, ' +
334758
+ 'questions, task requests, and any interaction with the assistant.';
334759
+ mcpServer.tool(toolName, toolDescription, {
334760
+ message: zod_1.z.string().describe('The message to send to the assistant.'),
334761
+ session_id: zod_1.z
334762
+ .string()
334763
+ .optional()
334764
+ .describe('Optional conversation session ID for maintaining context across messages. ' +
334765
+ 'If omitted, a new session is created. Re-use the same session_id for follow-up messages.'),
334766
+ }, async (args) => {
334767
+ return this.handleMessage(args.message, args.session_id);
334768
+ });
334769
+ const { transports } = this;
334770
+ const handleRequest = async (req, res) => {
334771
+ try {
334772
+ // CORS preflight
334773
+ if (req.method === 'OPTIONS') {
334774
+ res.writeHead(204, {
334775
+ 'Access-Control-Allow-Origin': '*',
334776
+ 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
334777
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, mcp-session-id',
334778
+ });
334779
+ res.end();
334780
+ return;
334781
+ }
334782
+ // Auth check (per MCP spec → OAuth 2.1 § 5.3 → RFC 6750 § 3)
334783
+ if (!(0, mcp_server_1.validateBearerToken)(req, authToken)) {
334784
+ const hasAuthHeader = !!req.headers['authorization'];
334785
+ const wwwAuth = hasAuthHeader
334786
+ ? 'Bearer error="invalid_token", error_description="The access token is invalid or expired"'
334787
+ : 'Bearer';
334788
+ res.writeHead(401, {
334789
+ 'WWW-Authenticate': wwwAuth,
334790
+ 'Content-Type': 'application/json',
334791
+ });
334792
+ res.end(JSON.stringify({
334793
+ error: hasAuthHeader ? 'invalid_token' : 'unauthorized',
334794
+ error_description: hasAuthHeader
334795
+ ? 'The access token is invalid or expired'
334796
+ : 'Authentication required. Provide a Bearer token in the Authorization header.',
334797
+ }));
334798
+ return;
334799
+ }
334800
+ // Only serve /mcp endpoint
334801
+ const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
334802
+ if (url.pathname !== '/mcp') {
334803
+ res.writeHead(404);
334804
+ res.end('Not found');
334805
+ return;
334806
+ }
334807
+ const sessionId = req.headers['mcp-session-id'];
334808
+ if (req.method === 'POST' && !sessionId) {
334809
+ // New session
334810
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
334811
+ sessionIdGenerator: () => crypto.randomUUID(),
334812
+ });
334813
+ transport.onclose = () => {
334814
+ if (transport.sessionId)
334815
+ transports.delete(transport.sessionId);
334816
+ };
334817
+ await mcpServer.connect(transport);
334818
+ const body = await readBody(req);
334819
+ await transport.handleRequest(req, res, body);
334820
+ if (transport.sessionId)
334821
+ transports.set(transport.sessionId, transport);
334822
+ return;
334823
+ }
334824
+ if (sessionId) {
334825
+ const transport = transports.get(sessionId);
334826
+ if (!transport) {
334827
+ if (req.method === 'DELETE') {
334828
+ // Nothing to close after restart; just acknowledge
334829
+ res.writeHead(200, { 'Content-Type': 'application/json' });
334830
+ res.end(JSON.stringify({ ok: true }));
334831
+ return;
334832
+ }
334833
+ // Session lost (e.g. server restart). Return 404 per MCP spec
334834
+ // so the client re-initializes with a fresh session. Also include
334835
+ // a JSON-RPC error response so clients that parse the body can
334836
+ // detect the "session expired" condition and auto-reconnect.
334837
+ const body = await readBody(req);
334838
+ const requestId = body?.id ?? null;
334839
+ res.writeHead(404, { 'Content-Type': 'application/json' });
334840
+ res.end(JSON.stringify({
334841
+ jsonrpc: '2.0',
334842
+ id: requestId,
334843
+ error: {
334844
+ code: -32000,
334845
+ message: 'Session expired. Please reconnect with a new session.',
334846
+ },
334847
+ }));
334848
+ return;
334849
+ }
334850
+ if (req.method === 'DELETE') {
334851
+ await transport.handleRequest(req, res);
334852
+ return;
334853
+ }
334854
+ const body = await readBody(req);
334855
+ await transport.handleRequest(req, res, body);
334856
+ return;
334857
+ }
334858
+ // GET without session (standalone SSE stream)
334859
+ if (req.method === 'GET') {
334860
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
334861
+ sessionIdGenerator: () => crypto.randomUUID(),
334862
+ });
334863
+ transport.onclose = () => {
334864
+ if (transport.sessionId)
334865
+ transports.delete(transport.sessionId);
334866
+ };
334867
+ await mcpServer.connect(transport);
334868
+ await transport.handleRequest(req, res);
334869
+ if (transport.sessionId)
334870
+ transports.set(transport.sessionId, transport);
334871
+ return;
334872
+ }
334873
+ res.writeHead(405);
334874
+ res.end('Method not allowed');
334875
+ }
334876
+ catch (err) {
334877
+ console.error(`[MCP] Request handler error: ${err}`);
334878
+ if (!res.headersSent) {
334879
+ res.writeHead(500, { 'Content-Type': 'application/json' });
334880
+ res.end(JSON.stringify({ error: 'Internal server error' }));
334881
+ }
334882
+ }
334883
+ };
334884
+ // Create HTTP or HTTPS server
334885
+ if (this.options.tlsCert && this.options.tlsKey) {
334886
+ const cert = fs.readFileSync(this.options.tlsCert);
334887
+ const key = fs.readFileSync(this.options.tlsKey);
334888
+ this.httpServer = https.createServer({ cert, key }, handleRequest);
334889
+ console.error(`Visor MCP frontend (HTTPS) listening on ${host}:${port}`);
334890
+ }
334891
+ else {
334892
+ this.httpServer = http.createServer(handleRequest);
334893
+ console.error(`Visor MCP frontend (HTTP) listening on ${host}:${port}`);
334894
+ if (host !== '127.0.0.1' && host !== 'localhost') {
334895
+ console.error('⚠️ WARNING: Running without TLS on a non-localhost address. Use TLS for production.');
334896
+ }
334897
+ }
334898
+ this.httpServer.listen(port, host);
334899
+ }
334900
+ async stop() {
334901
+ for (const transport of this.transports.values()) {
334902
+ transport.close();
334903
+ }
334904
+ this.transports.clear();
334905
+ if (this.httpServer) {
334906
+ this.httpServer.close();
334907
+ this.httpServer = null;
334908
+ }
334909
+ }
334910
+ updateConfig(cfg) {
334911
+ this.cfg = cfg;
334912
+ }
334913
+ setTaskStore(store, configPath) {
334914
+ this.taskStore = store;
334915
+ this.configPath = configPath;
334916
+ }
334917
+ /**
334918
+ * Handle an incoming message by dispatching it through the engine,
334919
+ * following the same pattern as Slack/Telegram runners.
334920
+ */
334921
+ async handleMessage(message, sessionId) {
334922
+ try {
334923
+ const { StateMachineExecutionEngine: SMEngine } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(39004)));
334924
+ const { createHash } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(76982)));
334925
+ // Create a dedicated engine for this run (same pattern as Slack)
334926
+ const runEngine = new SMEngine();
334927
+ // Propagate execution context from parent engine
334928
+ try {
334929
+ const parentCtx = this.engine.getExecutionContext?.() || {};
334930
+ const prevCtx = runEngine.getExecutionContext?.() || {};
334931
+ runEngine.setExecutionContext?.({
334932
+ ...parentCtx,
334933
+ ...prevCtx,
334934
+ });
334935
+ }
334936
+ catch { }
334937
+ // Build conversation context (same structure as Slack/Telegram)
334938
+ const convSessionId = sessionId || crypto.randomUUID();
334939
+ const now = Date.now();
334940
+ const conversationContext = {
334941
+ transport: 'mcp',
334942
+ thread: { id: convSessionId },
334943
+ current: {
334944
+ user: 'mcp-client',
334945
+ text: message,
334946
+ timestamp: now,
334947
+ },
334948
+ messages: [
334949
+ {
334950
+ role: 'user',
334951
+ text: message,
334952
+ timestamp: now,
334953
+ },
334954
+ ],
334955
+ };
334956
+ // Build webhook payload (same pattern as Slack's payloadForContext)
334957
+ const endpoint = '/bots/mcp/message';
334958
+ const payload = {
334959
+ event: {
334960
+ type: 'message',
334961
+ text: message,
334962
+ user: 'mcp-client',
334963
+ timestamp: now,
334964
+ },
334965
+ mcp_conversation: conversationContext,
334966
+ };
334967
+ const webhookData = new Map();
334968
+ webhookData.set(endpoint, payload);
334969
+ // Get all checks from config
334970
+ const allChecks = Object.keys(this.cfg.checks || {});
334971
+ if (allChecks.length === 0) {
334972
+ return {
334973
+ content: [{ type: 'text', text: 'No checks configured in workflow.' }],
334974
+ isError: true,
334975
+ };
334976
+ }
334977
+ // Prepare config for run
334978
+ const cfgForRun = (() => {
334979
+ try {
334980
+ const cfg = JSON.parse(JSON.stringify(this.cfg));
334981
+ return cfg;
334982
+ }
334983
+ catch {
334984
+ return this.cfg;
334985
+ }
334986
+ })();
334987
+ // Derive stable workspace name from session
334988
+ const hash = createHash('sha256').update(convSessionId).digest('hex').slice(0, 8);
334989
+ if (!cfgForRun.workspace) {
334990
+ cfgForRun.workspace = {};
334991
+ }
334992
+ cfgForRun.workspace.name = `mcp-${hash}`;
334993
+ cfgForRun.workspace.cleanup_on_exit = false;
334994
+ // Execute through the engine, wrapped in OTel tracing
334995
+ const execFn = () => runEngine.executeChecks({
334996
+ checks: allChecks,
334997
+ showDetails: true,
334998
+ outputFormat: 'json',
334999
+ config: cfgForRun,
335000
+ webhookContext: { webhookData, eventType: 'manual' },
335001
+ debug: process.env.VISOR_DEBUG === 'true',
335002
+ });
335003
+ const result = await (0, trace_helpers_1.withVisorRun)({
335004
+ ...(0, trace_helpers_1.getVisorRunAttributes)(),
335005
+ 'visor.run.source': 'mcp',
335006
+ 'mcp.session_id': convSessionId,
335007
+ 'mcp.message_length': message.length,
335008
+ }, {
335009
+ source: 'mcp',
335010
+ workflowId: allChecks.join(','),
335011
+ }, async () => {
335012
+ if (this.taskStore) {
335013
+ const { trackExecution } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(7628)));
335014
+ return trackExecution({
335015
+ taskStore: this.taskStore,
335016
+ source: 'mcp',
335017
+ workflowId: allChecks.join(','),
335018
+ configPath: this.configPath,
335019
+ messageText: message,
335020
+ metadata: { mcp_session: convSessionId },
335021
+ }, execFn);
335022
+ }
335023
+ return execFn();
335024
+ });
335025
+ // Extract response text from results
335026
+ // trackExecution wraps the result in { task, result } — unwrap if needed
335027
+ const rawResult = result?.result ?? result;
335028
+ const responseText = this.extractResponseText(rawResult);
335029
+ return {
335030
+ content: [
335031
+ {
335032
+ type: 'text',
335033
+ text: responseText,
335034
+ },
335035
+ ],
335036
+ };
335037
+ }
335038
+ catch (error) {
335039
+ const errorMessage = error instanceof Error ? error.message : String(error);
335040
+ return {
335041
+ content: [{ type: 'text', text: `Error: ${errorMessage}` }],
335042
+ isError: true,
335043
+ };
335044
+ }
335045
+ }
335046
+ /**
335047
+ * Extract a human-readable response from engine execution results.
335048
+ *
335049
+ * The result from executeChecks() has this structure:
335050
+ * {
335051
+ * repositoryInfo, reviewSummary: { issues, history: Record<checkId, output[]> },
335052
+ * executionStatistics: { checks: [...], groupedResults: { group: CheckResult[] } },
335053
+ * checksExecuted: string[]
335054
+ * }
335055
+ *
335056
+ * The assistant response text is typically in:
335057
+ * 1. reviewSummary.history.<checkId>[last].text (output history from journal)
335058
+ * 2. executionStatistics.groupedResults.<group>[].output.text (grouped check results)
335059
+ */
335060
+ extractResponseText(result) {
335061
+ if (!result)
335062
+ return 'No response from workflow.';
335063
+ // The output history (reviewSummary.history) contains outputs keyed by step ID.
335064
+ // For assistant workflows the steps are e.g. chat.route-intent, chat.build-config,
335065
+ // chat.generate-response. The final AI response is the LAST step that has a .text
335066
+ // field with substantial content (not a short routing label).
335067
+ const history = result?.reviewSummary?.history;
335068
+ if (history && typeof history === 'object') {
335069
+ // Collect all candidate text outputs, keeping the last (deepest) one
335070
+ let bestText = '';
335071
+ for (const [, outputs] of Object.entries(history)) {
335072
+ if (!Array.isArray(outputs))
335073
+ continue;
335074
+ for (const item of outputs) {
335075
+ const text = item?.text ?? item?.output?.text;
335076
+ if (typeof text === 'string' && text.length > bestText.length) {
335077
+ bestText = text;
335078
+ }
335079
+ }
335080
+ }
335081
+ if (bestText)
335082
+ return bestText;
335083
+ }
335084
+ // Grouped results from execution statistics
335085
+ const grouped = result?.executionStatistics?.groupedResults;
335086
+ if (grouped && typeof grouped === 'object') {
335087
+ let bestText = '';
335088
+ for (const checkResults of Object.values(grouped)) {
335089
+ if (!Array.isArray(checkResults))
335090
+ continue;
335091
+ for (const cr of checkResults) {
335092
+ const text = cr?.output?.text ??
335093
+ (typeof cr?.output === 'string' ? cr.output : null) ??
335094
+ (typeof cr?.content === 'string' && cr.content.trim() ? cr.content : null);
335095
+ if (typeof text === 'string' && text.length > bestText.length) {
335096
+ bestText = text;
335097
+ }
335098
+ }
335099
+ }
335100
+ if (bestText)
335101
+ return bestText;
335102
+ }
335103
+ // Direct properties on result
335104
+ if (result?.text)
335105
+ return String(result.text);
335106
+ if (result?.output?.text)
335107
+ return String(result.output.text);
335108
+ // Fallback: JSON dump (compact to avoid huge responses)
335109
+ try {
335110
+ const compact = history || grouped || result;
335111
+ return JSON.stringify(compact, null, 2);
335112
+ }
335113
+ catch {
335114
+ return 'Workflow completed but could not format response.';
335115
+ }
335116
+ }
335117
+ }
335118
+ exports.McpServerRunner = McpServerRunner;
335119
+
335120
+
335121
+ /***/ }),
335122
+
335123
+ /***/ 11866:
335124
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
335125
+
335126
+ "use strict";
335127
+
335128
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
335129
+ if (k2 === undefined) k2 = k;
335130
+ var desc = Object.getOwnPropertyDescriptor(m, k);
335131
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
335132
+ desc = { enumerable: true, get: function() { return m[k]; } };
335133
+ }
335134
+ Object.defineProperty(o, k2, desc);
335135
+ }) : (function(o, m, k, k2) {
335136
+ if (k2 === undefined) k2 = k;
335137
+ o[k2] = m[k];
335138
+ }));
335139
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
335140
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
335141
+ }) : function(o, v) {
335142
+ o["default"] = v;
335143
+ });
335144
+ var __importStar = (this && this.__importStar) || (function () {
335145
+ var ownKeys = function(o) {
335146
+ ownKeys = Object.getOwnPropertyNames || function (o) {
335147
+ var ar = [];
335148
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
335149
+ return ar;
335150
+ };
335151
+ return ownKeys(o);
335152
+ };
335153
+ return function (mod) {
335154
+ if (mod && mod.__esModule) return mod;
335155
+ var result = {};
335156
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
335157
+ __setModuleDefault(result, mod);
335158
+ return result;
335159
+ };
335160
+ })();
335161
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
335162
+ exports.createRunner = createRunner;
335163
+ /**
335164
+ * Create a Runner instance by name.
335165
+ *
335166
+ * Each case encapsulates the config-extraction logic previously duplicated
335167
+ * in cli-main.ts (reading config sub-objects, env vars, defaults).
335168
+ */
335169
+ async function createRunner(name, engine, config, options) {
335170
+ switch (name) {
335171
+ case 'slack': {
335172
+ const { SlackSocketRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(45278)));
335173
+ const slackAny = config.slack || {};
335174
+ return new SlackSocketRunner(engine, config, {
335175
+ appToken: slackAny.app_token || process.env.SLACK_APP_TOKEN,
335176
+ endpoint: slackAny.endpoint || '/bots/slack/support',
335177
+ mentions: slackAny.mentions || 'direct',
335178
+ threads: slackAny.threads || 'any',
335179
+ channel_allowlist: Array.isArray(slackAny.channel_allowlist)
335180
+ ? slackAny.channel_allowlist
335181
+ : [],
335182
+ });
335183
+ }
335184
+ case 'telegram': {
335185
+ const { TelegramPollingRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(33519)));
335186
+ const telegramAny = config.telegram || {};
335187
+ return new TelegramPollingRunner(engine, config, {
335188
+ botToken: telegramAny.bot_token || process.env.TELEGRAM_BOT_TOKEN,
335189
+ pollingTimeout: telegramAny.polling_timeout || 30,
335190
+ chatAllowlist: telegramAny.chat_allowlist,
335191
+ requireMention: telegramAny.require_mention ?? true,
335192
+ workflow: telegramAny.workflow,
335193
+ });
335194
+ }
335195
+ case 'email': {
335196
+ const { EmailPollingRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(66550)));
335197
+ const emailAny = config.email || {};
335198
+ return new EmailPollingRunner(engine, config, {
335199
+ receive: emailAny.receive || {},
335200
+ send: emailAny.send || {},
335201
+ allowlist: emailAny.allowlist,
335202
+ workflow: emailAny.workflow,
335203
+ });
335204
+ }
335205
+ case 'whatsapp': {
335206
+ const { WhatsAppWebhookRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(21718)));
335207
+ const waAny = config.whatsapp || {};
335208
+ return new WhatsAppWebhookRunner(engine, config, {
335209
+ accessToken: waAny.access_token || process.env.WHATSAPP_ACCESS_TOKEN,
335210
+ phoneNumberId: waAny.phone_number_id || process.env.WHATSAPP_PHONE_NUMBER_ID,
335211
+ appSecret: waAny.app_secret || process.env.WHATSAPP_APP_SECRET,
335212
+ verifyToken: waAny.verify_token || process.env.WHATSAPP_VERIFY_TOKEN,
335213
+ apiVersion: waAny.api_version || 'v21.0',
335214
+ port: waAny.port || parseInt(process.env.WHATSAPP_WEBHOOK_PORT || '8443'),
335215
+ host: waAny.host || '0.0.0.0',
335216
+ phoneAllowlist: waAny.phone_allowlist,
335217
+ workflow: waAny.workflow,
335218
+ });
335219
+ }
335220
+ case 'teams': {
335221
+ const { TeamsWebhookRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(86710)));
335222
+ const teamsAny = config.teams || {};
335223
+ return new TeamsWebhookRunner(engine, config, {
335224
+ appId: teamsAny.app_id || process.env.TEAMS_APP_ID,
335225
+ appPassword: teamsAny.app_password || process.env.TEAMS_APP_PASSWORD,
335226
+ tenantId: teamsAny.tenant_id || process.env.TEAMS_TENANT_ID,
335227
+ port: teamsAny.port || parseInt(process.env.TEAMS_WEBHOOK_PORT || '3978'),
335228
+ host: teamsAny.host || '0.0.0.0',
335229
+ userAllowlist: teamsAny.user_allowlist,
335230
+ workflow: teamsAny.workflow,
335231
+ });
335232
+ }
335233
+ case 'a2a': {
335234
+ const { A2AFrontend } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(75887)));
335235
+ const { EventBus } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(83864)));
335236
+ const { logger } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(86999)));
335237
+ const crypto = await Promise.resolve().then(() => __importStar(__nccwpck_require__(76982)));
335238
+ const agentConfig = config.agent_protocol;
335239
+ if (!agentConfig) {
335240
+ throw new Error('agent_protocol configuration is required for --a2a mode');
335241
+ }
335242
+ const frontend = new A2AFrontend(agentConfig);
335243
+ frontend.setEngine(engine);
335244
+ frontend.setVisorConfig(config);
335245
+ // Return a thin adapter that bridges A2AFrontend → Runner interface
335246
+ return {
335247
+ name: 'a2a',
335248
+ async start() {
335249
+ const eventBus = new EventBus();
335250
+ const ctx = {
335251
+ eventBus,
335252
+ logger,
335253
+ config,
335254
+ run: { runId: crypto.randomUUID() },
335255
+ engine,
335256
+ visorConfig: config,
335257
+ };
335258
+ await frontend.start(ctx);
335259
+ const port = agentConfig.port ?? 9000;
335260
+ const host = agentConfig.host ?? '0.0.0.0';
335261
+ console.log(`A2A server running on ${host}:${port}`);
335262
+ },
335263
+ async stop() {
335264
+ await frontend.stop();
335265
+ },
335266
+ updateConfig(cfg) {
335267
+ frontend.setVisorConfig(cfg);
335268
+ },
335269
+ setTaskStore(store, _configPath) {
335270
+ // A2AFrontend receives task store via constructor; update if possible
335271
+ frontend._taskStore = store;
335272
+ },
335273
+ };
335274
+ }
335275
+ case 'mcp': {
335276
+ const { McpServerRunner } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(31458)));
335277
+ const mcpAny = config.mcp_server || {};
335278
+ return new McpServerRunner(engine, config, {
335279
+ port: options.mcpPort ||
335280
+ mcpAny.port ||
335281
+ (process.env.VISOR_MCP_PORT ? parseInt(process.env.VISOR_MCP_PORT) : 8080),
335282
+ host: mcpAny.host || process.env.VISOR_MCP_HOST || '0.0.0.0',
335283
+ authToken: options.mcpAuthToken || mcpAny.auth_token || process.env.VISOR_MCP_AUTH_TOKEN,
335284
+ authTokenEnv: mcpAny.auth_token_env,
335285
+ tlsCert: mcpAny.tls_cert || process.env.VISOR_MCP_TLS_CERT,
335286
+ tlsKey: mcpAny.tls_key || process.env.VISOR_MCP_TLS_KEY,
335287
+ toolName: mcpAny.tool_name || process.env.VISOR_MCP_TOOL_NAME,
335288
+ toolDescription: mcpAny.tool_description || process.env.VISOR_MCP_TOOL_DESCRIPTION,
335289
+ });
335290
+ }
335291
+ default:
335292
+ throw new Error(`Unknown runner type: "${name}"`);
335293
+ }
335294
+ }
335295
+
335296
+
335297
+ /***/ }),
335298
+
335299
+ /***/ 89736:
335300
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
335301
+
335302
+ "use strict";
335303
+
335304
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
335305
+ exports.RunnerHost = void 0;
335306
+ const logger_1 = __nccwpck_require__(86999);
335307
+ /**
335308
+ * Orchestrates the lifecycle of multiple Runner instances running in parallel.
335309
+ *
335310
+ * Handles start/stop ordering, config broadcast, and shared task-store propagation.
335311
+ */
335312
+ class RunnerHost {
335313
+ runners = [];
335314
+ /** Register a runner to be managed by this host. */
335315
+ addRunner(runner) {
335316
+ this.runners.push(runner);
335317
+ }
335318
+ /** Return the list of registered runners. */
335319
+ getRunners() {
335320
+ return this.runners;
335321
+ }
335322
+ /**
335323
+ * Start all registered runners concurrently.
335324
+ * If any runner fails to start, previously-started runners are stopped.
335325
+ */
335326
+ async startAll() {
335327
+ const started = [];
335328
+ const results = await Promise.allSettled(this.runners.map(async (runner) => {
335329
+ await runner.start();
335330
+ started.push(runner);
335331
+ }));
335332
+ const failures = results.filter(r => r.status === 'rejected');
335333
+ if (failures.length > 0) {
335334
+ // Rollback: stop successfully-started runners
335335
+ await Promise.allSettled(started.map(r => r.stop()));
335336
+ const msgs = failures.map(f => f.reason?.message ?? String(f.reason));
335337
+ throw new Error(`Failed to start runner(s): ${msgs.join('; ')}`);
335338
+ }
335339
+ }
335340
+ /**
335341
+ * Stop all registered runners concurrently.
335342
+ * Tolerates individual stop failures (logs warnings).
335343
+ */
335344
+ async stopAll() {
335345
+ const results = await Promise.allSettled(this.runners.map(r => r.stop()));
335346
+ for (let i = 0; i < results.length; i++) {
335347
+ if (results[i].status === 'rejected') {
335348
+ const reason = results[i].reason;
335349
+ logger_1.logger.warn(`[RunnerHost] Failed to stop runner "${this.runners[i].name}": ${reason}`);
335350
+ }
335351
+ }
335352
+ }
335353
+ /** Broadcast a config update to all runners. */
335354
+ broadcastConfigUpdate(cfg) {
335355
+ for (const runner of this.runners) {
335356
+ runner.updateConfig(cfg);
335357
+ }
335358
+ }
335359
+ /** Propagate a shared task store to all runners that support it. */
335360
+ setTaskStore(store, configPath) {
335361
+ for (const runner of this.runners) {
335362
+ if (runner.setTaskStore) {
335363
+ runner.setTaskStore(store, configPath);
335364
+ }
335365
+ }
335366
+ }
335367
+ }
335368
+ exports.RunnerHost = RunnerHost;
335369
+
335370
+
335371
+ /***/ }),
335372
+
335373
+ /***/ 84763:
335374
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
335375
+
335376
+ "use strict";
335377
+
335378
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
335379
+ exports.initTelemetryFromConfig = initTelemetryFromConfig;
335380
+ const opentelemetry_1 = __nccwpck_require__(6248);
335381
+ /**
335382
+ * Initialize telemetry from a Visor config.
335383
+ *
335384
+ * Extracts the telemetry block (if any) and merges it with environment
335385
+ * variable overrides, then calls the shared `initTelemetry()` helper.
335386
+ * This replaces the ~20-line block that was duplicated per-frontend.
335387
+ */
335388
+ async function initTelemetryFromConfig(config) {
335389
+ const t = config?.telemetry;
335390
+ if (t) {
335391
+ await (0, opentelemetry_1.initTelemetry)({
335392
+ enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true' || !!t.enabled,
335393
+ sink: process.env.VISOR_TELEMETRY_SINK || t.sink || 'file',
335394
+ file: { dir: process.env.VISOR_TRACE_DIR || t.file?.dir, ndjson: !!t.file?.ndjson },
335395
+ autoInstrument: !!t.tracing?.auto_instrumentations,
335396
+ traceReport: !!t.tracing?.trace_report?.enabled,
335397
+ });
335398
+ }
335399
+ else {
335400
+ await (0, opentelemetry_1.initTelemetry)({
335401
+ enabled: process.env.VISOR_TELEMETRY_ENABLED === 'true',
335402
+ sink: process.env.VISOR_TELEMETRY_SINK || 'file',
335403
+ file: { dir: process.env.VISOR_TRACE_DIR },
335404
+ });
335405
+ }
335406
+ }
335407
+
335408
+
334723
335409
  /***/ }),
334724
335410
 
334725
335411
  /***/ 11207:
@@ -342772,6 +343458,7 @@ const workspace_manager_1 = __nccwpck_require__(6134);
342772
343458
  const message_trigger_1 = __nccwpck_require__(71983);
342773
343459
  const types_1 = __nccwpck_require__(83506);
342774
343460
  class SlackSocketRunner {
343461
+ name = 'slack';
342775
343462
  appToken;
342776
343463
  endpoint;
342777
343464
  mentions = 'direct';
@@ -348014,11 +348701,13 @@ async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
348014
348701
  let templateContent;
348015
348702
  if (checkConfig.template && checkConfig.template.content) {
348016
348703
  templateContent = String(checkConfig.template.content);
348704
+ logger_1.logger.debug(`[TemplateRenderer] Using inline template for ${checkId}`);
348017
348705
  }
348018
348706
  else if (checkConfig.template && checkConfig.template.file) {
348019
348707
  const file = String(checkConfig.template.file);
348020
348708
  const resolved = path.resolve(process.cwd(), file);
348021
348709
  templateContent = await fs.readFile(resolved, 'utf-8');
348710
+ logger_1.logger.debug(`[TemplateRenderer] Using template file for ${checkId}: ${resolved}`);
348022
348711
  }
348023
348712
  else if (schema && schema !== 'plain') {
348024
348713
  const sanitized = String(schema).replace(/[^a-zA-Z0-9-]/g, '');
@@ -348034,13 +348723,18 @@ async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
348034
348723
  for (const p of candidatePaths) {
348035
348724
  try {
348036
348725
  templateContent = await fs.readFile(p, 'utf-8');
348037
- if (templateContent)
348726
+ if (templateContent) {
348727
+ logger_1.logger.debug(`[TemplateRenderer] Using schema template for ${checkId}: ${p}`);
348038
348728
  break;
348729
+ }
348039
348730
  }
348040
348731
  catch {
348041
348732
  // try next
348042
348733
  }
348043
348734
  }
348735
+ if (!templateContent) {
348736
+ logger_1.logger.warn(`[TemplateRenderer] No template found for schema '${sanitized}' in ${checkId}. __dirname=${__dirname}, cwd=${process.cwd()}, tried ${candidatePaths.length} paths: ${candidatePaths.join(', ')}`);
348737
+ }
348044
348738
  }
348045
348739
  }
348046
348740
  if (!templateContent)
@@ -348071,8 +348765,13 @@ async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
348071
348765
  checkName: checkId,
348072
348766
  output,
348073
348767
  };
348768
+ logger_1.logger.debug(`[TemplateRenderer] Rendering template for ${checkId} with output type=${typeof output}, keys=${output && typeof output === 'object' ? Object.keys(output).join(',') : 'n/a'}`);
348074
348769
  const rendered = await liquid.parseAndRender(templateContent, templateData);
348075
- return rendered.trim();
348770
+ const trimmed = rendered.trim();
348771
+ if (!trimmed) {
348772
+ logger_1.logger.warn(`[TemplateRenderer] Template rendered EMPTY for ${checkId}. output=${JSON.stringify(output)?.substring(0, 200)}`);
348773
+ }
348774
+ return trimmed;
348076
348775
  }
348077
348776
  catch (error) {
348078
348777
  const msg = error instanceof Error ? error.message : String(error);
@@ -348561,8 +349260,11 @@ class StateMachineRunner {
348561
349260
  const allIssues = [];
348562
349261
  let debug = undefined;
348563
349262
  if (checkConfig.forEach && entries.length > 1) {
348564
- // Aggregate all forEach iterations
349263
+ // Aggregate all forEach iterations.
349264
+ // forEach producers have both an unscoped entry (with the full array output)
349265
+ // and scoped per-item entries. Prefer the unscoped entry's output.
348565
349266
  const contents = [];
349267
+ let unscopedOutput = undefined;
348566
349268
  for (const entry of entries) {
348567
349269
  if (entry.result.content) {
348568
349270
  contents.push(entry.result.content);
@@ -348573,19 +349275,29 @@ class StateMachineRunner {
348573
349275
  if (entry.result.debug) {
348574
349276
  debug = entry.result.debug;
348575
349277
  }
348576
- // For forEach, output is typically an array from the last iteration
348577
- if (entry.result.output !== undefined) {
349278
+ // Prefer the unscoped (scope=[]) entry which has the full array output
349279
+ if (entry.scope.length === 0 && entry.result.output !== undefined) {
349280
+ unscopedOutput = entry.result.output;
349281
+ }
349282
+ if (entry.result.output !== undefined && unscopedOutput === undefined) {
348578
349283
  output = entry.result.output;
348579
349284
  }
348580
349285
  }
349286
+ output = unscopedOutput !== undefined ? unscopedOutput : output;
348581
349287
  content = contents.join('\n');
348582
349288
  }
348583
349289
  else {
348584
- // For non-forEach or single-entry forEach, take the latest entry
349290
+ // For non-forEach or single-entry forEach, take the latest entry.
349291
+ // For forEach consumers (aggregated results from executeCheckWithForEachItems),
349292
+ // the aggregated result stores per-item outputs in .forEachItems instead of .output.
348585
349293
  const latestEntry = entries[entries.length - 1];
348586
349294
  if (latestEntry) {
348587
349295
  content = latestEntry.result.content || '';
348588
349296
  output = latestEntry.result.output;
349297
+ // Fall back to forEachItems for aggregated forEach consumer results
349298
+ if (output === undefined && latestEntry.result.forEachItems) {
349299
+ output = { forEachItems: latestEntry.result.forEachItems };
349300
+ }
348589
349301
  if (latestEntry.result.issues) {
348590
349302
  allIssues.push(...latestEntry.result.issues);
348591
349303
  }
@@ -349506,11 +350218,12 @@ async function executeCheckWithForEachItems(checkId, forEachParent, forEachItems
349506
350218
  logger_1.logger.info(`[LevelDispatch] webhookContext: ${webhookCtx ? 'present' : 'absent'}, webhookData size: ${webhookData?.size || 0}`);
349507
350219
  }
349508
350220
  if (webhookData && webhookData.size > 0) {
349509
- // Find the payload with slack_conversation or telegram_conversation
350221
+ // Find the payload with slack_conversation, telegram_conversation, or mcp_conversation
349510
350222
  for (const payload of webhookData.values()) {
349511
350223
  const slackConv = payload?.slack_conversation;
349512
350224
  const telegramConv = payload?.telegram_conversation;
349513
- const conv = slackConv || telegramConv;
350225
+ const mcpConv = payload?.mcp_conversation;
350226
+ const conv = slackConv || telegramConv || mcpConv;
349514
350227
  if (conv) {
349515
350228
  const event = payload?.event;
349516
350229
  const messageCount = Array.isArray(conv?.messages) ? conv.messages.length : 0;
@@ -349520,10 +350233,12 @@ async function executeCheckWithForEachItems(checkId, forEachParent, forEachItems
349520
350233
  // Build transport-specific context
349521
350234
  const transportCtx = slackConv
349522
350235
  ? { slack: { event: event || {}, conversation: slackConv } }
349523
- : {
349524
- telegram: { event: event || {}, conversation: telegramConv },
349525
- webhook: payload,
349526
- };
350236
+ : telegramConv
350237
+ ? {
350238
+ telegram: { event: event || {}, conversation: telegramConv },
350239
+ webhook: payload,
350240
+ }
350241
+ : { mcp: { event: event || {}, conversation: mcpConv }, webhook: payload };
349527
350242
  providerConfig.eventContext = {
349528
350243
  ...providerConfig.eventContext,
349529
350244
  ...transportCtx,
@@ -350744,11 +351459,12 @@ async function executeSingleCheck(checkId, context, state, emitEvent, transition
350744
351459
  logger_1.logger.info(`[LevelDispatch] webhookContext: ${webhookCtx ? 'present' : 'absent'}, webhookData size: ${webhookData?.size || 0}`);
350745
351460
  }
350746
351461
  if (webhookData && webhookData.size > 0) {
350747
- // Find the payload with slack_conversation or telegram_conversation
351462
+ // Find the payload with slack_conversation, telegram_conversation, or mcp_conversation
350748
351463
  for (const payload of webhookData.values()) {
350749
351464
  const slackConv = payload?.slack_conversation;
350750
351465
  const telegramConv = payload?.telegram_conversation;
350751
- const conv = slackConv || telegramConv;
351466
+ const mcpConv = payload?.mcp_conversation;
351467
+ const conv = slackConv || telegramConv || mcpConv;
350752
351468
  if (conv) {
350753
351469
  const event = payload?.event;
350754
351470
  const messageCount = Array.isArray(conv?.messages) ? conv.messages.length : 0;
@@ -354699,6 +355415,7 @@ const client_1 = __nccwpck_require__(55391);
354699
355415
  const adapter_1 = __nccwpck_require__(87917);
354700
355416
  const workspace_manager_1 = __nccwpck_require__(6134);
354701
355417
  class TeamsWebhookRunner {
355418
+ name = 'teams';
354702
355419
  client;
354703
355420
  adapter;
354704
355421
  engine;
@@ -355446,6 +356163,7 @@ const crypto_1 = __nccwpck_require__(76982);
355446
356163
  const runner_1 = __nccwpck_require__(23575);
355447
356164
  const workspace_manager_1 = __nccwpck_require__(6134);
355448
356165
  class TelegramPollingRunner {
356166
+ name = 'telegram';
355449
356167
  client;
355450
356168
  adapter;
355451
356169
  engine;
@@ -372780,6 +373498,7 @@ const client_1 = __nccwpck_require__(73407);
372780
373498
  const adapter_1 = __nccwpck_require__(1421);
372781
373499
  const workspace_manager_1 = __nccwpck_require__(6134);
372782
373500
  class WhatsAppWebhookRunner {
373501
+ name = 'whatsapp';
372783
373502
  client;
372784
373503
  adapter;
372785
373504
  engine;
@@ -494638,7 +495357,22 @@ function mapFlowchartParserError(err, text) {
494638
495357
  const lineContent = allLines[Math.max(0, line - 1)] || "";
494639
495358
  const beforeQuote = lineContent.slice(0, Math.max(0, column - 1));
494640
495359
  const hasLinkBefore = beforeQuote.match(/--\s*$|==\s*$|-\.\s*$|-\.-\s*$|\[\s*$/);
495360
+ const caret0 = Math.max(0, column - 1);
495361
+ const firstBar = lineContent.lastIndexOf("|", caret0);
495362
+ const secondBar = firstBar >= 0 ? lineContent.indexOf("|", caret0 + 1) : -1;
495363
+ const inPipeLabel = firstBar >= 0 && secondBar > firstBar && firstBar < caret0 && secondBar > caret0;
494641
495364
  if (inLinkRule || hasLinkBefore) {
495365
+ if (tokType === "QuotedString" && inPipeLabel) {
495366
+ return {
495367
+ line,
495368
+ column,
495369
+ severity: "error",
495370
+ code: "FL-EDGE-LABEL-QUOTE-IN-PIPES",
495371
+ message: "Quotes are not supported inside pipe-delimited edge labels.",
495372
+ hint: "Use &quot; inside |...|, e.g., --|e.g. &quot;navigate to example.com&quot;|-->",
495373
+ length: len
495374
+ };
495375
+ }
494642
495376
  if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
494643
495377
  return {
494644
495378
  line,
@@ -495240,17 +495974,6 @@ ${br.example}`,
495240
495974
  if (inRule("arrow") && err.name === "NoViableAltException") {
495241
495975
  return { line, column, severity: "error", code: "SE-ARROW-INVALID", message: `Invalid sequence arrow near '${found}'.`, hint: "Use ->, -->, ->>, -->>, -x, --x, -), --), <<->>, or <<-->>", length: len };
495242
495976
  }
495243
- if ((err.name === "NoViableAltException" || err.name === "MismatchedTokenException") && tokType === "Minus") {
495244
- return {
495245
- line,
495246
- column,
495247
- severity: "error",
495248
- code: "SE-BULLET-LINE-UNSUPPORTED",
495249
- message: "Bullet list lines starting with '-' are not supported in sequence diagrams.",
495250
- hint: "Wrap free\u2011form text in a note block instead, for example:\nNote over A : Item 1\nNote over A\n - Item 1\n - Item 2\nend note",
495251
- length: len
495252
- };
495253
- }
495254
495977
  if (inRule("noteStmt")) {
495255
495978
  if (err.name === "MismatchedTokenException" && exp("Colon")) {
495256
495979
  return { line, column, severity: "error", code: "SE-NOTE-MALFORMED", message: "Malformed note: missing colon before the note text.", hint: "Example: Note right of Alice: Hello", length: len };
@@ -495259,6 +495982,22 @@ ${br.example}`,
495259
495982
  return { line, column, severity: "error", code: "SE-NOTE-MALFORMED", message: "Malformed note statement. Use left|right of X or over X[,Y]: text", hint: "Examples: Note over A,B: hi", length: len };
495260
495983
  }
495261
495984
  }
495985
+ if ((err.name === "NoViableAltException" || err.name === "MismatchedTokenException") && tokType === "Minus") {
495986
+ const nonWs = ltxt.search(/\S/);
495987
+ const minusAtLineStart = nonWs >= 0 && ltxt[nonWs] === "-" && column === nonWs + 1;
495988
+ if (!minusAtLineStart) {
495989
+ } else {
495990
+ return {
495991
+ line,
495992
+ column,
495993
+ severity: "error",
495994
+ code: "SE-BULLET-LINE-UNSUPPORTED",
495995
+ message: "Bullet list lines starting with '-' are not supported in sequence diagrams.",
495996
+ hint: "Wrap free\u2011form text in a note block instead, for example:\nNote over A : Item 1\nNote over A\n - Item 1\n - Item 2\nend note",
495997
+ length: len
495998
+ };
495999
+ }
496000
+ }
495262
496001
  if (tokType === "ElseKeyword" && isInRule(err, "criticalBlock")) {
495263
496002
  return {
495264
496003
  line,
@@ -496058,7 +496797,7 @@ var Identifier2, NumberLiteral3, SequenceKeyword, ParticipantKeyword, ActorKeywo
496058
496797
  var init_lexer4 = __esm({
496059
496798
  "node_modules/@probelabs/maid/out/diagrams/sequence/lexer.js"() {
496060
496799
  init_api6();
496061
- Identifier2 = createToken({ name: "Identifier", pattern: /[A-Za-z_][A-Za-z0-9_]*/ });
496800
+ Identifier2 = createToken({ name: "Identifier", pattern: /[A-Za-z_][A-Za-z0-9_.]*(?:-(?![>x)\s])[A-Za-z0-9_.]+)*/ });
496062
496801
  NumberLiteral3 = createToken({ name: "NumberLiteral", pattern: /[0-9]+/ });
496063
496802
  SequenceKeyword = createToken({ name: "SequenceKeyword", pattern: /sequenceDiagram/, longer_alt: Identifier2 });
496064
496803
  ParticipantKeyword = createToken({ name: "ParticipantKeyword", pattern: /participant/i, longer_alt: Identifier2 });
@@ -497878,6 +498617,135 @@ var init_validate5 = __esm({
497878
498617
  }
497879
498618
  });
497880
498619
 
498620
+ // node_modules/@probelabs/maid/out/core/frontmatter.js
498621
+ function parseFrontmatter(input) {
498622
+ const text = input.startsWith("\uFEFF") ? input.slice(1) : input;
498623
+ const lines = text.split(/\r?\n/);
498624
+ if (lines.length < 3 || lines[0].trim() !== "---")
498625
+ return null;
498626
+ let i = 1;
498627
+ const block = [];
498628
+ while (i < lines.length && lines[i].trim() !== "---") {
498629
+ block.push(lines[i]);
498630
+ i++;
498631
+ }
498632
+ if (i >= lines.length)
498633
+ return null;
498634
+ const body = lines.slice(i + 1).join("\n");
498635
+ const bodyStartLine = i + 2;
498636
+ const raw = block.join("\n");
498637
+ const config2 = {};
498638
+ const themeVars = {};
498639
+ let themeUnderConfig = false;
498640
+ let ctx = "root";
498641
+ for (const line of block) {
498642
+ if (!line.trim())
498643
+ continue;
498644
+ const indent = line.match(/^\s*/)?.[0].length ?? 0;
498645
+ const mKey = line.match(/^\s*([A-Za-z0-9_\-]+):\s*(.*)$/);
498646
+ if (!mKey)
498647
+ continue;
498648
+ const key = mKey[1];
498649
+ let value = mKey[2] || "";
498650
+ if (indent === 0) {
498651
+ if (key === "config") {
498652
+ ctx = "config";
498653
+ continue;
498654
+ }
498655
+ if (key === "themeVariables") {
498656
+ ctx = "theme";
498657
+ continue;
498658
+ }
498659
+ ctx = "root";
498660
+ continue;
498661
+ }
498662
+ if (ctx === "config") {
498663
+ if (indent <= 2 && key !== "pie" && key !== "themeVariables")
498664
+ continue;
498665
+ if (key === "pie") {
498666
+ ctx = "config.pie";
498667
+ ensure(config2, "pie", {});
498668
+ continue;
498669
+ }
498670
+ if (key === "themeVariables") {
498671
+ ctx = "theme";
498672
+ themeUnderConfig = true;
498673
+ continue;
498674
+ }
498675
+ continue;
498676
+ }
498677
+ if (ctx === "config.pie") {
498678
+ if (indent < 4) {
498679
+ if (key === "pie") {
498680
+ ctx = "config.pie";
498681
+ ensure(config2, "pie", {});
498682
+ continue;
498683
+ }
498684
+ if (key === "themeVariables") {
498685
+ ctx = "theme";
498686
+ themeUnderConfig = true;
498687
+ continue;
498688
+ }
498689
+ ctx = "config";
498690
+ continue;
498691
+ }
498692
+ setKV(config2.pie, key, value);
498693
+ continue;
498694
+ }
498695
+ if (ctx === "theme") {
498696
+ if (indent < 2) {
498697
+ ctx = "root";
498698
+ continue;
498699
+ }
498700
+ setKV(themeVars, key, value);
498701
+ continue;
498702
+ }
498703
+ }
498704
+ if (themeUnderConfig && Object.keys(themeVars).length) {
498705
+ ensure(config2, "themeVariables", {});
498706
+ Object.assign(config2.themeVariables, themeVars);
498707
+ }
498708
+ return {
498709
+ raw,
498710
+ body,
498711
+ bodyStartLine,
498712
+ config: Object.keys(config2).length ? config2 : void 0,
498713
+ themeVariables: Object.keys(themeVars).length ? themeVars : void 0
498714
+ };
498715
+ }
498716
+ function ensure(obj, key, def) {
498717
+ if (obj[key] == null)
498718
+ obj[key] = def;
498719
+ }
498720
+ function unquote(val) {
498721
+ const v = val.trim();
498722
+ if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
498723
+ return v.slice(1, -1);
498724
+ }
498725
+ return v;
498726
+ }
498727
+ function setKV(target, key, rawValue) {
498728
+ const v = unquote(rawValue);
498729
+ if (v === "") {
498730
+ target[key] = "";
498731
+ return;
498732
+ }
498733
+ const num = Number(v);
498734
+ if (!Number.isNaN(num) && /^-?[0-9]+(\.[0-9]+)?$/.test(v)) {
498735
+ target[key] = num;
498736
+ return;
498737
+ }
498738
+ if (/^(true|false)$/i.test(v)) {
498739
+ target[key] = /^true$/i.test(v);
498740
+ return;
498741
+ }
498742
+ target[key] = v;
498743
+ }
498744
+ var init_frontmatter = __esm({
498745
+ "node_modules/@probelabs/maid/out/core/frontmatter.js"() {
498746
+ }
498747
+ });
498748
+
497881
498749
  // node_modules/@probelabs/maid/out/core/router.js
497882
498750
  function firstNonCommentLine(text) {
497883
498751
  const lines = text.split(/\r?\n/);
@@ -497892,7 +498760,8 @@ function firstNonCommentLine(text) {
497892
498760
  return void 0;
497893
498761
  }
497894
498762
  function detectDiagramType(text) {
497895
- const header = firstNonCommentLine(text);
498763
+ const { content } = stripFrontmatter(text);
498764
+ const header = firstNonCommentLine(content);
497896
498765
  if (!header)
497897
498766
  return "unknown";
497898
498767
  if (/^(flowchart|graph)\b/i.test(header))
@@ -497944,20 +498813,26 @@ function isOtherMermaidDiagram(headerLine) {
497944
498813
  return OTHER.has(t);
497945
498814
  }
497946
498815
  function validate(text, options = {}) {
497947
- const type = detectDiagramType(text);
498816
+ const { content, lineOffset } = stripFrontmatter(text);
498817
+ const type = detectDiagramType(content);
498818
+ const withOffset = (errors) => {
498819
+ if (lineOffset === 0)
498820
+ return errors;
498821
+ return errors.map((e) => ({ ...e, line: Math.max(1, (e.line || 1) + lineOffset) }));
498822
+ };
497948
498823
  switch (type) {
497949
498824
  case "flowchart":
497950
- return { type, errors: validateFlowchart(text, options) };
498825
+ return { type, errors: withOffset(validateFlowchart(content, options)) };
497951
498826
  case "pie":
497952
- return { type, errors: validatePie(text, options) };
498827
+ return { type, errors: withOffset(validatePie(content, options)) };
497953
498828
  case "sequence":
497954
- return { type, errors: validateSequence(text, options) };
498829
+ return { type, errors: withOffset(validateSequence(content, options)) };
497955
498830
  case "class":
497956
- return { type, errors: validateClass(text, options) };
498831
+ return { type, errors: withOffset(validateClass(content, options)) };
497957
498832
  case "state":
497958
- return { type, errors: validateState(text, options) };
498833
+ return { type, errors: withOffset(validateState(content, options)) };
497959
498834
  default:
497960
- const header = firstNonCommentLine(text);
498835
+ const header = firstNonCommentLine(content);
497961
498836
  if (isOtherMermaidDiagram(header)) {
497962
498837
  return { type, errors: [] };
497963
498838
  }
@@ -497965,7 +498840,7 @@ function validate(text, options = {}) {
497965
498840
  type,
497966
498841
  errors: [
497967
498842
  {
497968
- line: 1,
498843
+ line: lineOffset + 1,
497969
498844
  column: 1,
497970
498845
  message: 'Diagram must start with "graph", "flowchart", "pie", "sequenceDiagram", "classDiagram" or "stateDiagram[-v2]"',
497971
498846
  severity: "error",
@@ -497976,6 +498851,12 @@ function validate(text, options = {}) {
497976
498851
  };
497977
498852
  }
497978
498853
  }
498854
+ function stripFrontmatter(text) {
498855
+ const fm = parseFrontmatter(text);
498856
+ if (!fm)
498857
+ return { content: text, lineOffset: 0 };
498858
+ return { content: fm.body, lineOffset: fm.bodyStartLine - 1 };
498859
+ }
497979
498860
  var init_router = __esm({
497980
498861
  "node_modules/@probelabs/maid/out/core/router.js"() {
497981
498862
  init_validate();
@@ -497983,6 +498864,7 @@ var init_router = __esm({
497983
498864
  init_validate3();
497984
498865
  init_validate4();
497985
498866
  init_validate5();
498867
+ init_frontmatter();
497986
498868
  }
497987
498869
  });
497988
498870
 
@@ -498194,16 +499076,18 @@ function computeFixes(text, errors, level = "safe") {
498194
499076
  }
498195
499077
  continue;
498196
499078
  }
498197
- if (is("FL-EDGE-LABEL-BRACKET", e) || is("FL-EDGE-LABEL-CURLY-IN-PIPES", e)) {
499079
+ if (is("FL-EDGE-LABEL-BRACKET", e) || is("FL-EDGE-LABEL-CURLY-IN-PIPES", e) || is("FL-EDGE-LABEL-QUOTE-IN-PIPES", e)) {
498198
499080
  const lineText = lineTextAt(text, e.line);
498199
- const firstBar = lineText.indexOf("|");
498200
- const secondBar = firstBar >= 0 ? lineText.indexOf("|", firstBar + 1) : -1;
499081
+ const col = Math.max(0, e.column - 1);
499082
+ const firstBar = lineText.lastIndexOf("|", col);
499083
+ const secondBar = firstBar >= 0 ? lineText.indexOf("|", col + 1) : -1;
498201
499084
  if (firstBar >= 0 && secondBar > firstBar) {
498202
499085
  const before = lineText.slice(0, firstBar + 1);
498203
499086
  const label = lineText.slice(firstBar + 1, secondBar);
498204
499087
  const after = lineText.slice(secondBar);
498205
499088
  let fixedLabel = label.replace(/\[/g, "&#91;").replace(/\]/g, "&#93;");
498206
499089
  fixedLabel = fixedLabel.replace(/\{/g, "&#123;").replace(/\}/g, "&#125;");
499090
+ fixedLabel = fixedLabel.replace(/\\"/g, "&quot;").replace(/"/g, "&quot;");
498207
499091
  const fixedLine = before + fixedLabel + after;
498208
499092
  const finalLine = fixedLine.replace(/\[([^\]]*)\]/g, (m, seg) => "[" + String(seg).replace(/`/g, "") + "]");
498209
499093
  edits.push({ start: { line: e.line, column: 1 }, end: { line: e.line, column: lineText.length + 1 }, newText: finalLine });
@@ -509593,7 +510477,7 @@ ${overlay}</g>`;
509593
510477
  });
509594
510478
 
509595
510479
  // node_modules/@probelabs/maid/out/renderer/pie-builder.js
509596
- function unquote(s) {
510480
+ function unquote2(s) {
509597
510481
  if (!s)
509598
510482
  return s;
509599
510483
  const first2 = s.charAt(0);
@@ -509643,7 +510527,7 @@ function buildPieModel(text) {
509643
510527
  const collect = (k) => {
509644
510528
  const arr = tnode.children?.[k] ?? [];
509645
510529
  for (const tok of arr)
509646
- parts.push(unquote(tok.image));
510530
+ parts.push(unquote2(tok.image));
509647
510531
  };
509648
510532
  collect("QuotedString");
509649
510533
  collect("Text");
@@ -509656,7 +510540,7 @@ function buildPieModel(text) {
509656
510540
  const labelTok = snode.children?.sliceLabel?.[0]?.children?.QuotedString?.[0];
509657
510541
  const numTok = snode.children?.NumberLiteral?.[0];
509658
510542
  if (labelTok && numTok) {
509659
- const label = unquote(labelTok.image).trim();
510543
+ const label = unquote2(labelTok.image).trim();
509660
510544
  const value = Number(numTok.image);
509661
510545
  if (!Number.isNaN(value)) {
509662
510546
  model.slices.push({ label, value });
@@ -510812,128 +511696,6 @@ var init_state_renderer = __esm({
510812
511696
  }
510813
511697
  });
510814
511698
 
510815
- // node_modules/@probelabs/maid/out/core/frontmatter.js
510816
- function parseFrontmatter(input) {
510817
- const text = input.startsWith("\uFEFF") ? input.slice(1) : input;
510818
- const lines = text.split(/\r?\n/);
510819
- if (lines.length < 3 || lines[0].trim() !== "---")
510820
- return null;
510821
- let i = 1;
510822
- const block = [];
510823
- while (i < lines.length && lines[i].trim() !== "---") {
510824
- block.push(lines[i]);
510825
- i++;
510826
- }
510827
- if (i >= lines.length)
510828
- return null;
510829
- const body = lines.slice(i + 1).join("\n");
510830
- const raw = block.join("\n");
510831
- const config2 = {};
510832
- const themeVars = {};
510833
- let themeUnderConfig = false;
510834
- let ctx = "root";
510835
- for (const line of block) {
510836
- if (!line.trim())
510837
- continue;
510838
- const indent = line.match(/^\s*/)?.[0].length ?? 0;
510839
- const mKey = line.match(/^\s*([A-Za-z0-9_\-]+):\s*(.*)$/);
510840
- if (!mKey)
510841
- continue;
510842
- const key = mKey[1];
510843
- let value = mKey[2] || "";
510844
- if (indent === 0) {
510845
- if (key === "config") {
510846
- ctx = "config";
510847
- continue;
510848
- }
510849
- if (key === "themeVariables") {
510850
- ctx = "theme";
510851
- continue;
510852
- }
510853
- ctx = "root";
510854
- continue;
510855
- }
510856
- if (ctx === "config") {
510857
- if (indent <= 2 && key !== "pie" && key !== "themeVariables")
510858
- continue;
510859
- if (key === "pie") {
510860
- ctx = "config.pie";
510861
- ensure(config2, "pie", {});
510862
- continue;
510863
- }
510864
- if (key === "themeVariables") {
510865
- ctx = "theme";
510866
- themeUnderConfig = true;
510867
- continue;
510868
- }
510869
- continue;
510870
- }
510871
- if (ctx === "config.pie") {
510872
- if (indent < 4) {
510873
- if (key === "pie") {
510874
- ctx = "config.pie";
510875
- ensure(config2, "pie", {});
510876
- continue;
510877
- }
510878
- if (key === "themeVariables") {
510879
- ctx = "theme";
510880
- themeUnderConfig = true;
510881
- continue;
510882
- }
510883
- ctx = "config";
510884
- continue;
510885
- }
510886
- setKV(config2.pie, key, value);
510887
- continue;
510888
- }
510889
- if (ctx === "theme") {
510890
- if (indent < 2) {
510891
- ctx = "root";
510892
- continue;
510893
- }
510894
- setKV(themeVars, key, value);
510895
- continue;
510896
- }
510897
- }
510898
- if (themeUnderConfig && Object.keys(themeVars).length) {
510899
- ensure(config2, "themeVariables", {});
510900
- Object.assign(config2.themeVariables, themeVars);
510901
- }
510902
- return { raw, body, config: Object.keys(config2).length ? config2 : void 0, themeVariables: Object.keys(themeVars).length ? themeVars : void 0 };
510903
- }
510904
- function ensure(obj, key, def) {
510905
- if (obj[key] == null)
510906
- obj[key] = def;
510907
- }
510908
- function unquote2(val) {
510909
- const v = val.trim();
510910
- if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
510911
- return v.slice(1, -1);
510912
- }
510913
- return v;
510914
- }
510915
- function setKV(target, key, rawValue) {
510916
- const v = unquote2(rawValue);
510917
- if (v === "") {
510918
- target[key] = "";
510919
- return;
510920
- }
510921
- const num = Number(v);
510922
- if (!Number.isNaN(num) && /^-?[0-9]+(\.[0-9]+)?$/.test(v)) {
510923
- target[key] = num;
510924
- return;
510925
- }
510926
- if (/^(true|false)$/i.test(v)) {
510927
- target[key] = /^true$/i.test(v);
510928
- return;
510929
- }
510930
- target[key] = v;
510931
- }
510932
- var init_frontmatter = __esm({
510933
- "node_modules/@probelabs/maid/out/core/frontmatter.js"() {
510934
- }
510935
- });
510936
-
510937
511699
  // node_modules/@probelabs/maid/out/renderer/class-builder.js
510938
511700
  function textFromTokens3(tokens) {
510939
511701
  if (!tokens || tokens.length === 0)
@@ -520256,6 +521018,7 @@ var init_client = __esm({
520256
521018
  this.debug = options.debug || process.env.DEBUG_MCP === "1";
520257
521019
  this.config = null;
520258
521020
  this.tracer = options.tracer || null;
521021
+ this.agentEvents = options.agentEvents || null;
520259
521022
  }
520260
521023
  /**
520261
521024
  * Record an MCP telemetry event if tracer is available
@@ -520483,11 +521246,21 @@ var init_client = __esm({
520483
521246
  throw new Error(`Server ${tool6.serverName} not connected`);
520484
521247
  }
520485
521248
  const startTime = Date.now();
521249
+ const toolCallId = `mcp-${toolName}-${startTime}`;
520486
521250
  this.recordMcpEvent("tool.call_started", {
520487
521251
  toolName,
520488
521252
  serverName: tool6.serverName,
520489
521253
  originalToolName: tool6.originalName
520490
521254
  });
521255
+ if (this.agentEvents) {
521256
+ this.agentEvents.emit("toolCall", {
521257
+ toolCallId,
521258
+ name: toolName,
521259
+ args,
521260
+ status: "started",
521261
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
521262
+ });
521263
+ }
520491
521264
  try {
520492
521265
  if (this.debug) {
520493
521266
  console.error(`[MCP DEBUG] Calling ${toolName} with args:`, JSON.stringify(args, null, 2));
@@ -520517,6 +521290,14 @@ var init_client = __esm({
520517
521290
  originalToolName: tool6.originalName,
520518
521291
  durationMs
520519
521292
  });
521293
+ if (this.agentEvents) {
521294
+ this.agentEvents.emit("toolCall", {
521295
+ toolCallId,
521296
+ name: toolName,
521297
+ status: "completed",
521298
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
521299
+ });
521300
+ }
520520
521301
  return result;
520521
521302
  } catch (error40) {
520522
521303
  const durationMs = Date.now() - startTime;
@@ -520532,6 +521313,14 @@ var init_client = __esm({
520532
521313
  durationMs,
520533
521314
  isTimeout: error40.message.includes("timeout")
520534
521315
  });
521316
+ if (this.agentEvents) {
521317
+ this.agentEvents.emit("toolCall", {
521318
+ toolCallId,
521319
+ name: toolName,
521320
+ status: "error",
521321
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
521322
+ });
521323
+ }
520535
521324
  throw error40;
520536
521325
  }
520537
521326
  }
@@ -520717,6 +521506,7 @@ var init_xmlBridge = __esm({
520717
521506
  constructor(options = {}) {
520718
521507
  this.debug = options.debug || false;
520719
521508
  this.tracer = options.tracer || null;
521509
+ this.agentEvents = options.agentEvents || null;
520720
521510
  this.mcpTools = {};
520721
521511
  this.mcpManager = null;
520722
521512
  this.toolDescriptions = {};
@@ -520759,7 +521549,7 @@ var init_xmlBridge = __esm({
520759
521549
  if (this.debug) {
520760
521550
  console.error("[MCP DEBUG] Initializing MCP client manager...");
520761
521551
  }
520762
- this.mcpManager = new MCPClientManager({ debug: this.debug, tracer: this.tracer });
521552
+ this.mcpManager = new MCPClientManager({ debug: this.debug, tracer: this.tracer, agentEvents: this.agentEvents });
520763
521553
  const result = await this.mcpManager.initialize(mcpConfigs);
520764
521554
  const vercelTools = this.mcpManager.getVercelTools();
520765
521555
  this.mcpTools = vercelTools;
@@ -528206,7 +528996,7 @@ function deriveDescription(rawDescription, body) {
528206
528996
  }
528207
528997
  return truncateDescription(description);
528208
528998
  }
528209
- function stripFrontmatter(content) {
528999
+ function stripFrontmatter2(content) {
528210
529000
  const { body } = extractFrontmatter(content);
528211
529001
  return body.trim();
528212
529002
  }
@@ -528347,7 +529137,7 @@ var init_registry = __esm({
528347
529137
  const skill = this.skillsByName.get(name15);
528348
529138
  if (!skill) return null;
528349
529139
  const content = await (0, import_promises3.readFile)(skill.skillFilePath, "utf8");
528350
- return stripFrontmatter(content);
529140
+ return stripFrontmatter2(content);
528351
529141
  }
528352
529142
  async _resolveRealPath(target) {
528353
529143
  try {
@@ -545368,7 +546158,7 @@ var init_ProbeAgent = __esm({
545368
546158
  }
545369
546159
  mcpConfig = null;
545370
546160
  }
545371
- this.mcpBridge = new MCPXmlBridge({ debug: this.debug });
546161
+ this.mcpBridge = new MCPXmlBridge({ debug: this.debug, agentEvents: this.events });
545372
546162
  await this.mcpBridge.initialize(mcpConfig);
545373
546163
  const mcpToolNames = this.mcpBridge.getToolNames();
545374
546164
  const mcpToolCount = mcpToolNames.length;
@@ -546244,6 +547034,14 @@ or
546244
547034
  active_tools_count: activeToolsList.length
546245
547035
  });
546246
547036
  }
547037
+ this.events.emit("timeout.extended", {
547038
+ grantedMs,
547039
+ reason: decision.reason || "work in progress",
547040
+ extensionsUsed: negotiatedTimeoutState.extensionsUsed,
547041
+ extensionsRemaining: negotiatedTimeoutState.maxRequests - negotiatedTimeoutState.extensionsUsed,
547042
+ totalExtraTimeMs: negotiatedTimeoutState.totalExtraTimeMs,
547043
+ budgetRemainingMs: remainingBudgetMs - grantedMs
547044
+ });
546247
547045
  } else {
546248
547046
  if (this.debug) {
546249
547047
  console.log(`[DEBUG] Timeout observer: declined extension (reason: ${decision.reason}). Initiating graceful stop.`);
@@ -546257,6 +547055,11 @@ or
546257
547055
  active_tools: activeToolsList.map((t) => t.name)
546258
547056
  });
546259
547057
  }
547058
+ this.events.emit("timeout.windingDown", {
547059
+ reason: decision.reason || "observer declined",
547060
+ extensionsUsed: negotiatedTimeoutState.extensionsUsed,
547061
+ totalExtraTimeMs: negotiatedTimeoutState.totalExtraTimeMs
547062
+ });
546260
547063
  await this._initiateGracefulStop(gracefulTimeoutState, `observer declined: ${decision.reason}`);
546261
547064
  }
546262
547065
  };
@@ -619797,7 +620600,7 @@ module.exports = /*#__PURE__*/JSON.parse('["aaa","aarp","abb","abbott","abbvie",
619797
620600
  /***/ ((module) => {
619798
620601
 
619799
620602
  "use strict";
619800
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@probelabs/visor","version":"0.1.42","main":"dist/index.js","bin":{"visor":"./dist/index.js"},"exports":{".":{"require":"./dist/index.js","import":"./dist/index.js"},"./sdk":{"types":"./dist/sdk/sdk.d.ts","import":"./dist/sdk/sdk.mjs","require":"./dist/sdk/sdk.js"},"./cli":{"require":"./dist/index.js"}},"files":["dist/","defaults/","action.yml","README.md","LICENSE"],"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"scripts":{"build:cli":"ncc build src/index.ts -o dist && cp -r defaults dist/ && cp -r output dist/ && cp -r docs dist/ && cp -r examples dist/ && cp -r src/debug-visualizer/ui dist/debug-visualizer/ && node scripts/inject-version.js && echo \'#!/usr/bin/env node\' | cat - dist/index.js > temp && mv temp dist/index.js && chmod +x dist/index.js","build:sdk":"tsup src/sdk.ts --dts --sourcemap --format esm,cjs --out-dir dist/sdk","build":"./scripts/build-oss.sh","build:ee":"npm run build:cli && npm run build:sdk","test":"jest && npm run test:yaml","test:unit":"jest","prepublishOnly":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage","test:ee":"jest --testPathPatterns=\'tests/ee\' --testPathIgnorePatterns=\'/node_modules/\' --no-coverage","test:manual:bash":"RUN_MANUAL_TESTS=true jest tests/manual/bash-config-manual.test.ts","lint":"eslint src tests --ext .ts","lint:fix":"eslint src tests --ext .ts --fix","format":"prettier --write src tests","format:check":"prettier --check src tests","clean":"","clean:traces":"node scripts/clean-traces.js","prebuild":"npm run clean && node scripts/generate-config-schema.js","pretest":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","pretest:unit":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","test:with-build":"npm run build:cli && jest","test:yaml":"node dist/index.js test --progress compact","test:yaml:parallel":"node dist/index.js test --progress compact --max-parallel 4","prepare":"husky","pre-commit":"lint-staged","deploy:site":"cd site && npx wrangler pages deploy . --project-name=visor-site --commit-dirty=true","deploy:worker":"npx wrangler deploy","deploy":"npm run deploy:site && npm run deploy:worker","publish:ee":"./scripts/publish-ee.sh","release":"./scripts/release.sh","release:patch":"./scripts/release.sh patch","release:minor":"./scripts/release.sh minor","release:major":"./scripts/release.sh major","release:prerelease":"./scripts/release.sh prerelease","docs:validate":"node scripts/validate-readme-links.js","workshop:setup":"npm install -D reveal-md@6.1.2","workshop:serve":"cd workshop && reveal-md slides.md -w","workshop:export":"reveal-md workshop/slides.md --static workshop/build","workshop:pdf":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter","workshop:pdf:ci":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter --puppeteer-launch-args=\\"--no-sandbox --disable-dev-shm-usage\\"","workshop:pdf:a4":"reveal-md workshop/slides.md --print workshop/Visor-Workshop-A4.pdf --print-size A4","workshop:build":"npm run workshop:export && npm run workshop:pdf","simulate:issue":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issues --action opened --debug","simulate:comment":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issue_comment --action created --debug"},"keywords":["code-review","ai","github-action","cli","pr-review","visor"],"author":"Probe Labs","license":"MIT","description":"AI workflow engine for code review, assistants, and automation — orchestrate checks, MCP tools, and AI providers with YAML-driven pipelines","repository":{"type":"git","url":"git+https://github.com/probelabs/visor.git"},"bugs":{"url":"https://github.com/probelabs/visor/issues"},"homepage":"https://github.com/probelabs/visor#readme","dependencies":{"@actions/core":"^1.11.1","@apidevtools/swagger-parser":"^12.1.0","@grammyjs/runner":"^2.0.3","@modelcontextprotocol/sdk":"^1.25.3","@nyariv/sandboxjs":"github:probelabs/SandboxJS#23c4bb611f7d05f3cb8c523917b5f57103e48108","@octokit/action":"^8.0.2","@octokit/auth-app":"^8.1.0","@octokit/core":"^7.0.3","@octokit/rest":"^22.0.0","@opentelemetry/api":"^1.9.0","@opentelemetry/api-logs":"^0.203.0","@opentelemetry/core":"^1.30.1","@opentelemetry/exporter-logs-otlp-http":"^0.203.0","@opentelemetry/exporter-metrics-otlp-http":"^0.203.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.203.0","@opentelemetry/exporter-trace-otlp-http":"^0.203.0","@opentelemetry/instrumentation":"^0.203.0","@opentelemetry/resources":"^1.30.1","@opentelemetry/sdk-logs":"^0.203.0","@opentelemetry/sdk-metrics":"^1.30.1","@opentelemetry/sdk-node":"^0.203.0","@opentelemetry/sdk-trace-base":"^1.30.1","@opentelemetry/semantic-conventions":"^1.30.1","@probelabs/probe":"0.6.0-rc296","@types/commander":"^2.12.0","@types/uuid":"^10.0.0","@utcp/file":"^1.1.0","@utcp/text":"^1.1.0","acorn":"^8.16.0","acorn-walk":"^8.3.5","ajv":"^8.17.1","ajv-formats":"^3.0.1","better-sqlite3":"^11.0.0","blessed":"^0.1.81","botbuilder":"^4.23.3","botframework-connector":"^4.23.3","cli-table3":"^0.6.5","commander":"^14.0.0","deepmerge":"^4.3.1","dotenv":"^17.2.3","grammy":"^1.41.1","ignore":"^7.0.5","imapflow":"^1.2.12","js-yaml":"^4.1.0","jsonpath-plus":"^10.4.0","liquidjs":"^10.21.1","mailparser":"^3.9.3","minimatch":"^10.2.2","node-cron":"^3.0.3","nodemailer":"^8.0.1","open":"^9.1.0","resend":"^6.9.3","simple-git":"^3.28.0","uuid":"^11.1.0","ws":"^8.18.3"},"optionalDependencies":{"@anthropic/claude-code-sdk":"npm:null@*","@open-policy-agent/opa-wasm":"^1.10.0","@utcp/http":"^1.1.0","@utcp/sdk":"^1.1.0","knex":"^3.1.0","mysql2":"^3.11.0","pg":"^8.13.0","tedious":"^19.0.0"},"devDependencies":{"@eslint/js":"^9.34.0","@kie/act-js":"^2.6.2","@kie/mock-github":"^2.0.1","@swc/core":"^1.13.2","@swc/jest":"^0.2.37","@types/better-sqlite3":"^7.6.0","@types/blessed":"^0.1.27","@types/jest":"^30.0.0","@types/js-yaml":"^4.0.9","@types/mailparser":"^3.4.6","@types/node":"^24.3.0","@types/node-cron":"^3.0.11","@types/nodemailer":"^7.0.11","@types/ws":"^8.18.1","@typescript-eslint/eslint-plugin":"^8.42.0","@typescript-eslint/parser":"^8.42.0","@vercel/ncc":"^0.38.4","eslint":"^9.34.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4","husky":"^9.1.7","jest":"^30.1.3","lint-staged":"^16.1.6","prettier":"^3.6.2","reveal-md":"^6.1.2","ts-json-schema-generator":"^1.5.1","ts-node":"^10.9.2","tsup":"^8.5.0","typescript":"^5.9.2","wrangler":"^3.0.0"},"peerDependenciesMeta":{"@anthropic/claude-code-sdk":{"optional":true},"@utcp/sdk":{"optional":true},"@utcp/http":{"optional":true}},"directories":{"test":"tests"},"lint-staged":{"src/**/*.{ts,js}":["eslint --fix","prettier --write"],"tests/**/*.{ts,js}":["eslint --fix","prettier --write"],"*.{json,md,yml,yaml}":["prettier --write"]}}');
620603
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@probelabs/visor","version":"0.1.42","main":"dist/index.js","bin":{"visor":"./dist/index.js"},"exports":{".":{"require":"./dist/index.js","import":"./dist/index.js"},"./sdk":{"types":"./dist/sdk/sdk.d.ts","import":"./dist/sdk/sdk.mjs","require":"./dist/sdk/sdk.js"},"./cli":{"require":"./dist/index.js"}},"files":["dist/","defaults/","action.yml","README.md","LICENSE"],"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"scripts":{"build:cli":"ncc build src/index.ts -o dist && cp -r defaults dist/ && cp -r output dist/ && cp -r docs dist/ && cp -r examples dist/ && cp -r src/debug-visualizer/ui dist/debug-visualizer/ && node scripts/inject-version.js && echo \'#!/usr/bin/env node\' | cat - dist/index.js > temp && mv temp dist/index.js && chmod +x dist/index.js","build:sdk":"tsup src/sdk.ts --dts --sourcemap --format esm,cjs --out-dir dist/sdk","build":"./scripts/build-oss.sh","build:ee":"npm run build:cli && npm run build:sdk","test":"jest && npm run test:yaml","test:unit":"jest","prepublishOnly":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage","test:ee":"jest --testPathPatterns=\'tests/ee\' --testPathIgnorePatterns=\'/node_modules/\' --no-coverage","test:manual:bash":"RUN_MANUAL_TESTS=true jest tests/manual/bash-config-manual.test.ts","lint":"eslint src tests --ext .ts","lint:fix":"eslint src tests --ext .ts --fix","format":"prettier --write src tests","format:check":"prettier --check src tests","clean":"","clean:traces":"node scripts/clean-traces.js","prebuild":"npm run clean && node scripts/generate-config-schema.js","pretest":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","pretest:unit":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","test:with-build":"npm run build:cli && jest","test:yaml":"node dist/index.js test --progress compact","test:yaml:parallel":"node dist/index.js test --progress compact --max-parallel 4","prepare":"husky","pre-commit":"lint-staged","deploy:site":"cd site && npx wrangler pages deploy . --project-name=visor-site --commit-dirty=true","deploy:worker":"npx wrangler deploy","deploy":"npm run deploy:site && npm run deploy:worker","publish:ee":"./scripts/publish-ee.sh","release":"./scripts/release.sh","release:patch":"./scripts/release.sh patch","release:minor":"./scripts/release.sh minor","release:major":"./scripts/release.sh major","release:prerelease":"./scripts/release.sh prerelease","docs:validate":"node scripts/validate-readme-links.js","workshop:setup":"npm install -D reveal-md@6.1.2","workshop:serve":"cd workshop && reveal-md slides.md -w","workshop:export":"reveal-md workshop/slides.md --static workshop/build","workshop:pdf":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter","workshop:pdf:ci":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter --puppeteer-launch-args=\\"--no-sandbox --disable-dev-shm-usage\\"","workshop:pdf:a4":"reveal-md workshop/slides.md --print workshop/Visor-Workshop-A4.pdf --print-size A4","workshop:build":"npm run workshop:export && npm run workshop:pdf","simulate:issue":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issues --action opened --debug","simulate:comment":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issue_comment --action created --debug"},"keywords":["code-review","ai","github-action","cli","pr-review","visor"],"author":"Probe Labs","license":"MIT","description":"AI workflow engine for code review, assistants, and automation — orchestrate checks, MCP tools, and AI providers with YAML-driven pipelines","repository":{"type":"git","url":"git+https://github.com/probelabs/visor.git"},"bugs":{"url":"https://github.com/probelabs/visor/issues"},"homepage":"https://github.com/probelabs/visor#readme","dependencies":{"@actions/core":"^1.11.1","@apidevtools/swagger-parser":"^12.1.0","@grammyjs/runner":"^2.0.3","@modelcontextprotocol/sdk":"^1.25.3","@nyariv/sandboxjs":"github:probelabs/SandboxJS#23c4bb611f7d05f3cb8c523917b5f57103e48108","@octokit/action":"^8.0.2","@octokit/auth-app":"^8.1.0","@octokit/core":"^7.0.3","@octokit/rest":"^22.0.0","@opentelemetry/api":"^1.9.0","@opentelemetry/api-logs":"^0.203.0","@opentelemetry/core":"^1.30.1","@opentelemetry/exporter-logs-otlp-http":"^0.203.0","@opentelemetry/exporter-metrics-otlp-http":"^0.203.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.203.0","@opentelemetry/exporter-trace-otlp-http":"^0.203.0","@opentelemetry/instrumentation":"^0.203.0","@opentelemetry/resources":"^1.30.1","@opentelemetry/sdk-logs":"^0.203.0","@opentelemetry/sdk-metrics":"^1.30.1","@opentelemetry/sdk-node":"^0.203.0","@opentelemetry/sdk-trace-base":"^1.30.1","@opentelemetry/semantic-conventions":"^1.30.1","@probelabs/probe":"^0.6.0-rc297","@types/commander":"^2.12.0","@types/uuid":"^10.0.0","@utcp/file":"^1.1.0","@utcp/http":"^1.1.0","@utcp/sdk":"^1.1.0","@utcp/text":"^1.1.0","acorn":"^8.16.0","acorn-walk":"^8.3.5","ajv":"^8.17.1","ajv-formats":"^3.0.1","better-sqlite3":"^11.0.0","blessed":"^0.1.81","botbuilder":"^4.23.3","botframework-connector":"^4.23.3","cli-table3":"^0.6.5","commander":"^14.0.0","deepmerge":"^4.3.1","dotenv":"^17.2.3","grammy":"^1.41.1","ignore":"^7.0.5","imapflow":"^1.2.12","js-yaml":"^4.1.0","jsonpath-plus":"^10.4.0","liquidjs":"^10.21.1","mailparser":"^3.9.3","minimatch":"^10.2.2","node-cron":"^3.0.3","nodemailer":"^8.0.1","open":"^9.1.0","resend":"^6.9.3","simple-git":"^3.28.0","uuid":"^11.1.0","ws":"^8.18.3"},"optionalDependencies":{"@anthropic/claude-code-sdk":"npm:null@*","@open-policy-agent/opa-wasm":"^1.10.0","knex":"^3.1.0","mysql2":"^3.11.0","pg":"^8.13.0","tedious":"^19.0.0"},"devDependencies":{"@eslint/js":"^9.34.0","@kie/act-js":"^2.6.2","@kie/mock-github":"^2.0.1","@swc/core":"^1.13.2","@swc/jest":"^0.2.37","@types/better-sqlite3":"^7.6.0","@types/blessed":"^0.1.27","@types/jest":"^30.0.0","@types/js-yaml":"^4.0.9","@types/mailparser":"^3.4.6","@types/node":"^24.3.0","@types/node-cron":"^3.0.11","@types/nodemailer":"^7.0.11","@types/ws":"^8.18.1","@typescript-eslint/eslint-plugin":"^8.42.0","@typescript-eslint/parser":"^8.42.0","@vercel/ncc":"^0.38.4","eslint":"^9.34.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4","husky":"^9.1.7","jest":"^30.1.3","lint-staged":"^16.1.6","prettier":"^3.6.2","reveal-md":"^6.1.2","ts-json-schema-generator":"^1.5.1","ts-node":"^10.9.2","tsup":"^8.5.0","typescript":"^5.9.2","wrangler":"^3.0.0"},"peerDependenciesMeta":{"@anthropic/claude-code-sdk":{"optional":true}},"directories":{"test":"tests"},"lint-staged":{"src/**/*.{ts,js}":["eslint --fix","prettier --write"],"tests/**/*.{ts,js}":["eslint --fix","prettier --write"],"*.{json,md,yml,yaml}":["prettier --write"]}}');
619801
620604
 
619802
620605
  /***/ })
619803
620606