@probelabs/visor 0.1.113 → 0.1.122

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 (120) hide show
  1. package/defaults/workflow-builder.tests.yaml +363 -0
  2. package/defaults/workflow-builder.yaml +720 -0
  3. package/dist/ai-review-service.d.ts.map +1 -1
  4. package/dist/cli-main.d.ts.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/defaults/workflow-builder.tests.yaml +363 -0
  7. package/dist/defaults/workflow-builder.yaml +720 -0
  8. package/dist/docs/workflow-creation-guide.md +1274 -0
  9. package/dist/frontends/slack-frontend.d.ts +3 -0
  10. package/dist/frontends/slack-frontend.d.ts.map +1 -1
  11. package/dist/generated/config-schema.d.ts +14 -6
  12. package/dist/generated/config-schema.d.ts.map +1 -1
  13. package/dist/generated/config-schema.json +14 -6
  14. package/dist/index.js +36879 -13895
  15. package/dist/logger.d.ts +9 -0
  16. package/dist/logger.d.ts.map +1 -1
  17. package/dist/mcp-server.d.ts +4 -4
  18. package/dist/output/traces/{run-2026-01-21T12-31-10-108Z.ndjson → run-2026-01-28T13-56-32-263Z.ndjson} +84 -84
  19. package/dist/output/traces/run-2026-01-28T13-57-13-140Z.ndjson +1357 -0
  20. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  21. package/dist/providers/check-provider.interface.d.ts +15 -1
  22. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  23. package/dist/providers/command-check-provider.d.ts.map +1 -1
  24. package/dist/providers/git-checkout-provider.d.ts.map +1 -1
  25. package/dist/sdk/{check-provider-registry-534KL5HT.mjs → check-provider-registry-JMNLGIMJ.mjs} +11 -11
  26. package/dist/sdk/{chunk-AIVFBIS4.mjs → chunk-35NT3725.mjs} +30 -10
  27. package/dist/sdk/chunk-35NT3725.mjs.map +1 -0
  28. package/dist/sdk/{chunk-AGIZJ4UZ.mjs → chunk-3NMLT3YS.mjs} +42 -8
  29. package/dist/sdk/chunk-3NMLT3YS.mjs.map +1 -0
  30. package/dist/sdk/{chunk-AK6BVWIT.mjs → chunk-7GUAFV6L.mjs} +2 -2
  31. package/dist/sdk/{chunk-QY2XYPEV.mjs → chunk-CUNPH6TR.mjs} +18 -10
  32. package/dist/sdk/chunk-CUNPH6TR.mjs.map +1 -0
  33. package/dist/sdk/{chunk-HTOKWMPO.mjs → chunk-HQL734ZI.mjs} +2 -2
  34. package/dist/sdk/{chunk-7UK3NIIT.mjs → chunk-IHZOSIF4.mjs} +2 -2
  35. package/dist/sdk/{chunk-AUT26LHW.mjs → chunk-J2QWVDXK.mjs} +2 -2
  36. package/dist/sdk/{chunk-QR7MOMJH.mjs → chunk-J6EVEXC2.mjs} +2 -2
  37. package/dist/sdk/{chunk-SIWNBRTK.mjs → chunk-SWEEZ5D5.mjs} +3 -3
  38. package/dist/sdk/{chunk-23L3QRYX.mjs → chunk-VPEQOQ7G.mjs} +279 -70
  39. package/dist/sdk/chunk-VPEQOQ7G.mjs.map +1 -0
  40. package/dist/sdk/{command-executor-TYUV6HUS.mjs → command-executor-Q7MHJKZJ.mjs} +3 -3
  41. package/dist/sdk/{config-YNC2EOOT.mjs → config-MK4XTU45.mjs} +3 -3
  42. package/dist/sdk/{failure-condition-evaluator-YGTF2GHG.mjs → failure-condition-evaluator-HB35XRLZ.mjs} +4 -4
  43. package/dist/sdk/{github-frontend-SIAEOCON.mjs → github-frontend-6Q4BISZX.mjs} +4 -4
  44. package/dist/sdk/{host-DXUYTNMU.mjs → host-P5NQICP7.mjs} +3 -3
  45. package/dist/sdk/{liquid-extensions-PKWCKK7E.mjs → liquid-extensions-DFDEBMUI.mjs} +4 -4
  46. package/dist/sdk/{memory-store-XGBB7LX7.mjs → memory-store-RW5N2NGJ.mjs} +3 -3
  47. package/dist/sdk/{prompt-state-YRJY6QAL.mjs → prompt-state-EZYOUG75.mjs} +3 -3
  48. package/dist/sdk/{renderer-schema-LPKN5UJS.mjs → renderer-schema-CKFB5NDB.mjs} +2 -2
  49. package/dist/sdk/{routing-6N45MJ4F.mjs → routing-KZ345OFG.mjs} +5 -5
  50. package/dist/sdk/sdk.d.mts +19 -1
  51. package/dist/sdk/sdk.d.ts +19 -1
  52. package/dist/sdk/sdk.js +421 -75
  53. package/dist/sdk/sdk.js.map +1 -1
  54. package/dist/sdk/sdk.mjs +35 -18
  55. package/dist/sdk/sdk.mjs.map +1 -1
  56. package/dist/sdk/{slack-frontend-BVKW3GD5.mjs → slack-frontend-J442FJWZ.mjs} +61 -3
  57. package/dist/sdk/slack-frontend-J442FJWZ.mjs.map +1 -0
  58. package/dist/sdk/{workflow-registry-R6KSACFR.mjs → workflow-registry-6LZKCWHP.mjs} +3 -3
  59. package/dist/state-machine/context/build-engine-context.d.ts.map +1 -1
  60. package/dist/state-machine/dispatch/history-snapshot.d.ts.map +1 -1
  61. package/dist/state-machine/runner.d.ts.map +1 -1
  62. package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
  63. package/dist/state-machine/states/routing.d.ts.map +1 -1
  64. package/dist/state-machine/states/wave-planning.d.ts.map +1 -1
  65. package/dist/ter-u14b.json +17826 -0
  66. package/dist/ter-u14n.json +17826 -0
  67. package/dist/test-runner/index.d.ts.map +1 -1
  68. package/dist/traces/{run-2026-01-21T12-31-10-108Z.ndjson → run-2026-01-28T13-56-32-263Z.ndjson} +84 -84
  69. package/dist/traces/run-2026-01-28T13-57-13-140Z.ndjson +1357 -0
  70. package/dist/tui.d.ts +51 -0
  71. package/dist/tui.d.ts.map +1 -0
  72. package/dist/types/cli.d.ts +10 -0
  73. package/dist/types/cli.d.ts.map +1 -1
  74. package/dist/types/config.d.ts +4 -0
  75. package/dist/types/config.d.ts.map +1 -1
  76. package/dist/types/engine.d.ts +3 -0
  77. package/dist/types/engine.d.ts.map +1 -1
  78. package/dist/usr/fonts/AUTHORS +1 -0
  79. package/dist/usr/fonts/LICENSE +94 -0
  80. package/dist/usr/fonts/README +340 -0
  81. package/dist/usr/fonts/ter-u14b.json +17826 -0
  82. package/dist/usr/fonts/ter-u14n.json +17826 -0
  83. package/dist/usr/linux +0 -0
  84. package/dist/usr/windows-ansi +0 -0
  85. package/dist/usr/xterm +0 -0
  86. package/dist/usr/xterm-256color +0 -0
  87. package/dist/usr/xterm.termcap +243 -0
  88. package/dist/usr/xterm.terminfo +1977 -0
  89. package/dist/utils/workspace-manager.d.ts +2 -0
  90. package/dist/utils/workspace-manager.d.ts.map +1 -1
  91. package/dist/utils/worktree-manager.d.ts +5 -0
  92. package/dist/utils/worktree-manager.d.ts.map +1 -1
  93. package/dist/xterm +0 -0
  94. package/dist/xterm.termcap +243 -0
  95. package/package.json +9 -7
  96. package/dist/output/traces/run-2026-01-21T12-32-04-510Z.ndjson +0 -1067
  97. package/dist/sdk/chunk-23L3QRYX.mjs.map +0 -1
  98. package/dist/sdk/chunk-AGIZJ4UZ.mjs.map +0 -1
  99. package/dist/sdk/chunk-AIVFBIS4.mjs.map +0 -1
  100. package/dist/sdk/chunk-QY2XYPEV.mjs.map +0 -1
  101. package/dist/sdk/slack-frontend-BVKW3GD5.mjs.map +0 -1
  102. package/dist/traces/run-2026-01-21T12-32-04-510Z.ndjson +0 -1067
  103. /package/dist/sdk/{check-provider-registry-534KL5HT.mjs.map → check-provider-registry-JMNLGIMJ.mjs.map} +0 -0
  104. /package/dist/sdk/{chunk-AK6BVWIT.mjs.map → chunk-7GUAFV6L.mjs.map} +0 -0
  105. /package/dist/sdk/{chunk-HTOKWMPO.mjs.map → chunk-HQL734ZI.mjs.map} +0 -0
  106. /package/dist/sdk/{chunk-7UK3NIIT.mjs.map → chunk-IHZOSIF4.mjs.map} +0 -0
  107. /package/dist/sdk/{chunk-AUT26LHW.mjs.map → chunk-J2QWVDXK.mjs.map} +0 -0
  108. /package/dist/sdk/{chunk-QR7MOMJH.mjs.map → chunk-J6EVEXC2.mjs.map} +0 -0
  109. /package/dist/sdk/{chunk-SIWNBRTK.mjs.map → chunk-SWEEZ5D5.mjs.map} +0 -0
  110. /package/dist/sdk/{command-executor-TYUV6HUS.mjs.map → command-executor-Q7MHJKZJ.mjs.map} +0 -0
  111. /package/dist/sdk/{config-YNC2EOOT.mjs.map → config-MK4XTU45.mjs.map} +0 -0
  112. /package/dist/sdk/{failure-condition-evaluator-YGTF2GHG.mjs.map → failure-condition-evaluator-HB35XRLZ.mjs.map} +0 -0
  113. /package/dist/sdk/{github-frontend-SIAEOCON.mjs.map → github-frontend-6Q4BISZX.mjs.map} +0 -0
  114. /package/dist/sdk/{host-DXUYTNMU.mjs.map → host-P5NQICP7.mjs.map} +0 -0
  115. /package/dist/sdk/{liquid-extensions-PKWCKK7E.mjs.map → liquid-extensions-DFDEBMUI.mjs.map} +0 -0
  116. /package/dist/sdk/{memory-store-XGBB7LX7.mjs.map → memory-store-RW5N2NGJ.mjs.map} +0 -0
  117. /package/dist/sdk/{prompt-state-YRJY6QAL.mjs.map → prompt-state-EZYOUG75.mjs.map} +0 -0
  118. /package/dist/sdk/{renderer-schema-LPKN5UJS.mjs.map → renderer-schema-CKFB5NDB.mjs.map} +0 -0
  119. /package/dist/sdk/{routing-6N45MJ4F.mjs.map → routing-KZ345OFG.mjs.map} +0 -0
  120. /package/dist/sdk/{workflow-registry-R6KSACFR.mjs.map → workflow-registry-6LZKCWHP.mjs.map} +0 -0
package/dist/sdk/sdk.js CHANGED
@@ -211,6 +211,10 @@ var init_logger = __esm({
211
211
  isTTY = typeof process !== "undefined" ? !!process.stderr.isTTY : false;
212
212
  showTimestamps = true;
213
213
  // default: always show timestamps
214
+ sink;
215
+ sinkPassthrough = true;
216
+ sinkErrorMode = "throw";
217
+ sinkErrorHandler;
214
218
  configure(opts = {}) {
215
219
  let lvl = "info";
216
220
  if (opts.debug || process.env.VISOR_DEBUG === "true") {
@@ -231,6 +235,12 @@ var init_logger = __esm({
231
235
  const output = opts.outputFormat || process.env.VISOR_OUTPUT_FORMAT || "table";
232
236
  this.isJsonLike = output === "json" || output === "sarif";
233
237
  }
238
+ setSink(sink, opts = {}) {
239
+ this.sink = sink;
240
+ this.sinkPassthrough = opts.passthrough !== void 0 ? opts.passthrough : true;
241
+ this.sinkErrorMode = opts.errorMode || "throw";
242
+ this.sinkErrorHandler = opts.onError;
243
+ }
234
244
  shouldLog(level) {
235
245
  const desired = levelToNumber(level);
236
246
  const current = levelToNumber(this.level);
@@ -253,14 +263,38 @@ var init_logger = __esm({
253
263
  }
254
264
  }
255
265
  write(msg, level) {
266
+ const suffix = this.getTraceSuffix(msg);
267
+ const decoratedMsg = suffix ? `${msg}${suffix}` : msg;
268
+ const lvl = level || "info";
269
+ if (this.sink) {
270
+ try {
271
+ this.sink(decoratedMsg, lvl);
272
+ } catch (error) {
273
+ if (this.sinkErrorMode === "warn") {
274
+ try {
275
+ if (this.sinkErrorHandler) {
276
+ this.sinkErrorHandler(error);
277
+ } else {
278
+ const errMsg = error instanceof Error ? error.message : String(error);
279
+ process.stderr.write(`[logger] sink failed: ${errMsg}
280
+ `);
281
+ }
282
+ } catch {
283
+ }
284
+ }
285
+ if (this.sinkErrorMode === "throw") {
286
+ throw error;
287
+ }
288
+ return;
289
+ }
290
+ if (!this.sinkPassthrough) return;
291
+ }
256
292
  try {
257
- const suffix = this.getTraceSuffix(msg);
258
- const decoratedMsg = suffix ? `${msg}${suffix}` : msg;
259
293
  if (this.showTimestamps) {
260
294
  const ts = (/* @__PURE__ */ new Date()).toISOString();
261
- const lvl = level ? level : void 0;
295
+ const lvl2 = level ? level : void 0;
262
296
  let tsToken = `[${ts}]`;
263
- let lvlToken = lvl ? `[${lvl}]` : "";
297
+ let lvlToken = lvl2 ? `[${lvl2}]` : "";
264
298
  if (this.isTTY && !this.isJsonLike) {
265
299
  const reset = "\x1B[0m";
266
300
  const dim = "\x1B[2m";
@@ -278,14 +312,14 @@ var init_logger = __esm({
278
312
  // bright black / gray
279
313
  };
280
314
  tsToken = `${dim}${tsToken}${reset}`;
281
- if (lvl) {
282
- const colour = colours[lvl] || "";
315
+ if (lvl2) {
316
+ const colour = colours[lvl2] || "";
283
317
  if (colour) {
284
318
  lvlToken = `${colour}${lvlToken}${reset}`;
285
319
  }
286
320
  }
287
321
  }
288
- const prefix = lvl ? `${tsToken} ${lvlToken}` : tsToken;
322
+ const prefix = lvl2 ? `${tsToken} ${lvlToken}` : tsToken;
289
323
  process.stderr.write(`${prefix} ${decoratedMsg}
290
324
  `);
291
325
  } else {
@@ -1163,22 +1197,41 @@ async function handleWavePlanning(context2, state, transition) {
1163
1197
  if (gotoEvent) {
1164
1198
  eventOverrides.set(target, gotoEvent);
1165
1199
  }
1200
+ const sourceCheck = request.sourceCheck;
1201
+ if (sourceCheck && request.origin === "run") {
1202
+ if (!state.allowedFailedDeps) {
1203
+ state.allowedFailedDeps = /* @__PURE__ */ new Map();
1204
+ }
1205
+ const allowedSet = state.allowedFailedDeps.get(target) || /* @__PURE__ */ new Set();
1206
+ allowedSet.add(sourceCheck);
1207
+ state.allowedFailedDeps.set(target, allowedSet);
1208
+ if (context2.debug) {
1209
+ logger.info(
1210
+ `[WavePlanning] Allowing ${target} to run despite failed dependency ${sourceCheck}`
1211
+ );
1212
+ }
1213
+ }
1214
+ const origin = request.origin;
1166
1215
  const dependencies = findTransitiveDependencies(target, context2);
1167
1216
  for (const dep of dependencies) {
1168
1217
  const stats = state.stats.get(dep);
1169
1218
  const hasSucceeded = !!stats && (stats.successfulRuns || 0) > 0;
1219
+ const hasRun = !!stats && (stats.totalRuns || 0) > 0;
1220
+ if (origin === "run" && hasRun) {
1221
+ continue;
1222
+ }
1170
1223
  if (!hasSucceeded) {
1171
1224
  checksToRun.add(dep);
1172
1225
  }
1173
1226
  }
1174
1227
  let shouldIncludeDependents = true;
1175
1228
  try {
1176
- const origin = request.origin;
1229
+ const origin2 = request.origin;
1177
1230
  const cfg = context2.config.checks?.[target];
1178
1231
  const targetType = String(cfg?.type || "").toLowerCase();
1179
1232
  const execCtx = context2.executionContext || {};
1180
1233
  const hasWebhook = !!execCtx.webhookContext;
1181
- if (hasWebhook && (origin === "goto" || origin === "goto_js") && targetType === "human-input") {
1234
+ if (hasWebhook && (origin2 === "goto" || origin2 === "goto_js") && targetType === "human-input") {
1182
1235
  shouldIncludeDependents = false;
1183
1236
  }
1184
1237
  } catch {
@@ -3302,6 +3355,12 @@ function createMemoryHelpers() {
3302
3355
  }
3303
3356
  };
3304
3357
  }
3358
+ function getHistoryLimit() {
3359
+ const raw = process.env.VISOR_TEST_HISTORY_LIMIT || process.env.VISOR_OUTPUT_HISTORY_LIMIT;
3360
+ if (!raw) return void 0;
3361
+ const n = parseInt(raw, 10);
3362
+ return Number.isFinite(n) && n > 0 ? n : void 0;
3363
+ }
3305
3364
  function formatScopeLabel(scope) {
3306
3365
  if (!scope || scope.length === 0) return "";
3307
3366
  return scope.map((item) => `${item.check}:${item.index}`).join("|");
@@ -3902,7 +3961,9 @@ async function processOnFail(checkId, scope, result, checkConfig, context2, stat
3902
3961
  type: "ForwardRunRequested",
3903
3962
  target: targetCheck,
3904
3963
  scope: itemScope,
3905
- origin: "run"
3964
+ origin: "run",
3965
+ sourceCheck: checkId
3966
+ // The failed check that triggered on_fail.run
3906
3967
  });
3907
3968
  }
3908
3969
  } else {
@@ -3919,7 +3980,9 @@ async function processOnFail(checkId, scope, result, checkConfig, context2, stat
3919
3980
  type: "ForwardRunRequested",
3920
3981
  target: targetCheck,
3921
3982
  scope,
3922
- origin: "run"
3983
+ origin: "run",
3984
+ sourceCheck: checkId
3985
+ // The failed check that triggered on_fail.run
3923
3986
  });
3924
3987
  }
3925
3988
  }
@@ -3962,7 +4025,9 @@ async function processOnFail(checkId, scope, result, checkConfig, context2, stat
3962
4025
  type: "ForwardRunRequested",
3963
4026
  target: targetCheck,
3964
4027
  scope,
3965
- origin: "run_js"
4028
+ origin: "run_js",
4029
+ sourceCheck: checkId
4030
+ // The failed check that triggered on_fail.run_js
3966
4031
  });
3967
4032
  }
3968
4033
  }
@@ -4109,6 +4174,7 @@ async function processOnFail(checkId, scope, result, checkConfig, context2, stat
4109
4174
  async function evaluateRunJs(runJs, checkId, checkConfig, result, context2, _state) {
4110
4175
  try {
4111
4176
  const sandbox = createSecureSandbox();
4177
+ const historyLimit = getHistoryLimit();
4112
4178
  const snapshotId = context2.journal.beginSnapshot();
4113
4179
  const contextView = new (init_snapshot_store(), __toCommonJS(snapshot_store_exports)).ContextView(
4114
4180
  context2.journal,
@@ -4133,7 +4199,8 @@ async function evaluateRunJs(runJs, checkId, checkConfig, result, context2, _sta
4133
4199
  try {
4134
4200
  const history = contextView.getHistory(checkIdFromJournal);
4135
4201
  if (history && history.length > 0) {
4136
- outputsHistory[checkIdFromJournal] = history.map(
4202
+ const trimmed = historyLimit && history.length > historyLimit ? history.slice(history.length - historyLimit) : history;
4203
+ outputsHistory[checkIdFromJournal] = trimmed.map(
4137
4204
  (r) => r.output !== void 0 ? r.output : r
4138
4205
  );
4139
4206
  }
@@ -4215,6 +4282,7 @@ async function evaluateGoto(gotoJs, gotoStatic, checkId, checkConfig, result, co
4215
4282
  if (gotoJs) {
4216
4283
  try {
4217
4284
  const sandbox = createSecureSandbox();
4285
+ const historyLimit = getHistoryLimit();
4218
4286
  const snapshotId = context2.journal.beginSnapshot();
4219
4287
  const contextView = new (init_snapshot_store(), __toCommonJS(snapshot_store_exports)).ContextView(
4220
4288
  context2.journal,
@@ -4239,7 +4307,8 @@ async function evaluateGoto(gotoJs, gotoStatic, checkId, checkConfig, result, co
4239
4307
  try {
4240
4308
  const history = contextView.getHistory(checkIdFromJournal);
4241
4309
  if (history && history.length > 0) {
4242
- outputsHistory[checkIdFromJournal] = history.map(
4310
+ const trimmed = historyLimit && history.length > historyLimit ? history.slice(history.length - historyLimit) : history;
4311
+ outputsHistory[checkIdFromJournal] = trimmed.map(
4243
4312
  (r) => r.output !== void 0 ? r.output : r
4244
4313
  );
4245
4314
  }
@@ -4342,6 +4411,7 @@ async function evaluateTransitions(transitions, checkId, checkConfig, result, co
4342
4411
  if (!transitions || transitions.length === 0) return void 0;
4343
4412
  try {
4344
4413
  const sandbox = createSecureSandbox();
4414
+ const historyLimit = getHistoryLimit();
4345
4415
  const snapshotId = context2.journal.beginSnapshot();
4346
4416
  const ContextView2 = (init_snapshot_store(), __toCommonJS(snapshot_store_exports)).ContextView;
4347
4417
  const view = new ContextView2(context2.journal, context2.sessionId, snapshotId, [], void 0);
@@ -4358,7 +4428,10 @@ async function evaluateTransitions(transitions, checkId, checkConfig, result, co
4358
4428
  try {
4359
4429
  const hist = view.getHistory(cid);
4360
4430
  if (hist && hist.length > 0) {
4361
- outputsHistory[cid] = hist.map((r) => r.output !== void 0 ? r.output : r);
4431
+ const trimmed = historyLimit && hist.length > historyLimit ? hist.slice(hist.length - historyLimit) : hist;
4432
+ outputsHistory[cid] = trimmed.map(
4433
+ (r) => r.output !== void 0 ? r.output : r
4434
+ );
4362
4435
  }
4363
4436
  } catch {
4364
4437
  }
@@ -4516,8 +4589,15 @@ var history_snapshot_exports = {};
4516
4589
  __export(history_snapshot_exports, {
4517
4590
  buildOutputHistoryFromJournal: () => buildOutputHistoryFromJournal
4518
4591
  });
4592
+ function getHistoryLimit2() {
4593
+ const raw = process.env.VISOR_TEST_HISTORY_LIMIT || process.env.VISOR_OUTPUT_HISTORY_LIMIT;
4594
+ if (!raw) return void 0;
4595
+ const n = parseInt(raw, 10);
4596
+ return Number.isFinite(n) && n > 0 ? n : void 0;
4597
+ }
4519
4598
  function buildOutputHistoryFromJournal(context2) {
4520
4599
  const outputHistory = /* @__PURE__ */ new Map();
4600
+ const limit = getHistoryLimit2();
4521
4601
  try {
4522
4602
  const snapshot = context2.journal.beginSnapshot();
4523
4603
  const allEntries = context2.journal.readVisible(context2.sessionId, snapshot, void 0);
@@ -4539,7 +4619,13 @@ function buildOutputHistoryFromJournal(context2) {
4539
4619
  }
4540
4620
  } catch {
4541
4621
  }
4542
- if (payload !== void 0) outputHistory.get(checkId).push(payload);
4622
+ if (payload !== void 0) {
4623
+ const arr = outputHistory.get(checkId);
4624
+ arr.push(payload);
4625
+ if (limit && arr.length > limit) {
4626
+ arr.splice(0, arr.length - limit);
4627
+ }
4628
+ }
4543
4629
  }
4544
4630
  } catch (error) {
4545
4631
  logger.debug(`[LevelDispatch] Error building output history: ${error}`);
@@ -5757,8 +5843,8 @@ var init_ai_review_service = __esm({
5757
5843
  sessionRegistry;
5758
5844
  constructor(config = {}) {
5759
5845
  this.config = {
5760
- timeout: 6e5,
5761
- // Increased timeout to 10 minutes for AI responses
5846
+ timeout: 12e5,
5847
+ // Increased timeout to 20 minutes for AI responses
5762
5848
  ...config
5763
5849
  };
5764
5850
  this.sessionRegistry = SessionRegistry.getInstance();
@@ -5853,12 +5939,10 @@ var init_ai_review_service = __esm({
5853
5939
  }
5854
5940
  }
5855
5941
  if (!this.config.apiKey) {
5856
- const errorMessage = "No API key configured. Please set GOOGLE_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY environment variable, or configure AWS credentials for Bedrock (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY).";
5942
+ log("\u26A0\uFE0F No API key configured - ProbeAgent will attempt CLI fallback (claude-code/codex)");
5857
5943
  if (debugInfo) {
5858
- debugInfo.errors = [errorMessage];
5859
- debugInfo.rawResponse = "API call attempted in debug without API key (test mode)";
5860
- } else {
5861
- throw new Error(errorMessage);
5944
+ debugInfo.errors = debugInfo.errors || [];
5945
+ debugInfo.errors.push("No API key configured - attempting CLI fallback");
5862
5946
  }
5863
5947
  }
5864
5948
  }
@@ -7010,6 +7094,9 @@ ${"=".repeat(60)}
7010
7094
  options.model = this.config.model;
7011
7095
  }
7012
7096
  const agent = new import_probe2.ProbeAgent(options);
7097
+ if (typeof agent.initialize === "function") {
7098
+ await agent.initialize();
7099
+ }
7013
7100
  log("\u{1F680} Calling ProbeAgent...");
7014
7101
  let schemaString = void 0;
7015
7102
  let effectiveSchema = typeof schema === "object" ? "custom" : schema;
@@ -9299,10 +9386,18 @@ var init_ai_check_provider = __esm({
9299
9386
  if (info && typeof info.workspacePath === "string") {
9300
9387
  workspaceRoot = info.workspacePath;
9301
9388
  mainProjectPath = info.mainProjectPath;
9302
- folders.push(info.workspacePath);
9303
9389
  }
9304
9390
  } catch {
9305
9391
  }
9392
+ if (mainProjectPath) {
9393
+ folders.push(mainProjectPath);
9394
+ logger.debug(
9395
+ `[AI Provider] Including main project FIRST in allowedFolders: ${mainProjectPath}`
9396
+ );
9397
+ }
9398
+ if (workspaceRoot) {
9399
+ folders.push(workspaceRoot);
9400
+ }
9306
9401
  const projectPaths = [];
9307
9402
  try {
9308
9403
  const projects = workspace.listProjects?.() || [];
@@ -9314,27 +9409,16 @@ var init_ai_check_provider = __esm({
9314
9409
  }
9315
9410
  } catch {
9316
9411
  }
9317
- if (projectPaths.length === 0 && mainProjectPath) {
9318
- folders.push(mainProjectPath);
9319
- logger.debug(
9320
- `[AI Provider] No external projects - including main project as fallback: ${mainProjectPath}`
9321
- );
9322
- } else if (mainProjectPath) {
9323
- logger.debug(
9324
- `[AI Provider] Excluding main project (visor) from allowedFolders: ${mainProjectPath}`
9325
- );
9326
- }
9327
9412
  const unique = Array.from(new Set(folders.filter((p) => typeof p === "string" && p)));
9328
9413
  if (unique.length > 0 && workspaceRoot) {
9329
9414
  aiConfig.allowedFolders = unique;
9330
- aiConfig.path = workspaceRoot;
9331
- aiConfig.cwd = workspaceRoot;
9332
- aiConfig.workspacePath = workspaceRoot;
9415
+ const aiCwd = mainProjectPath || workspaceRoot;
9416
+ aiConfig.path = aiCwd;
9417
+ aiConfig.cwd = aiCwd;
9418
+ aiConfig.workspacePath = aiCwd;
9333
9419
  logger.debug(`[AI Provider] Workspace isolation enabled:`);
9334
- logger.debug(`[AI Provider] workspaceRoot (cwd): ${workspaceRoot}`);
9335
- logger.debug(
9336
- `[AI Provider] mainProjectPath (excluded unless fallback): ${mainProjectPath || "N/A"}`
9337
- );
9420
+ logger.debug(`[AI Provider] cwd (mainProjectPath): ${aiCwd}`);
9421
+ logger.debug(`[AI Provider] workspaceRoot: ${workspaceRoot}`);
9338
9422
  logger.debug(`[AI Provider] allowedFolders: ${JSON.stringify(unique)}`);
9339
9423
  }
9340
9424
  } else if (parentCtx && typeof parentCtx.workingDirectory === "string") {
@@ -9502,10 +9586,15 @@ ${processedPrompt}` : processedPrompt;
9502
9586
  const stepName = config.checkName || "unknown";
9503
9587
  const mock = sessionInfo?.hooks?.mockForStep?.(String(stepName));
9504
9588
  if (mock !== void 0) {
9505
- if (mock && typeof mock === "object" && ("issues" in mock || "content" in mock)) {
9506
- return mock;
9507
- }
9508
- return { issues: [], output: mock };
9589
+ const ms = mock;
9590
+ const issuesArr = Array.isArray(ms?.issues) ? ms.issues : [];
9591
+ const out = ms && typeof ms === "object" && "output" in ms ? ms.output : ms;
9592
+ const summary = {
9593
+ issues: issuesArr,
9594
+ output: out,
9595
+ ...typeof ms?.content === "string" ? { content: String(ms.content) } : {}
9596
+ };
9597
+ return summary;
9509
9598
  }
9510
9599
  } catch {
9511
9600
  }
@@ -11964,7 +12053,17 @@ var init_command_check_provider = __esm({
11964
12053
  }
11965
12054
  try {
11966
12055
  const stepName = config.checkName || "unknown";
12056
+ if (process.env.VISOR_DEBUG === "true") {
12057
+ logger.debug(
12058
+ `[Command] Mock check: stepName=${stepName}, context=${!!context2}, hooks=${!!context2?.hooks}, mockForStep=${!!context2?.hooks?.mockForStep}`
12059
+ );
12060
+ }
11967
12061
  const rawMock = context2?.hooks?.mockForStep?.(String(stepName));
12062
+ if (process.env.VISOR_DEBUG === "true") {
12063
+ logger.debug(
12064
+ `[Command] Mock result: ${rawMock !== void 0 ? "found" : "not found"}, value=${JSON.stringify(rawMock)?.slice(0, 200)}`
12065
+ );
12066
+ }
11968
12067
  if (rawMock !== void 0) {
11969
12068
  let mock;
11970
12069
  if (typeof rawMock === "number") {
@@ -11989,6 +12088,16 @@ var init_command_check_provider = __esm({
11989
12088
  out = mock;
11990
12089
  }
11991
12090
  const code = isCommandMock ? typeof m.exit_code === "number" ? m.exit_code : typeof m.exit === "number" ? m.exit : 0 : 0;
12091
+ let outputWithMeta;
12092
+ if (isCommandMock) {
12093
+ if (out && typeof out === "object" && !Array.isArray(out)) {
12094
+ outputWithMeta = { ...out, exit_code: code };
12095
+ } else {
12096
+ outputWithMeta = { value: out, exit_code: code };
12097
+ }
12098
+ } else {
12099
+ outputWithMeta = out;
12100
+ }
11992
12101
  if (code !== 0) {
11993
12102
  return {
11994
12103
  issues: [
@@ -12001,10 +12110,10 @@ var init_command_check_provider = __esm({
12001
12110
  category: "logic"
12002
12111
  }
12003
12112
  ],
12004
- output: out
12113
+ output: outputWithMeta
12005
12114
  };
12006
12115
  }
12007
- return { issues: [], output: out };
12116
+ return { issues: [], output: outputWithMeta };
12008
12117
  }
12009
12118
  } catch {
12010
12119
  }
@@ -17417,7 +17526,7 @@ var init_config_schema = __esm({
17417
17526
  description: "Arguments/inputs for the workflow"
17418
17527
  },
17419
17528
  overrides: {
17420
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-11359-23556-src_types_config.ts-0-40845%3E%3E",
17529
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-11359-23556-src_types_config.ts-0-41044%3E%3E",
17421
17530
  description: "Override specific step configurations in the workflow"
17422
17531
  },
17423
17532
  output_mapping: {
@@ -17433,7 +17542,7 @@ var init_config_schema = __esm({
17433
17542
  description: "Config file path - alternative to workflow ID (loads a Visor config file as workflow)"
17434
17543
  },
17435
17544
  workflow_overrides: {
17436
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-11359-23556-src_types_config.ts-0-40845%3E%3E",
17545
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-11359-23556-src_types_config.ts-0-41044%3E%3E",
17437
17546
  description: "Alias for overrides - workflow step overrides (backward compatibility)"
17438
17547
  },
17439
17548
  ref: {
@@ -18063,7 +18172,7 @@ var init_config_schema = __esm({
18063
18172
  description: "Custom output name (defaults to workflow name)"
18064
18173
  },
18065
18174
  overrides: {
18066
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-11359-23556-src_types_config.ts-0-40845%3E%3E",
18175
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-11359-23556-src_types_config.ts-0-41044%3E%3E",
18067
18176
  description: "Step overrides"
18068
18177
  },
18069
18178
  output_mapping: {
@@ -18078,13 +18187,13 @@ var init_config_schema = __esm({
18078
18187
  "^x-": {}
18079
18188
  }
18080
18189
  },
18081
- "Record<string,Partial<interface-src_types_config.ts-11359-23556-src_types_config.ts-0-40845>>": {
18190
+ "Record<string,Partial<interface-src_types_config.ts-11359-23556-src_types_config.ts-0-41044>>": {
18082
18191
  type: "object",
18083
18192
  additionalProperties: {
18084
- $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-11359-23556-src_types_config.ts-0-40845%3E"
18193
+ $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-11359-23556-src_types_config.ts-0-41044%3E"
18085
18194
  }
18086
18195
  },
18087
- "Partial<interface-src_types_config.ts-11359-23556-src_types_config.ts-0-40845>": {
18196
+ "Partial<interface-src_types_config.ts-11359-23556-src_types_config.ts-0-41044>": {
18088
18197
  type: "object",
18089
18198
  additionalProperties: false
18090
18199
  },
@@ -18655,6 +18764,14 @@ var init_config_schema = __esm({
18655
18764
  type: "string",
18656
18765
  description: "Base path for workspaces (default: /tmp/visor-workspaces)"
18657
18766
  },
18767
+ name: {
18768
+ type: "string",
18769
+ description: "Workspace directory name (defaults to session id)"
18770
+ },
18771
+ main_project_name: {
18772
+ type: "string",
18773
+ description: "Main project folder name inside the workspace (defaults to original directory name)"
18774
+ },
18658
18775
  cleanup_on_exit: {
18659
18776
  type: "boolean",
18660
18777
  description: "Clean up workspace on exit (default: true)"
@@ -20635,25 +20752,76 @@ var init_worktree_manager = __esm({
20635
20752
  }
20636
20753
  if (fs14.existsSync(worktreePath)) {
20637
20754
  logger.debug(`Worktree already exists: ${worktreePath}`);
20638
- if (options.clean) {
20639
- logger.debug(`Cleaning existing worktree`);
20640
- await this.cleanWorktree(worktreePath);
20641
- }
20642
20755
  const metadata2 = await this.loadMetadata(worktreePath);
20643
20756
  if (metadata2) {
20644
- this.activeWorktrees.set(worktreeId, metadata2);
20645
- return {
20646
- id: worktreeId,
20647
- path: worktreePath,
20648
- ref: metadata2.ref,
20649
- commit: metadata2.commit,
20650
- metadata: metadata2,
20651
- locked: false
20652
- };
20757
+ if (metadata2.ref === ref) {
20758
+ if (options.clean) {
20759
+ logger.debug(`Cleaning existing worktree`);
20760
+ await this.cleanWorktree(worktreePath);
20761
+ }
20762
+ this.activeWorktrees.set(worktreeId, metadata2);
20763
+ return {
20764
+ id: worktreeId,
20765
+ path: worktreePath,
20766
+ ref: metadata2.ref,
20767
+ commit: metadata2.commit,
20768
+ metadata: metadata2,
20769
+ locked: false
20770
+ };
20771
+ } else {
20772
+ logger.info(
20773
+ `Worktree exists with different ref (${metadata2.ref} -> ${ref}), updating...`
20774
+ );
20775
+ try {
20776
+ const bareRepoPath2 = metadata2.bare_repo_path || await this.getOrCreateBareRepo(
20777
+ repository,
20778
+ repoUrl,
20779
+ options.token,
20780
+ options.fetchDepth,
20781
+ options.cloneTimeoutMs
20782
+ );
20783
+ await this.fetchRef(bareRepoPath2, ref);
20784
+ const newCommit = await this.getCommitShaForRef(bareRepoPath2, ref);
20785
+ const checkoutCmd = `git -C ${this.escapeShellArg(worktreePath)} checkout --detach ${this.escapeShellArg(newCommit)}`;
20786
+ const checkoutResult = await this.executeGitCommand(checkoutCmd, { timeout: 6e4 });
20787
+ if (checkoutResult.exitCode !== 0) {
20788
+ throw new Error(`Failed to checkout new ref: ${checkoutResult.stderr}`);
20789
+ }
20790
+ const updatedMetadata = {
20791
+ ...metadata2,
20792
+ ref,
20793
+ commit: newCommit,
20794
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
20795
+ };
20796
+ await this.saveMetadata(worktreePath, updatedMetadata);
20797
+ if (options.clean) {
20798
+ logger.debug(`Cleaning updated worktree`);
20799
+ await this.cleanWorktree(worktreePath);
20800
+ }
20801
+ this.activeWorktrees.set(worktreeId, updatedMetadata);
20802
+ logger.info(`Successfully updated worktree to ${ref} (${newCommit})`);
20803
+ return {
20804
+ id: worktreeId,
20805
+ path: worktreePath,
20806
+ ref,
20807
+ commit: newCommit,
20808
+ metadata: updatedMetadata,
20809
+ locked: false
20810
+ };
20811
+ } catch (error) {
20812
+ const errorMessage = error instanceof Error ? error.message : String(error);
20813
+ logger.warn(`Failed to update worktree, will recreate: ${errorMessage}`);
20814
+ await fsp.rm(worktreePath, { recursive: true, force: true });
20815
+ }
20816
+ }
20817
+ } else {
20818
+ logger.info(`Removing stale directory (no metadata): ${worktreePath}`);
20819
+ await fsp.rm(worktreePath, { recursive: true, force: true });
20653
20820
  }
20654
20821
  }
20655
20822
  await this.fetchRef(bareRepoPath, ref);
20656
20823
  const commit = await this.getCommitShaForRef(bareRepoPath, ref);
20824
+ await this.pruneWorktrees(bareRepoPath);
20657
20825
  logger.info(`Creating worktree for ${repository}@${ref} (${commit})`);
20658
20826
  const createCmd = `git -C ${this.escapeShellArg(
20659
20827
  bareRepoPath
@@ -20686,6 +20854,20 @@ var init_worktree_manager = __esm({
20686
20854
  locked: false
20687
20855
  };
20688
20856
  }
20857
+ /**
20858
+ * Prune stale worktree entries from a bare repository.
20859
+ * This removes entries for worktrees whose directories no longer exist.
20860
+ */
20861
+ async pruneWorktrees(bareRepoPath) {
20862
+ logger.debug(`Pruning stale worktrees for ${bareRepoPath}`);
20863
+ const pruneCmd = `git -C ${this.escapeShellArg(bareRepoPath)} worktree prune`;
20864
+ const result = await this.executeGitCommand(pruneCmd, { timeout: 1e4 });
20865
+ if (result.exitCode !== 0) {
20866
+ logger.warn(`Failed to prune worktrees: ${result.stderr}`);
20867
+ } else {
20868
+ logger.debug(`Successfully pruned stale worktrees`);
20869
+ }
20870
+ }
20689
20871
  /**
20690
20872
  * Fetch a specific ref in bare repository
20691
20873
  */
@@ -21046,6 +21228,53 @@ var init_git_checkout_provider = __esm({
21046
21228
  async execute(prInfo, config, dependencyResults, context2) {
21047
21229
  const checkoutConfig = config;
21048
21230
  const issues = [];
21231
+ try {
21232
+ const stepName = config.checkName || "git-checkout";
21233
+ if (process.env.VISOR_DEBUG === "true") {
21234
+ logger.debug(
21235
+ `[GitCheckout] Mock check: stepName=${stepName}, context=${!!context2}, hooks=${!!context2?.hooks}, mockForStep=${!!context2?.hooks?.mockForStep}`
21236
+ );
21237
+ }
21238
+ const mock = context2?.hooks?.mockForStep?.(String(stepName));
21239
+ if (process.env.VISOR_DEBUG === "true") {
21240
+ logger.debug(
21241
+ `[GitCheckout] Mock result: ${mock !== void 0 ? "found" : "not found"}, value=${JSON.stringify(mock)}`
21242
+ );
21243
+ }
21244
+ if (mock !== void 0) {
21245
+ if (mock && typeof mock === "object") {
21246
+ const mockOutput = mock;
21247
+ if (mockOutput.success === false) {
21248
+ const errorMsg = String(mockOutput.error || "Mocked checkout failure");
21249
+ if (process.env.VISOR_DEBUG === "true") {
21250
+ logger.debug(`[GitCheckout] Returning mock failure: ${errorMsg}`);
21251
+ }
21252
+ return {
21253
+ issues: [
21254
+ {
21255
+ file: "git-checkout",
21256
+ line: 0,
21257
+ ruleId: "git-checkout/error",
21258
+ message: `Failed to checkout code: ${errorMsg}`,
21259
+ severity: "error",
21260
+ category: "logic"
21261
+ }
21262
+ ],
21263
+ output: mockOutput
21264
+ };
21265
+ }
21266
+ if (process.env.VISOR_DEBUG === "true") {
21267
+ logger.debug(`[GitCheckout] Returning mock success: ${JSON.stringify(mockOutput)}`);
21268
+ }
21269
+ return { issues: [], output: mockOutput };
21270
+ }
21271
+ if (process.env.VISOR_DEBUG === "true") {
21272
+ logger.debug(`[GitCheckout] Returning primitive mock: ${String(mock)}`);
21273
+ }
21274
+ return { issues: [], output: { success: true, path: String(mock) } };
21275
+ }
21276
+ } catch {
21277
+ }
21049
21278
  try {
21050
21279
  const templateContext = this.buildTemplateContext(
21051
21280
  prInfo,
@@ -21548,7 +21777,7 @@ async function executeCheckWithForEachItems(checkId, forEachParent, forEachItems
21548
21777
  __outputHistory: outputHistory,
21549
21778
  ai: {
21550
21779
  ...checkConfig.ai || {},
21551
- timeout: checkConfig.ai?.timeout || 6e5,
21780
+ timeout: checkConfig.ai?.timeout || 12e5,
21552
21781
  debug: !!context2.debug
21553
21782
  }
21554
21783
  };
@@ -22028,7 +22257,7 @@ async function executeInvocation(item, context2, scope, prInfo, dependencyResult
22028
22257
  __outputHistory: outputHistory,
22029
22258
  ai: {
22030
22259
  ...stepConfig.ai || {},
22031
- timeout: stepConfig.ai?.timeout || 6e5,
22260
+ timeout: stepConfig.ai?.timeout || 12e5,
22032
22261
  debug: !!context2.debug
22033
22262
  }
22034
22263
  };
@@ -22504,7 +22733,7 @@ async function executeSingleCheck(checkId, context2, state, emitEvent, transitio
22504
22733
  __outputHistory: outputHistory,
22505
22734
  ai: {
22506
22735
  ...checkConfig.ai || {},
22507
- timeout: checkConfig.ai?.timeout || 6e5,
22736
+ timeout: checkConfig.ai?.timeout || 12e5,
22508
22737
  debug: !!context2.debug
22509
22738
  }
22510
22739
  };
@@ -22857,8 +23086,15 @@ function recordOnFinishRoutingEvent(args) {
22857
23086
  if (args.gotoEvent) attrs.goto_event = args.gotoEvent;
22858
23087
  addEvent("visor.routing", attrs);
22859
23088
  }
23089
+ function getHistoryLimit3() {
23090
+ const raw = process.env.VISOR_TEST_HISTORY_LIMIT || process.env.VISOR_OUTPUT_HISTORY_LIMIT;
23091
+ if (!raw) return void 0;
23092
+ const n = parseInt(raw, 10);
23093
+ return Number.isFinite(n) && n > 0 ? n : void 0;
23094
+ }
22860
23095
  function buildOutputHistoryFromJournal2(context2) {
22861
23096
  const outputHistory = /* @__PURE__ */ new Map();
23097
+ const limit = getHistoryLimit3();
22862
23098
  try {
22863
23099
  const snapshot = context2.journal.beginSnapshot();
22864
23100
  const allEntries = context2.journal.readVisible(context2.sessionId, snapshot, void 0);
@@ -22868,7 +23104,13 @@ function buildOutputHistoryFromJournal2(context2) {
22868
23104
  outputHistory.set(checkId, []);
22869
23105
  }
22870
23106
  const payload = entry.result.output !== void 0 ? entry.result.output : entry.result;
22871
- if (payload !== void 0) outputHistory.get(checkId).push(payload);
23107
+ if (payload !== void 0) {
23108
+ const arr = outputHistory.get(checkId);
23109
+ arr.push(payload);
23110
+ if (limit && arr.length > limit) {
23111
+ arr.splice(0, arr.length - limit);
23112
+ }
23113
+ }
22872
23114
  }
22873
23115
  } catch (error) {
22874
23116
  logger.debug(`[LevelDispatch] Error building output history: ${error}`);
@@ -23281,7 +23523,7 @@ async function executeCheckWithForEachItems2(checkId, forEachParent, forEachItem
23281
23523
  __outputHistory: outputHistory,
23282
23524
  ai: {
23283
23525
  ...checkConfig.ai || {},
23284
- timeout: checkConfig.ai?.timeout || 6e5,
23526
+ timeout: checkConfig.ai?.timeout || 12e5,
23285
23527
  debug: !!context2.debug
23286
23528
  }
23287
23529
  };
@@ -24057,10 +24299,20 @@ async function executeSingleCheck2(checkId, context2, state, emitEvent, transiti
24057
24299
  const dependencies = checkConfig?.depends_on || [];
24058
24300
  const depList = Array.isArray(dependencies) ? dependencies : [dependencies];
24059
24301
  const failedChecks = state.failedChecks;
24302
+ const allowedFailedDeps = state.allowedFailedDeps?.get(checkId);
24060
24303
  const tokens = depList.filter(Boolean);
24061
24304
  const groupSatisfied = (token) => {
24062
24305
  const options = token.includes("|") ? token.split("|").map((s) => s.trim()).filter(Boolean) : [token];
24063
24306
  for (const opt of options) {
24307
+ const isAllowedFailedDep = !!(allowedFailedDeps && allowedFailedDeps.has(opt));
24308
+ if (isAllowedFailedDep) {
24309
+ if (context2.debug) {
24310
+ logger.info(
24311
+ `[LevelDispatch] Allowing ${checkId} to run despite failed dependency ${opt} (on_fail.run)`
24312
+ );
24313
+ }
24314
+ return true;
24315
+ }
24064
24316
  const depCfg = context2.config.checks?.[opt];
24065
24317
  const cont = !!(depCfg && depCfg.continue_on_failure === true);
24066
24318
  const st = state.stats.get(opt);
@@ -24299,7 +24551,7 @@ async function executeSingleCheck2(checkId, context2, state, emitEvent, transiti
24299
24551
  checksMeta,
24300
24552
  ai: {
24301
24553
  ...checkConfig2.ai || {},
24302
- timeout: checkConfig2.ai?.timeout || 6e5,
24554
+ timeout: checkConfig2.ai?.timeout || 12e5,
24303
24555
  debug: !!context2.debug
24304
24556
  }
24305
24557
  };
@@ -25373,6 +25625,25 @@ var init_runner = __esm({
25373
25625
  }
25374
25626
  } catch {
25375
25627
  }
25628
+ if (event.type === "CheckCompleted") {
25629
+ try {
25630
+ const hook = this.context.executionContext?.hooks?.onCheckComplete;
25631
+ if (typeof hook === "function") {
25632
+ const checkConfig = this.context.config?.checks?.[event.checkId];
25633
+ hook({
25634
+ checkId: event.checkId,
25635
+ result: event.result,
25636
+ checkConfig: checkConfig ? {
25637
+ type: checkConfig.type,
25638
+ group: checkConfig.group,
25639
+ criticality: checkConfig.criticality,
25640
+ schema: checkConfig.schema
25641
+ } : void 0
25642
+ });
25643
+ }
25644
+ } catch {
25645
+ }
25646
+ }
25376
25647
  if (this.context.debug && event.type !== "StateTransition") {
25377
25648
  logger.debug(`[StateMachine] Event: ${event.type}`);
25378
25649
  }
@@ -26122,14 +26393,19 @@ var init_workspace_manager = __esm({
26122
26393
  constructor(sessionId, originalPath, config) {
26123
26394
  this.sessionId = sessionId;
26124
26395
  this.originalPath = originalPath;
26396
+ const configuredName = config?.name || process.env.VISOR_WORKSPACE_NAME;
26397
+ const configuredMainProjectName = config?.mainProjectName || process.env.VISOR_WORKSPACE_PROJECT;
26125
26398
  this.config = {
26126
26399
  enabled: true,
26127
26400
  basePath: process.env.VISOR_WORKSPACE_PATH || "/tmp/visor-workspaces",
26128
26401
  cleanupOnExit: true,
26402
+ name: configuredName,
26403
+ mainProjectName: configuredMainProjectName,
26129
26404
  ...config
26130
26405
  };
26131
26406
  this.basePath = this.config.basePath;
26132
- this.workspacePath = path18.join(this.basePath, sanitizePathComponent(this.sessionId));
26407
+ const workspaceDirName = sanitizePathComponent(this.config.name || this.sessionId);
26408
+ this.workspacePath = path18.join(this.basePath, workspaceDirName);
26133
26409
  }
26134
26410
  /**
26135
26411
  * Get or create a WorkspaceManager instance for a session
@@ -26186,7 +26462,10 @@ var init_workspace_manager = __esm({
26186
26462
  logger.info(`Initializing workspace: ${this.workspacePath}`);
26187
26463
  await fsp2.mkdir(this.workspacePath, { recursive: true });
26188
26464
  logger.debug(`Created workspace directory: ${this.workspacePath}`);
26189
- const mainProjectName = sanitizePathComponent(this.extractProjectName(this.originalPath));
26465
+ const configuredMainProjectName = this.config.mainProjectName;
26466
+ const mainProjectName = sanitizePathComponent(
26467
+ configuredMainProjectName || this.extractProjectName(this.originalPath)
26468
+ );
26190
26469
  this.usedNames.add(mainProjectName);
26191
26470
  const mainProjectPath = path18.join(this.workspacePath, mainProjectName);
26192
26471
  const isGitRepo = await this.isGitRepository(this.originalPath);
@@ -26462,12 +26741,21 @@ async function initializeWorkspace(context2) {
26462
26741
  const workspace = WorkspaceManager.getInstance(context2.sessionId, originalPath, {
26463
26742
  enabled: true,
26464
26743
  basePath: workspaceConfig?.base_path || process.env.VISOR_WORKSPACE_PATH || "/tmp/visor-workspaces",
26465
- cleanupOnExit: keepWorkspace ? false : workspaceConfig?.cleanup_on_exit !== false
26744
+ cleanupOnExit: keepWorkspace ? false : workspaceConfig?.cleanup_on_exit !== false,
26745
+ name: workspaceConfig?.name || process.env.VISOR_WORKSPACE_NAME,
26746
+ mainProjectName: workspaceConfig?.main_project_name || process.env.VISOR_WORKSPACE_PROJECT
26466
26747
  });
26467
26748
  const info = await workspace.initialize();
26468
26749
  context2.workspace = workspace;
26469
26750
  context2.workingDirectory = info.mainProjectPath;
26470
26751
  context2.originalWorkingDirectory = originalPath;
26752
+ try {
26753
+ process.env.VISOR_WORKSPACE_ROOT = info.workspacePath;
26754
+ process.env.VISOR_WORKSPACE_MAIN_PROJECT = info.mainProjectPath;
26755
+ process.env.VISOR_WORKSPACE_MAIN_PROJECT_NAME = info.mainProjectName;
26756
+ process.env.VISOR_ORIGINAL_WORKDIR = originalPath;
26757
+ } catch {
26758
+ }
26471
26759
  logger.info(`[Workspace] Initialized workspace: ${info.workspacePath}`);
26472
26760
  logger.debug(`[Workspace] Main project at: ${info.mainProjectPath}`);
26473
26761
  if (keepWorkspace) {
@@ -28381,6 +28669,7 @@ var init_slack_frontend = __esm({
28381
28669
  ackRef = null;
28382
28670
  ackName = "eyes";
28383
28671
  doneName = "thumbsup";
28672
+ errorNotified = false;
28384
28673
  constructor(config) {
28385
28674
  this.cfg = config || {};
28386
28675
  }
@@ -28413,6 +28702,14 @@ var init_slack_frontend = __esm({
28413
28702
  });
28414
28703
  })
28415
28704
  );
28705
+ this.subs.push(
28706
+ bus.on("CheckErrored", async (env) => {
28707
+ const ev = env && env.payload || env;
28708
+ const message = ev?.error?.message || "Execution error";
28709
+ await this.maybePostError(ctx, "Check failed", message, ev?.checkId).catch(() => {
28710
+ });
28711
+ })
28712
+ );
28416
28713
  this.subs.push(
28417
28714
  bus.on("StateTransition", async (env) => {
28418
28715
  const ev = env && env.payload || env;
@@ -28422,6 +28719,14 @@ var init_slack_frontend = __esm({
28422
28719
  }
28423
28720
  })
28424
28721
  );
28722
+ this.subs.push(
28723
+ bus.on("Shutdown", async (env) => {
28724
+ const ev = env && env.payload || env;
28725
+ const message = ev?.error?.message || "Fatal error";
28726
+ await this.maybePostError(ctx, "Run failed", message).catch(() => {
28727
+ });
28728
+ })
28729
+ );
28425
28730
  this.subs.push(
28426
28731
  bus.on("CheckScheduled", async () => {
28427
28732
  await this.ensureAcknowledgement(ctx).catch(() => {
@@ -28532,6 +28837,47 @@ var init_slack_frontend = __esm({
28532
28837
  }
28533
28838
  return null;
28534
28839
  }
28840
+ isTelemetryEnabled(ctx) {
28841
+ try {
28842
+ const anyCfg = ctx.config || {};
28843
+ const slackCfg = anyCfg.slack || {};
28844
+ const telemetryCfg = slackCfg.telemetry ?? this.cfg?.telemetry;
28845
+ return telemetryCfg === true || telemetryCfg && typeof telemetryCfg === "object" && telemetryCfg.enabled === true;
28846
+ } catch {
28847
+ return false;
28848
+ }
28849
+ }
28850
+ async maybePostError(ctx, title, message, checkId) {
28851
+ if (this.errorNotified) return;
28852
+ if (!this.isTelemetryEnabled(ctx)) return;
28853
+ const slack = this.getSlack(ctx);
28854
+ if (!slack) return;
28855
+ const payload = this.getInboundSlackPayload(ctx);
28856
+ const ev = payload?.event;
28857
+ const channel = String(ev?.channel || "");
28858
+ const threadTs = String(ev?.thread_ts || ev?.ts || ev?.event_ts || "");
28859
+ if (!channel || !threadTs) return;
28860
+ let text = `\u274C ${title}`;
28861
+ if (checkId) text += `
28862
+ Check: ${checkId}`;
28863
+ if (message) text += `
28864
+ ${message}`;
28865
+ const traceInfo = this.getTraceInfo();
28866
+ if (traceInfo?.traceId) {
28867
+ text += `
28868
+
28869
+ \`trace_id: ${traceInfo.traceId}\``;
28870
+ }
28871
+ const formattedText = formatSlackText(text);
28872
+ await slack.chat.postMessage({ channel, text: formattedText, thread_ts: threadTs });
28873
+ try {
28874
+ ctx.logger.info(
28875
+ `[slack-frontend] posted error notice to ${channel} thread=${threadTs} check=${checkId || "run"}`
28876
+ );
28877
+ } catch {
28878
+ }
28879
+ this.errorNotified = true;
28880
+ }
28535
28881
  async ensureAcknowledgement(ctx) {
28536
28882
  if (this.acked) return;
28537
28883
  const ref = this.getInboundSlackEvent(ctx);