@probelabs/visor 0.1.149 → 0.1.150

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 (123) hide show
  1. package/README.md +52 -1
  2. package/dist/909.index.js +27117 -0
  3. package/dist/ai-review-service.d.ts.map +1 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/docs/assistant-workflows.md +805 -0
  6. package/dist/docs/event-triggers.md +25 -0
  7. package/dist/docs/scheduler.md +156 -0
  8. package/dist/docs/slack-integration.md +48 -0
  9. package/dist/enterprise/scheduler/knex-store.d.ts +7 -1
  10. package/dist/enterprise/scheduler/knex-store.d.ts.map +1 -1
  11. package/dist/examples/code-talk-as-tool.yaml +76 -0
  12. package/dist/examples/code-talk-workflow.yaml +68 -0
  13. package/dist/examples/intent-router-workflow.yaml +66 -0
  14. package/dist/examples/slack-message-triggers.yaml +270 -0
  15. package/dist/generated/config-schema.d.ts +102 -7
  16. package/dist/generated/config-schema.d.ts.map +1 -1
  17. package/dist/generated/config-schema.json +116 -7
  18. package/dist/git-repository-analyzer.d.ts +8 -0
  19. package/dist/git-repository-analyzer.d.ts.map +1 -1
  20. package/dist/index.js +4030 -4735
  21. package/dist/output/traces/{run-2026-03-03T07-19-07-543Z.ndjson → run-2026-03-03T20-21-24-501Z.ndjson} +84 -84
  22. package/dist/{traces/run-2026-03-03T07-19-50-933Z.ndjson → output/traces/run-2026-03-03T20-22-08-701Z.ndjson} +1806 -1730
  23. package/dist/scheduler/message-trigger.d.ts +47 -0
  24. package/dist/scheduler/message-trigger.d.ts.map +1 -0
  25. package/dist/scheduler/schedule-store.d.ts +31 -1
  26. package/dist/scheduler/schedule-store.d.ts.map +1 -1
  27. package/dist/scheduler/schedule-tool.d.ts +17 -1
  28. package/dist/scheduler/schedule-tool.d.ts.map +1 -1
  29. package/dist/scheduler/store/sqlite-store.d.ts +7 -1
  30. package/dist/scheduler/store/sqlite-store.d.ts.map +1 -1
  31. package/dist/scheduler/store/types.d.ts +45 -0
  32. package/dist/scheduler/store/types.d.ts.map +1 -1
  33. package/dist/sdk/{check-provider-registry-LVLC4EPF.mjs → check-provider-registry-6D5CAX44.mjs} +6 -6
  34. package/dist/sdk/{check-provider-registry-X4OZJWPK.mjs → check-provider-registry-RRYBG6AU.mjs} +6 -6
  35. package/dist/sdk/{check-provider-registry-IYSUDKPB.mjs → check-provider-registry-YFC5KSJY.mjs} +6 -6
  36. package/dist/sdk/{chunk-V6GI4U2M.mjs → chunk-AADKUA6L.mjs} +585 -29
  37. package/dist/sdk/{chunk-6EXCUX7Y.mjs.map → chunk-AADKUA6L.mjs.map} +1 -1
  38. package/dist/sdk/{chunk-6EXCUX7Y.mjs → chunk-CKDFGNF4.mjs} +585 -29
  39. package/dist/sdk/{chunk-GFNXX64M.mjs.map → chunk-CKDFGNF4.mjs.map} +1 -1
  40. package/dist/sdk/{chunk-YYZAN5NK.mjs → chunk-FYK2DJK6.mjs} +106 -9
  41. package/dist/sdk/chunk-FYK2DJK6.mjs.map +1 -0
  42. package/dist/sdk/{chunk-RJLJUTSU.mjs → chunk-FZEQ744M.mjs} +2 -2
  43. package/dist/sdk/{chunk-Q6EPAJ6Z.mjs → chunk-GIAN7HCT.mjs} +3 -3
  44. package/dist/sdk/{chunk-CISJ6DJW.mjs → chunk-GLROSEYJ.mjs} +3 -3
  45. package/dist/sdk/{chunk-VLUGLWLA.mjs → chunk-H4HFH7HH.mjs} +3 -3
  46. package/dist/sdk/{chunk-VLUGLWLA.mjs.map → chunk-H4HFH7HH.mjs.map} +1 -1
  47. package/dist/sdk/{chunk-62TNF5PJ.mjs → chunk-PETLPNRA.mjs} +2 -2
  48. package/dist/sdk/{chunk-62TNF5PJ.mjs.map → chunk-PETLPNRA.mjs.map} +1 -1
  49. package/dist/sdk/{chunk-BR7DYA3S.mjs → chunk-SWO4W57C.mjs} +2 -2
  50. package/dist/sdk/{chunk-GFNXX64M.mjs → chunk-YY3KADY2.mjs} +585 -29
  51. package/dist/sdk/{chunk-V6GI4U2M.mjs.map → chunk-YY3KADY2.mjs.map} +1 -1
  52. package/dist/sdk/{config-KQH254CA.mjs → config-MTEIGCOQ.mjs} +2 -2
  53. package/dist/sdk/{failure-condition-evaluator-IVCTD4BZ.mjs → failure-condition-evaluator-P6QUFLIN.mjs} +3 -3
  54. package/dist/sdk/{failure-condition-evaluator-LZ2AG5PY.mjs → failure-condition-evaluator-XV2ZMFFY.mjs} +3 -3
  55. package/dist/sdk/{git-repository-analyzer-QFMW6WIS.mjs → git-repository-analyzer-TWNJUN42.mjs} +34 -3
  56. package/dist/sdk/git-repository-analyzer-TWNJUN42.mjs.map +1 -0
  57. package/dist/sdk/{github-frontend-DFT5G32K.mjs → github-frontend-A2R7D4N6.mjs} +3 -3
  58. package/dist/sdk/{github-frontend-S523EEJB.mjs → github-frontend-GSB2P5PE.mjs} +3 -3
  59. package/dist/sdk/{host-7YKRMOUJ.mjs → host-CLPM2WVQ.mjs} +2 -2
  60. package/dist/sdk/{host-H7IX4GBK.mjs → host-MR7L57QI.mjs} +2 -2
  61. package/dist/sdk/{routing-LU5PAREW.mjs → routing-KLVK2MJZ.mjs} +4 -4
  62. package/dist/sdk/{routing-ZMBKWMVI.mjs → routing-V3MYZAOH.mjs} +4 -4
  63. package/dist/sdk/{schedule-tool-EOMZFICZ.mjs → schedule-tool-HYM55K3H.mjs} +6 -6
  64. package/dist/sdk/{schedule-tool-NX75VKGA.mjs → schedule-tool-NYLNJUEW.mjs} +6 -6
  65. package/dist/sdk/{schedule-tool-CDVUSZEG.mjs → schedule-tool-PRXFAV4K.mjs} +6 -6
  66. package/dist/sdk/{schedule-tool-handler-3FJHDIPG.mjs → schedule-tool-handler-EFQIYD3W.mjs} +6 -6
  67. package/dist/sdk/{schedule-tool-handler-KKN7XJYT.mjs → schedule-tool-handler-HVWCEGQ6.mjs} +6 -6
  68. package/dist/sdk/{schedule-tool-handler-QNZG55DX.mjs → schedule-tool-handler-M5YI573R.mjs} +6 -6
  69. package/dist/sdk/sdk.d.mts +38 -1
  70. package/dist/sdk/sdk.d.ts +38 -1
  71. package/dist/sdk/sdk.js +706 -22
  72. package/dist/sdk/sdk.js.map +1 -1
  73. package/dist/sdk/sdk.mjs +5 -5
  74. package/dist/sdk/{trace-helpers-EJUIOP6L.mjs → trace-helpers-6NSZBC35.mjs} +2 -2
  75. package/dist/sdk/{trace-helpers-6ROJR7N3.mjs → trace-helpers-TORF3JD5.mjs} +2 -2
  76. package/dist/sdk/{workflow-check-provider-AGZ5JY2I.mjs → workflow-check-provider-3F6CBHVD.mjs} +6 -6
  77. package/dist/sdk/{workflow-check-provider-PTNUWM5W.mjs → workflow-check-provider-OXHMLICJ.mjs} +6 -6
  78. package/dist/sdk/{workflow-check-provider-6ERNNCNA.mjs → workflow-check-provider-XLH7N4FV.mjs} +6 -6
  79. package/dist/slack/socket-runner.d.ts +14 -0
  80. package/dist/slack/socket-runner.d.ts.map +1 -1
  81. package/dist/test-runner/core/flow-stage.d.ts.map +1 -1
  82. package/dist/test-runner/fixture-loader.d.ts +1 -1
  83. package/dist/test-runner/fixture-loader.d.ts.map +1 -1
  84. package/dist/test-runner/index.d.ts.map +1 -1
  85. package/dist/test-runner/validator.d.ts.map +1 -1
  86. package/dist/traces/{run-2026-03-03T07-19-07-543Z.ndjson → run-2026-03-03T20-21-24-501Z.ndjson} +84 -84
  87. package/dist/{output/traces/run-2026-03-03T07-19-50-933Z.ndjson → traces/run-2026-03-03T20-22-08-701Z.ndjson} +1806 -1730
  88. package/dist/types/config.d.ts +38 -1
  89. package/dist/types/config.d.ts.map +1 -1
  90. package/dist/utils/workspace-manager.d.ts +4 -0
  91. package/dist/utils/workspace-manager.d.ts.map +1 -1
  92. package/dist/utils/worktree-manager.d.ts +15 -1
  93. package/dist/utils/worktree-manager.d.ts.map +1 -1
  94. package/package.json +2 -2
  95. package/dist/sdk/chunk-YYZAN5NK.mjs.map +0 -1
  96. package/dist/sdk/git-repository-analyzer-QFMW6WIS.mjs.map +0 -1
  97. /package/dist/sdk/{check-provider-registry-IYSUDKPB.mjs.map → check-provider-registry-6D5CAX44.mjs.map} +0 -0
  98. /package/dist/sdk/{check-provider-registry-LVLC4EPF.mjs.map → check-provider-registry-RRYBG6AU.mjs.map} +0 -0
  99. /package/dist/sdk/{check-provider-registry-X4OZJWPK.mjs.map → check-provider-registry-YFC5KSJY.mjs.map} +0 -0
  100. /package/dist/sdk/{chunk-BR7DYA3S.mjs.map → chunk-FZEQ744M.mjs.map} +0 -0
  101. /package/dist/sdk/{chunk-CISJ6DJW.mjs.map → chunk-GIAN7HCT.mjs.map} +0 -0
  102. /package/dist/sdk/{chunk-Q6EPAJ6Z.mjs.map → chunk-GLROSEYJ.mjs.map} +0 -0
  103. /package/dist/sdk/{chunk-RJLJUTSU.mjs.map → chunk-SWO4W57C.mjs.map} +0 -0
  104. /package/dist/sdk/{config-KQH254CA.mjs.map → config-MTEIGCOQ.mjs.map} +0 -0
  105. /package/dist/sdk/{failure-condition-evaluator-IVCTD4BZ.mjs.map → failure-condition-evaluator-P6QUFLIN.mjs.map} +0 -0
  106. /package/dist/sdk/{failure-condition-evaluator-LZ2AG5PY.mjs.map → failure-condition-evaluator-XV2ZMFFY.mjs.map} +0 -0
  107. /package/dist/sdk/{github-frontend-DFT5G32K.mjs.map → github-frontend-A2R7D4N6.mjs.map} +0 -0
  108. /package/dist/sdk/{github-frontend-S523EEJB.mjs.map → github-frontend-GSB2P5PE.mjs.map} +0 -0
  109. /package/dist/sdk/{host-7YKRMOUJ.mjs.map → host-CLPM2WVQ.mjs.map} +0 -0
  110. /package/dist/sdk/{host-H7IX4GBK.mjs.map → host-MR7L57QI.mjs.map} +0 -0
  111. /package/dist/sdk/{routing-LU5PAREW.mjs.map → routing-KLVK2MJZ.mjs.map} +0 -0
  112. /package/dist/sdk/{routing-ZMBKWMVI.mjs.map → routing-V3MYZAOH.mjs.map} +0 -0
  113. /package/dist/sdk/{schedule-tool-CDVUSZEG.mjs.map → schedule-tool-HYM55K3H.mjs.map} +0 -0
  114. /package/dist/sdk/{schedule-tool-EOMZFICZ.mjs.map → schedule-tool-NYLNJUEW.mjs.map} +0 -0
  115. /package/dist/sdk/{schedule-tool-NX75VKGA.mjs.map → schedule-tool-PRXFAV4K.mjs.map} +0 -0
  116. /package/dist/sdk/{schedule-tool-handler-3FJHDIPG.mjs.map → schedule-tool-handler-EFQIYD3W.mjs.map} +0 -0
  117. /package/dist/sdk/{schedule-tool-handler-KKN7XJYT.mjs.map → schedule-tool-handler-HVWCEGQ6.mjs.map} +0 -0
  118. /package/dist/sdk/{schedule-tool-handler-QNZG55DX.mjs.map → schedule-tool-handler-M5YI573R.mjs.map} +0 -0
  119. /package/dist/sdk/{trace-helpers-6ROJR7N3.mjs.map → trace-helpers-6NSZBC35.mjs.map} +0 -0
  120. /package/dist/sdk/{trace-helpers-EJUIOP6L.mjs.map → trace-helpers-TORF3JD5.mjs.map} +0 -0
  121. /package/dist/sdk/{workflow-check-provider-6ERNNCNA.mjs.map → workflow-check-provider-3F6CBHVD.mjs.map} +0 -0
  122. /package/dist/sdk/{workflow-check-provider-AGZ5JY2I.mjs.map → workflow-check-provider-OXHMLICJ.mjs.map} +0 -0
  123. /package/dist/sdk/{workflow-check-provider-PTNUWM5W.mjs.map → workflow-check-provider-XLH7N4FV.mjs.map} +0 -0
package/dist/sdk/sdk.js CHANGED
@@ -646,7 +646,7 @@ var require_package = __commonJS({
646
646
  "package.json"(exports2, module2) {
647
647
  module2.exports = {
648
648
  name: "@probelabs/visor",
649
- version: "0.1.149",
649
+ version: "0.1.150",
650
650
  main: "dist/index.js",
651
651
  bin: {
652
652
  visor: "./dist/index.js"
@@ -760,7 +760,7 @@ var require_package = __commonJS({
760
760
  "@opentelemetry/sdk-node": "^0.203.0",
761
761
  "@opentelemetry/sdk-trace-base": "^1.30.1",
762
762
  "@opentelemetry/semantic-conventions": "^1.30.1",
763
- "@probelabs/probe": "^0.6.0-rc264",
763
+ "@probelabs/probe": "^0.6.0-rc266",
764
764
  "@types/commander": "^2.12.0",
765
765
  "@types/uuid": "^10.0.0",
766
766
  acorn: "^8.16.0",
@@ -7192,6 +7192,23 @@ function createProbeTracerAdapter(fallbackTracer) {
7192
7192
  }
7193
7193
  }
7194
7194
  },
7195
+ recordToolResult: (toolName, result, success, durationMs, metadata) => {
7196
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
7197
+ emitEvent("tool.result", {
7198
+ "tool.name": toolName,
7199
+ "tool.result": resultStr.substring(0, 1e4),
7200
+ "tool.result.length": resultStr.length,
7201
+ "tool.duration_ms": durationMs,
7202
+ "tool.success": success,
7203
+ ...metadata || {}
7204
+ });
7205
+ if (fallback && typeof fallback.recordToolResult === "function") {
7206
+ try {
7207
+ fallback.recordToolResult(toolName, result, success, durationMs, metadata);
7208
+ } catch {
7209
+ }
7210
+ }
7211
+ },
7195
7212
  recordDelegationEvent: (phase, attrs) => {
7196
7213
  emitEvent(`delegation.${phase}`, attrs);
7197
7214
  if (fallback && typeof fallback.recordDelegationEvent === "function") {
@@ -13146,7 +13163,7 @@ var init_config_schema = __esm({
13146
13163
  description: "Arguments/inputs for the workflow"
13147
13164
  },
13148
13165
  overrides: {
13149
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E%3E",
13166
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255%3E%3E",
13150
13167
  description: "Override specific step configurations in the workflow"
13151
13168
  },
13152
13169
  output_mapping: {
@@ -13162,7 +13179,7 @@ var init_config_schema = __esm({
13162
13179
  description: "Config file path - alternative to workflow ID (loads a Visor config file as workflow)"
13163
13180
  },
13164
13181
  workflow_overrides: {
13165
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E%3E",
13182
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255%3E%3E",
13166
13183
  description: "Alias for overrides - workflow step overrides (backward compatibility)"
13167
13184
  },
13168
13185
  ref: {
@@ -13344,7 +13361,8 @@ var init_config_schema = __esm({
13344
13361
  "issue_comment",
13345
13362
  "manual",
13346
13363
  "schedule",
13347
- "webhook_received"
13364
+ "webhook_received",
13365
+ "slack_message"
13348
13366
  ],
13349
13367
  description: "Valid event triggers for checks"
13350
13368
  },
@@ -13850,7 +13868,7 @@ var init_config_schema = __esm({
13850
13868
  description: "Custom output name (defaults to workflow name)"
13851
13869
  },
13852
13870
  overrides: {
13853
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E%3E",
13871
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255%3E%3E",
13854
13872
  description: "Step overrides"
13855
13873
  },
13856
13874
  output_mapping: {
@@ -13865,13 +13883,13 @@ var init_config_schema = __esm({
13865
13883
  "^x-": {}
13866
13884
  }
13867
13885
  },
13868
- "Record<string,Partial<interface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867>>": {
13886
+ "Record<string,Partial<interface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255>>": {
13869
13887
  type: "object",
13870
13888
  additionalProperties: {
13871
- $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E"
13889
+ $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255%3E"
13872
13890
  }
13873
13891
  },
13874
- "Partial<interface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867>": {
13892
+ "Partial<interface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255>": {
13875
13893
  type: "object",
13876
13894
  additionalProperties: false
13877
13895
  },
@@ -14753,6 +14771,10 @@ var init_config_schema = __esm({
14753
14771
  cron: {
14754
14772
  $ref: "#/definitions/Record%3Cstring%2CStaticCronJob%3E",
14755
14773
  description: "Static cron jobs defined in configuration (always executed)"
14774
+ },
14775
+ on_message: {
14776
+ $ref: "#/definitions/Record%3Cstring%2CSlackMessageTrigger%3E",
14777
+ description: "Slack message triggers for reactive workflow execution"
14756
14778
  }
14757
14779
  },
14758
14780
  additionalProperties: false,
@@ -15005,6 +15027,97 @@ var init_config_schema = __esm({
15005
15027
  "^x-": {}
15006
15028
  }
15007
15029
  },
15030
+ "Record<string,SlackMessageTrigger>": {
15031
+ type: "object",
15032
+ additionalProperties: {
15033
+ $ref: "#/definitions/SlackMessageTrigger"
15034
+ }
15035
+ },
15036
+ SlackMessageTrigger: {
15037
+ type: "object",
15038
+ properties: {
15039
+ channels: {
15040
+ type: "array",
15041
+ items: {
15042
+ type: "string"
15043
+ },
15044
+ description: 'Channel IDs to monitor (supports wildcard suffix, e.g., "CENG*")'
15045
+ },
15046
+ from: {
15047
+ type: "array",
15048
+ items: {
15049
+ type: "string"
15050
+ },
15051
+ description: "Only trigger on messages from these user IDs"
15052
+ },
15053
+ from_bots: {
15054
+ type: "boolean",
15055
+ description: "Allow bot messages to trigger (default: false)"
15056
+ },
15057
+ contains: {
15058
+ type: "array",
15059
+ items: {
15060
+ type: "string"
15061
+ },
15062
+ description: "Keyword match - any keyword triggers (case-insensitive OR)"
15063
+ },
15064
+ match: {
15065
+ type: "string",
15066
+ description: "Regex pattern to match against message text"
15067
+ },
15068
+ threads: {
15069
+ type: "string",
15070
+ enum: ["root_only", "thread_only", "any"],
15071
+ description: "Thread scope filter (default: 'any')"
15072
+ },
15073
+ workflow: {
15074
+ type: "string",
15075
+ description: "Workflow/check ID to execute"
15076
+ },
15077
+ inputs: {
15078
+ $ref: "#/definitions/Record%3Cstring%2Cunknown%3E",
15079
+ description: "Optional workflow inputs"
15080
+ },
15081
+ output: {
15082
+ type: "object",
15083
+ properties: {
15084
+ type: {
15085
+ type: "string",
15086
+ enum: ["slack", "github", "webhook", "none"],
15087
+ description: "Output type: slack, github, webhook, or none"
15088
+ },
15089
+ target: {
15090
+ type: "string",
15091
+ description: "Target (channel name, repo, URL)"
15092
+ },
15093
+ thread_id: {
15094
+ type: "string",
15095
+ description: "Thread ID for threaded outputs"
15096
+ }
15097
+ },
15098
+ required: ["type"],
15099
+ additionalProperties: false,
15100
+ description: "Output destination configuration",
15101
+ patternProperties: {
15102
+ "^x-": {}
15103
+ }
15104
+ },
15105
+ description: {
15106
+ type: "string",
15107
+ description: "Description for logging/display"
15108
+ },
15109
+ enabled: {
15110
+ type: "boolean",
15111
+ description: "Enable/disable this trigger (default: true)"
15112
+ }
15113
+ },
15114
+ required: ["workflow"],
15115
+ additionalProperties: false,
15116
+ description: "Slack message trigger for reactive workflow execution Triggers workflows based on Slack messages matching configured filters",
15117
+ patternProperties: {
15118
+ "^x-": {}
15119
+ }
15120
+ },
15008
15121
  PolicyConfig: {
15009
15122
  type: "object",
15010
15123
  properties: {
@@ -15146,7 +15259,8 @@ var init_config = __esm({
15146
15259
  "issue_comment",
15147
15260
  "manual",
15148
15261
  "schedule",
15149
- "webhook_received"
15262
+ "webhook_received",
15263
+ "slack_message"
15150
15264
  ];
15151
15265
  ConfigManager = class {
15152
15266
  validCheckTypes = [
@@ -17713,6 +17827,48 @@ function fromDbRow(row) {
17713
17827
  previousResponse: row.previous_response ?? void 0
17714
17828
  };
17715
17829
  }
17830
+ function toTriggerRow(trigger) {
17831
+ return {
17832
+ id: trigger.id,
17833
+ creator_id: trigger.creatorId,
17834
+ creator_context: trigger.creatorContext ?? null,
17835
+ creator_name: trigger.creatorName ?? null,
17836
+ description: trigger.description ?? null,
17837
+ channels: trigger.channels ? JSON.stringify(trigger.channels) : null,
17838
+ from_users: trigger.fromUsers ? JSON.stringify(trigger.fromUsers) : null,
17839
+ from_bots: trigger.fromBots ? 1 : 0,
17840
+ contains: trigger.contains ? JSON.stringify(trigger.contains) : null,
17841
+ match_pattern: trigger.matchPattern ?? null,
17842
+ threads: trigger.threads,
17843
+ workflow: trigger.workflow,
17844
+ inputs: trigger.inputs ? JSON.stringify(trigger.inputs) : null,
17845
+ output_context: trigger.outputContext ? JSON.stringify(trigger.outputContext) : null,
17846
+ status: trigger.status,
17847
+ enabled: trigger.enabled ? 1 : 0,
17848
+ created_at: trigger.createdAt
17849
+ };
17850
+ }
17851
+ function fromTriggerRow(row) {
17852
+ return {
17853
+ id: row.id,
17854
+ creatorId: row.creator_id,
17855
+ creatorContext: row.creator_context ?? void 0,
17856
+ creatorName: row.creator_name ?? void 0,
17857
+ description: row.description ?? void 0,
17858
+ channels: safeJsonParse(row.channels),
17859
+ fromUsers: safeJsonParse(row.from_users),
17860
+ fromBots: row.from_bots === 1,
17861
+ contains: safeJsonParse(row.contains),
17862
+ matchPattern: row.match_pattern ?? void 0,
17863
+ threads: row.threads,
17864
+ workflow: row.workflow,
17865
+ inputs: safeJsonParse(row.inputs),
17866
+ outputContext: safeJsonParse(row.output_context),
17867
+ status: row.status,
17868
+ enabled: row.enabled === 1,
17869
+ createdAt: row.created_at
17870
+ };
17871
+ }
17716
17872
  var import_path5, import_fs4, import_uuid, SqliteStoreBackend;
17717
17873
  var init_sqlite_store = __esm({
17718
17874
  "src/scheduler/store/sqlite-store.ts"() {
@@ -17805,6 +17961,32 @@ var init_sqlite_store = __esm({
17805
17961
  acquired_at BIGINT NOT NULL,
17806
17962
  expires_at BIGINT NOT NULL
17807
17963
  );
17964
+
17965
+ CREATE TABLE IF NOT EXISTS message_triggers (
17966
+ id VARCHAR(36) PRIMARY KEY,
17967
+ creator_id VARCHAR(255) NOT NULL,
17968
+ creator_context VARCHAR(255),
17969
+ creator_name VARCHAR(255),
17970
+ description TEXT,
17971
+ channels TEXT,
17972
+ from_users TEXT,
17973
+ from_bots BOOLEAN NOT NULL DEFAULT 0,
17974
+ contains TEXT,
17975
+ match_pattern TEXT,
17976
+ threads VARCHAR(20) NOT NULL DEFAULT 'any',
17977
+ workflow VARCHAR(255) NOT NULL,
17978
+ inputs TEXT,
17979
+ output_context TEXT,
17980
+ status VARCHAR(20) NOT NULL DEFAULT 'active',
17981
+ enabled BOOLEAN NOT NULL DEFAULT 1,
17982
+ created_at BIGINT NOT NULL
17983
+ );
17984
+
17985
+ CREATE INDEX IF NOT EXISTS idx_message_triggers_creator
17986
+ ON message_triggers(creator_id);
17987
+
17988
+ CREATE INDEX IF NOT EXISTS idx_message_triggers_status
17989
+ ON message_triggers(status);
17808
17990
  `);
17809
17991
  }
17810
17992
  // --- Helpers ---
@@ -18086,6 +18268,114 @@ var init_sqlite_store = __esm({
18086
18268
  }
18087
18269
  async flush() {
18088
18270
  }
18271
+ // --- Message Triggers ---
18272
+ async createTrigger(trigger) {
18273
+ const db = this.getDb();
18274
+ const newTrigger = {
18275
+ ...trigger,
18276
+ id: (0, import_uuid.v4)(),
18277
+ createdAt: Date.now()
18278
+ };
18279
+ const row = toTriggerRow(newTrigger);
18280
+ db.prepare(
18281
+ `INSERT INTO message_triggers (
18282
+ id, creator_id, creator_context, creator_name, description,
18283
+ channels, from_users, from_bots, contains, match_pattern,
18284
+ threads, workflow, inputs, output_context,
18285
+ status, enabled, created_at
18286
+ ) VALUES (
18287
+ ?, ?, ?, ?, ?,
18288
+ ?, ?, ?, ?, ?,
18289
+ ?, ?, ?, ?,
18290
+ ?, ?, ?
18291
+ )`
18292
+ ).run(
18293
+ row.id,
18294
+ row.creator_id,
18295
+ row.creator_context,
18296
+ row.creator_name,
18297
+ row.description,
18298
+ row.channels,
18299
+ row.from_users,
18300
+ row.from_bots,
18301
+ row.contains,
18302
+ row.match_pattern,
18303
+ row.threads,
18304
+ row.workflow,
18305
+ row.inputs,
18306
+ row.output_context,
18307
+ row.status,
18308
+ row.enabled,
18309
+ row.created_at
18310
+ );
18311
+ logger.info(
18312
+ `[SqliteStore] Created message trigger ${newTrigger.id} for user ${newTrigger.creatorId}`
18313
+ );
18314
+ return newTrigger;
18315
+ }
18316
+ async getTrigger(id) {
18317
+ const db = this.getDb();
18318
+ const row = db.prepare("SELECT * FROM message_triggers WHERE id = ?").get(id);
18319
+ return row ? fromTriggerRow(row) : void 0;
18320
+ }
18321
+ async updateTrigger(id, patch) {
18322
+ const db = this.getDb();
18323
+ const existing = db.prepare("SELECT * FROM message_triggers WHERE id = ?").get(id);
18324
+ if (!existing) return void 0;
18325
+ const current = fromTriggerRow(existing);
18326
+ const updated = {
18327
+ ...current,
18328
+ ...patch,
18329
+ id: current.id,
18330
+ createdAt: current.createdAt
18331
+ };
18332
+ const row = toTriggerRow(updated);
18333
+ db.prepare(
18334
+ `UPDATE message_triggers SET
18335
+ creator_id = ?, creator_context = ?, creator_name = ?, description = ?,
18336
+ channels = ?, from_users = ?, from_bots = ?, contains = ?, match_pattern = ?,
18337
+ threads = ?, workflow = ?, inputs = ?, output_context = ?,
18338
+ status = ?, enabled = ?
18339
+ WHERE id = ?`
18340
+ ).run(
18341
+ row.creator_id,
18342
+ row.creator_context,
18343
+ row.creator_name,
18344
+ row.description,
18345
+ row.channels,
18346
+ row.from_users,
18347
+ row.from_bots,
18348
+ row.contains,
18349
+ row.match_pattern,
18350
+ row.threads,
18351
+ row.workflow,
18352
+ row.inputs,
18353
+ row.output_context,
18354
+ row.status,
18355
+ row.enabled,
18356
+ row.id
18357
+ );
18358
+ return updated;
18359
+ }
18360
+ async deleteTrigger(id) {
18361
+ const db = this.getDb();
18362
+ const result = db.prepare("DELETE FROM message_triggers WHERE id = ?").run(id);
18363
+ if (result.changes > 0) {
18364
+ logger.info(`[SqliteStore] Deleted message trigger ${id}`);
18365
+ return true;
18366
+ }
18367
+ return false;
18368
+ }
18369
+ async getTriggersByCreator(creatorId) {
18370
+ const db = this.getDb();
18371
+ const rows = db.prepare("SELECT * FROM message_triggers WHERE creator_id = ?").all(creatorId);
18372
+ return rows.map(fromTriggerRow);
18373
+ }
18374
+ async getActiveTriggers() {
18375
+ const db = this.getDb();
18376
+ const rows = db.prepare("SELECT * FROM message_triggers WHERE status = 'active' AND enabled = 1").all();
18377
+ return rows.map(fromTriggerRow);
18378
+ }
18089
18379
  };
18090
18380
  }
18091
18381
  });
@@ -18205,11 +18495,19 @@ var init_schedule_store = __esm({
18205
18495
  init_json_migrator();
18206
18496
  ScheduleStore = class _ScheduleStore {
18207
18497
  static instance;
18498
+ static onTriggersChanged;
18208
18499
  backend = null;
18209
18500
  initialized = false;
18210
18501
  limits;
18211
18502
  config;
18212
18503
  externalBackend = null;
18504
+ /**
18505
+ * Register a callback to be invoked when message triggers change (create/update/delete).
18506
+ * Used by SlackSocketRunner to rebuild its evaluator.
18507
+ */
18508
+ static setTriggersChangedCallback(cb) {
18509
+ _ScheduleStore.onTriggersChanged = cb;
18510
+ }
18213
18511
  constructor(config, limits, backend) {
18214
18512
  this.config = config || {};
18215
18513
  this.limits = {
@@ -18371,6 +18669,53 @@ var init_schedule_store = __esm({
18371
18669
  }
18372
18670
  return this.backend;
18373
18671
  }
18672
+ // --- Message Trigger Methods ---
18673
+ /**
18674
+ * Create a new message trigger
18675
+ */
18676
+ async createTriggerAsync(trigger) {
18677
+ const result = await this.getBackend().createTrigger(trigger);
18678
+ _ScheduleStore.onTriggersChanged?.();
18679
+ return result;
18680
+ }
18681
+ /**
18682
+ * Get a trigger by ID
18683
+ */
18684
+ async getTriggerAsync(id) {
18685
+ return this.getBackend().getTrigger(id);
18686
+ }
18687
+ /**
18688
+ * Update a trigger
18689
+ */
18690
+ async updateTriggerAsync(id, patch) {
18691
+ const result = await this.getBackend().updateTrigger(id, patch);
18692
+ if (result) {
18693
+ _ScheduleStore.onTriggersChanged?.();
18694
+ }
18695
+ return result;
18696
+ }
18697
+ /**
18698
+ * Delete a trigger
18699
+ */
18700
+ async deleteTriggerAsync(id) {
18701
+ const result = await this.getBackend().deleteTrigger(id);
18702
+ if (result) {
18703
+ _ScheduleStore.onTriggersChanged?.();
18704
+ }
18705
+ return result;
18706
+ }
18707
+ /**
18708
+ * Get all triggers for a specific creator
18709
+ */
18710
+ async getTriggersByCreatorAsync(creatorId) {
18711
+ return this.getBackend().getTriggersByCreator(creatorId);
18712
+ }
18713
+ /**
18714
+ * Get all active triggers
18715
+ */
18716
+ async getActiveTriggersAsync() {
18717
+ return this.getBackend().getActiveTriggers();
18718
+ }
18374
18719
  /**
18375
18720
  * Shut down the backend cleanly
18376
18721
  */
@@ -19611,11 +19956,19 @@ async function handleScheduleAction(args, context2) {
19611
19956
  return handlePauseResume(args, context2, store, "paused");
19612
19957
  case "resume":
19613
19958
  return handlePauseResume(args, context2, store, "active");
19959
+ case "create_trigger":
19960
+ return handleCreateTrigger(args, context2, store);
19961
+ case "list_triggers":
19962
+ return handleListTriggers(context2, store);
19963
+ case "delete_trigger":
19964
+ return handleDeleteTrigger(args, context2, store);
19965
+ case "update_trigger":
19966
+ return handleUpdateTrigger(args, context2, store);
19614
19967
  default:
19615
19968
  return {
19616
19969
  success: false,
19617
19970
  message: `Unknown action: ${args.action}`,
19618
- error: `Supported actions: create, list, cancel, pause, resume`
19971
+ error: `Supported actions: create, list, cancel, pause, resume, create_trigger, list_triggers, delete_trigger, update_trigger`
19619
19972
  };
19620
19973
  }
19621
19974
  }
@@ -19858,6 +20211,172 @@ async function handlePauseResume(args, context2, store, newStatus) {
19858
20211
  schedule: updated
19859
20212
  };
19860
20213
  }
20214
+ function formatTrigger(trigger) {
20215
+ const channels = trigger.channels?.length ? trigger.channels.join(", ") : "all";
20216
+ const filters = [];
20217
+ if (trigger.contains?.length) filters.push(`contains: ${trigger.contains.join(", ")}`);
20218
+ if (trigger.matchPattern) filters.push(`match: /${trigger.matchPattern}/`);
20219
+ if (trigger.fromBots) filters.push("bots: yes");
20220
+ const filterStr = filters.length ? ` [${filters.join("; ")}]` : "";
20221
+ const status = trigger.enabled ? "" : " (disabled)";
20222
+ return `\`${trigger.id.substring(0, 8)}\` - channels: ${channels} \u2192 workflow: "${trigger.workflow}"${filterStr}${status}`;
20223
+ }
20224
+ async function handleCreateTrigger(args, context2, store) {
20225
+ if (!args.workflow) {
20226
+ return {
20227
+ success: false,
20228
+ message: "Missing workflow",
20229
+ error: "Please specify the workflow to run when the trigger fires."
20230
+ };
20231
+ }
20232
+ if (context2.availableWorkflows && !context2.availableWorkflows.includes(args.workflow)) {
20233
+ return {
20234
+ success: false,
20235
+ message: `Workflow "${args.workflow}" not found`,
20236
+ error: `Available workflows: ${context2.availableWorkflows.slice(0, 5).join(", ")}${context2.availableWorkflows.length > 5 ? "..." : ""}`
20237
+ };
20238
+ }
20239
+ if ((!args.trigger_channels || args.trigger_channels.length === 0) && (!args.trigger_contains || args.trigger_contains.length === 0) && !args.trigger_match) {
20240
+ return {
20241
+ success: false,
20242
+ message: "Missing trigger filters",
20243
+ error: "Please specify at least one filter: trigger_channels, trigger_contains, or trigger_match."
20244
+ };
20245
+ }
20246
+ const permissionCheck = checkSchedulePermissions(context2, args.workflow);
20247
+ if (!permissionCheck.allowed) {
20248
+ return {
20249
+ success: false,
20250
+ message: "Permission denied",
20251
+ error: permissionCheck.reason || "You do not have permission to create triggers for this workflow"
20252
+ };
20253
+ }
20254
+ try {
20255
+ const trigger = await store.createTriggerAsync({
20256
+ creatorId: context2.userId,
20257
+ creatorContext: context2.contextType,
20258
+ creatorName: context2.userName,
20259
+ description: args.trigger_description,
20260
+ channels: args.trigger_channels,
20261
+ fromBots: args.trigger_from_bots ?? false,
20262
+ contains: args.trigger_contains,
20263
+ matchPattern: args.trigger_match,
20264
+ threads: args.trigger_threads ?? "any",
20265
+ workflow: args.workflow,
20266
+ inputs: args.workflow_inputs,
20267
+ status: "active",
20268
+ enabled: true
20269
+ });
20270
+ logger.info(
20271
+ `[ScheduleTool] Created message trigger ${trigger.id} for user ${context2.userId}: workflow="${args.workflow}"`
20272
+ );
20273
+ return {
20274
+ success: true,
20275
+ message: `**Message trigger created!**
20276
+
20277
+ **Workflow**: ${trigger.workflow}
20278
+ **Channels**: ${trigger.channels?.join(", ") || "all"}
20279
+ ${trigger.contains?.length ? `**Contains**: ${trigger.contains.join(", ")}
20280
+ ` : ""}${trigger.matchPattern ? `**Pattern**: /${trigger.matchPattern}/
20281
+ ` : ""}${trigger.description ? `**Description**: ${trigger.description}
20282
+ ` : ""}
20283
+ ID: \`${trigger.id.substring(0, 8)}\``
20284
+ };
20285
+ } catch (error) {
20286
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
20287
+ logger.warn(`[ScheduleTool] Failed to create trigger: ${errorMsg}`);
20288
+ return {
20289
+ success: false,
20290
+ message: `Failed to create trigger: ${errorMsg}`,
20291
+ error: errorMsg
20292
+ };
20293
+ }
20294
+ }
20295
+ async function handleListTriggers(context2, store) {
20296
+ const triggers = await store.getTriggersByCreatorAsync(context2.userId);
20297
+ const active = triggers.filter((t) => t.status !== "deleted");
20298
+ if (active.length === 0) {
20299
+ return {
20300
+ success: true,
20301
+ message: `You don't have any message triggers.
20302
+
20303
+ To create one: "watch #channel for messages containing 'error' and run %my-workflow"`
20304
+ };
20305
+ }
20306
+ const lines = active.map((t, i) => `${i + 1}. ${formatTrigger(t)}`);
20307
+ return {
20308
+ success: true,
20309
+ message: `**Your message triggers:**
20310
+
20311
+ ${lines.join("\n")}
20312
+
20313
+ To delete: "delete trigger <id>"
20314
+ To disable: "disable trigger <id>"`
20315
+ };
20316
+ }
20317
+ async function handleDeleteTrigger(args, context2, store) {
20318
+ if (!args.trigger_id) {
20319
+ return {
20320
+ success: false,
20321
+ message: "Missing trigger ID",
20322
+ error: "Please specify which trigger to delete."
20323
+ };
20324
+ }
20325
+ const userTriggers = await store.getTriggersByCreatorAsync(context2.userId);
20326
+ let trigger = userTriggers.find((t) => t.id === args.trigger_id);
20327
+ if (!trigger) {
20328
+ trigger = userTriggers.find((t) => t.id.startsWith(args.trigger_id));
20329
+ }
20330
+ if (!trigger) {
20331
+ return {
20332
+ success: false,
20333
+ message: "Trigger not found",
20334
+ error: `Could not find trigger with ID "${args.trigger_id}" in your triggers. Use "list my triggers" to see your triggers.`
20335
+ };
20336
+ }
20337
+ await store.deleteTriggerAsync(trigger.id);
20338
+ logger.info(`[ScheduleTool] Deleted trigger ${trigger.id} for user ${context2.userId}`);
20339
+ return {
20340
+ success: true,
20341
+ message: `**Trigger deleted!**
20342
+
20343
+ Was: watching for "${trigger.workflow}" ${trigger.channels?.length ? `in ${trigger.channels.join(", ")}` : ""}`
20344
+ };
20345
+ }
20346
+ async function handleUpdateTrigger(args, context2, store) {
20347
+ if (!args.trigger_id) {
20348
+ return {
20349
+ success: false,
20350
+ message: "Missing trigger ID",
20351
+ error: "Please specify which trigger to update."
20352
+ };
20353
+ }
20354
+ const userTriggers = await store.getTriggersByCreatorAsync(context2.userId);
20355
+ let trigger = userTriggers.find((t) => t.id === args.trigger_id);
20356
+ if (!trigger) {
20357
+ trigger = userTriggers.find((t) => t.id.startsWith(args.trigger_id));
20358
+ }
20359
+ if (!trigger) {
20360
+ return {
20361
+ success: false,
20362
+ message: "Trigger not found",
20363
+ error: `Could not find trigger with ID "${args.trigger_id}" in your triggers.`
20364
+ };
20365
+ }
20366
+ const patch = {};
20367
+ if (args.trigger_enabled !== void 0) {
20368
+ patch.enabled = args.trigger_enabled;
20369
+ }
20370
+ await store.updateTriggerAsync(trigger.id, patch);
20371
+ const action = args.trigger_enabled === false ? "disabled" : "enabled";
20372
+ logger.info(`[ScheduleTool] ${action} trigger ${trigger.id} for user ${context2.userId}`);
20373
+ return {
20374
+ success: true,
20375
+ message: `**Trigger ${action}!**
20376
+
20377
+ "${trigger.workflow}" ${trigger.channels?.length ? `in ${trigger.channels.join(", ")}` : ""}`
20378
+ };
20379
+ }
19861
20380
  function getScheduleToolDefinition() {
19862
20381
  return {
19863
20382
  name: "schedule",
@@ -19877,6 +20396,17 @@ ACTIONS:
19877
20396
  - cancel: Remove a schedule by ID
19878
20397
  - pause/resume: Temporarily disable/enable a schedule
19879
20398
 
20399
+ MESSAGE-BASED TRIGGERS:
20400
+ In addition to time-based schedules, this tool can create/manage message-based triggers that react to
20401
+ Slack messages in specific channels. Use the create_trigger, list_triggers, delete_trigger, and update_trigger
20402
+ actions for this. Message triggers fire workflows based on message content, channel, sender, and thread scope.
20403
+
20404
+ TRIGGER ACTIONS:
20405
+ - create_trigger: Create a new message trigger (requires workflow + at least one filter)
20406
+ - list_triggers: Show user's message triggers
20407
+ - delete_trigger: Remove a trigger by ID
20408
+ - update_trigger: Enable/disable a trigger by ID
20409
+
19880
20410
  FOR CREATE ACTION - Extract these from user's request:
19881
20411
  1. WHAT:
19882
20412
  - If user says "schedule %some-workflow ...", populate 'workflow' with "some-workflow".
@@ -19964,14 +20494,36 @@ User: "list my schedules"
19964
20494
  \u2192 { "action": "list" }
19965
20495
 
19966
20496
  User: "cancel schedule abc123"
19967
- \u2192 { "action": "cancel", "schedule_id": "abc123" }`,
20497
+ \u2192 { "action": "cancel", "schedule_id": "abc123" }
20498
+
20499
+ User: "watch #cicd for messages containing 'failed' and run %handle-cicd"
20500
+ \u2192 { "action": "create_trigger", "trigger_channels": ["C0CICD"], "trigger_contains": ["failed"], "workflow": "handle-cicd" }
20501
+
20502
+ User: "list my message triggers"
20503
+ \u2192 { "action": "list_triggers" }
20504
+
20505
+ User: "delete trigger abc123"
20506
+ \u2192 { "action": "delete_trigger", "trigger_id": "abc123" }
20507
+
20508
+ User: "disable trigger abc123"
20509
+ \u2192 { "action": "update_trigger", "trigger_id": "abc123", "trigger_enabled": false }`,
19968
20510
  inputSchema: {
19969
20511
  type: "object",
19970
20512
  properties: {
19971
20513
  action: {
19972
20514
  type: "string",
19973
- enum: ["create", "list", "cancel", "pause", "resume"],
19974
- description: "What to do: create new, list existing, cancel/pause/resume by ID"
20515
+ enum: [
20516
+ "create",
20517
+ "list",
20518
+ "cancel",
20519
+ "pause",
20520
+ "resume",
20521
+ "create_trigger",
20522
+ "list_triggers",
20523
+ "delete_trigger",
20524
+ "update_trigger"
20525
+ ],
20526
+ description: "What to do: create/list/cancel/pause/resume for time-based schedules; create_trigger/list_triggers/delete_trigger/update_trigger for message-based triggers"
19975
20527
  },
19976
20528
  // WHAT to do
19977
20529
  reminder_text: {
@@ -20021,6 +20573,42 @@ User: "cancel schedule abc123"
20021
20573
  schedule_id: {
20022
20574
  type: "string",
20023
20575
  description: "For cancel/pause/resume: the schedule ID to act on (first 8 chars is enough)"
20576
+ },
20577
+ // For message trigger actions
20578
+ trigger_channels: {
20579
+ type: "array",
20580
+ items: { type: "string" },
20581
+ description: 'For create_trigger: Slack channel IDs to monitor (e.g., ["C0CICD"]). Supports wildcard suffix (e.g., "CENG*").'
20582
+ },
20583
+ trigger_from_bots: {
20584
+ type: "boolean",
20585
+ description: "For create_trigger: allow bot messages to trigger (default: false)"
20586
+ },
20587
+ trigger_contains: {
20588
+ type: "array",
20589
+ items: { type: "string" },
20590
+ description: 'For create_trigger: keywords to match (case-insensitive OR). E.g., ["failed", "error"].'
20591
+ },
20592
+ trigger_match: {
20593
+ type: "string",
20594
+ description: "For create_trigger: regex pattern to match against message text."
20595
+ },
20596
+ trigger_threads: {
20597
+ type: "string",
20598
+ enum: ["root_only", "thread_only", "any"],
20599
+ description: 'For create_trigger: thread scope filter (default: "any").'
20600
+ },
20601
+ trigger_description: {
20602
+ type: "string",
20603
+ description: "For create_trigger: human-readable description of the trigger."
20604
+ },
20605
+ trigger_id: {
20606
+ type: "string",
20607
+ description: "For delete_trigger/update_trigger: the trigger ID to act on (first 8 chars is enough)."
20608
+ },
20609
+ trigger_enabled: {
20610
+ type: "boolean",
20611
+ description: "For update_trigger: set to false to disable, true to enable."
20024
20612
  }
20025
20613
  },
20026
20614
  required: ["action"]
@@ -46314,7 +46902,7 @@ var init_worktree_manager = __esm({
46314
46902
  await this.saveMetadata(worktreePath, updatedMetadata);
46315
46903
  if (options.clean) {
46316
46904
  logger.debug(`Cleaning updated worktree`);
46317
- await this.cleanWorktree(worktreePath);
46905
+ await this.cleanWorktree(worktreePath, latestCommit);
46318
46906
  }
46319
46907
  this.activeWorktrees.set(worktreeId, updatedMetadata);
46320
46908
  return {
@@ -46333,7 +46921,7 @@ var init_worktree_manager = __esm({
46333
46921
  }
46334
46922
  if (options.clean) {
46335
46923
  logger.debug(`Cleaning existing worktree`);
46336
- await this.cleanWorktree(worktreePath);
46924
+ await this.cleanWorktree(worktreePath, metadata2.commit);
46337
46925
  }
46338
46926
  this.activeWorktrees.set(worktreeId, metadata2);
46339
46927
  return {
@@ -46375,7 +46963,7 @@ var init_worktree_manager = __esm({
46375
46963
  await this.saveMetadata(worktreePath, updatedMetadata);
46376
46964
  if (options.clean) {
46377
46965
  logger.debug(`Cleaning updated worktree`);
46378
- await this.cleanWorktree(worktreePath);
46966
+ await this.cleanWorktree(worktreePath, newCommit);
46379
46967
  }
46380
46968
  this.activeWorktrees.set(worktreeId, updatedMetadata);
46381
46969
  logger.info(`Successfully updated worktree to ${ref} (${newCommit})`);
@@ -46465,13 +47053,54 @@ var init_worktree_manager = __esm({
46465
47053
  return true;
46466
47054
  }
46467
47055
  /**
46468
- * Clean worktree (reset and remove untracked files)
46469
- */
46470
- async cleanWorktree(worktreePath) {
47056
+ * Clean worktree (reset and remove untracked files).
47057
+ *
47058
+ * When `expectedCommit` is provided the worktree is first forced back to a
47059
+ * detached HEAD at that commit. This is essential because AI agents or user
47060
+ * commands may have created local branches inside the worktree, switching
47061
+ * HEAD away from the detached state. A plain `reset --hard HEAD` would
47062
+ * then reset to the *wrong* commit. After resetting, any local branches
47063
+ * that were created inside the worktree are deleted so they cannot leak
47064
+ * into future runs or PRs.
47065
+ */
47066
+ async cleanWorktree(worktreePath, expectedCommit) {
47067
+ if (expectedCommit) {
47068
+ const detachCmd = `git -C ${this.escapeShellArg(worktreePath)} checkout --detach ${this.escapeShellArg(expectedCommit)}`;
47069
+ const detachResult = await this.executeGitCommand(detachCmd, { timeout: 3e4 });
47070
+ if (detachResult.exitCode !== 0) {
47071
+ await this.executeGitCommand(`git -C ${this.escapeShellArg(worktreePath)} reset --hard`, {
47072
+ timeout: 1e4
47073
+ });
47074
+ await this.executeGitCommand(detachCmd, { timeout: 3e4 });
47075
+ }
47076
+ }
46471
47077
  const resetCmd = `git -C ${this.escapeShellArg(worktreePath)} reset --hard HEAD`;
46472
47078
  await this.executeGitCommand(resetCmd);
46473
47079
  const cleanCmd = `git -C ${this.escapeShellArg(worktreePath)} clean -fdx`;
46474
47080
  await this.executeGitCommand(cleanCmd);
47081
+ await this.deleteLocalBranches(worktreePath);
47082
+ }
47083
+ /**
47084
+ * Delete all local branches in a worktree.
47085
+ * Worktrees are always used in detached HEAD state, so any local branches
47086
+ * were unintentionally created and should be cleaned up.
47087
+ */
47088
+ async deleteLocalBranches(worktreePath) {
47089
+ const listCmd = `git -C ${this.escapeShellArg(worktreePath)} branch --list --format='%(refname:short)'`;
47090
+ const listResult = await this.executeGitCommand(listCmd, { timeout: 1e4 });
47091
+ if (listResult.exitCode !== 0 || !listResult.stdout.trim()) {
47092
+ return;
47093
+ }
47094
+ const branches = listResult.stdout.trim().split("\n").map((b) => b.trim()).filter((b) => b.length > 0);
47095
+ for (const branch of branches) {
47096
+ const deleteCmd = `git -C ${this.escapeShellArg(worktreePath)} branch -D ${this.escapeShellArg(branch)}`;
47097
+ const deleteResult = await this.executeGitCommand(deleteCmd, { timeout: 1e4 });
47098
+ if (deleteResult.exitCode === 0) {
47099
+ logger.debug(`Deleted local branch '${branch}' from worktree`);
47100
+ } else {
47101
+ logger.warn(`Failed to delete branch '${branch}': ${deleteResult.stderr}`);
47102
+ }
47103
+ }
46475
47104
  }
46476
47105
  /**
46477
47106
  * Get commit SHA for a given ref inside a bare repository.
@@ -53022,9 +53651,40 @@ ${file.patch}`).join("\n\n");
53022
53651
  /**
53023
53652
  * Get diff between current branch and base branch (for feature branch analysis)
53024
53653
  */
53654
+ /**
53655
+ * Resolve the base branch ref to a revision that exists locally.
53656
+ * In CI (shallow clones), the local branch may not exist, so we try:
53657
+ * 1. The branch name as-is (e.g. "main")
53658
+ * 2. The remote-tracking ref (e.g. "origin/main")
53659
+ * 3. Fetch from origin then retry
53660
+ */
53661
+ async resolveBaseBranchRef(baseBranch) {
53662
+ try {
53663
+ await this.git.revparse([baseBranch]);
53664
+ return baseBranch;
53665
+ } catch {
53666
+ }
53667
+ const remoteBranch = `origin/${baseBranch}`;
53668
+ try {
53669
+ await this.git.revparse([remoteBranch]);
53670
+ return remoteBranch;
53671
+ } catch {
53672
+ }
53673
+ try {
53674
+ await this.git.fetch(["origin", baseBranch]);
53675
+ await this.git.revparse([remoteBranch]);
53676
+ return remoteBranch;
53677
+ } catch {
53678
+ return baseBranch;
53679
+ }
53680
+ }
53025
53681
  async getBranchDiff(baseBranch, includeContext = true) {
53026
53682
  try {
53027
- const diffSummary = await this.git.diffSummary([baseBranch]);
53683
+ const resolvedBase = await this.resolveBaseBranchRef(baseBranch);
53684
+ if (resolvedBase !== baseBranch) {
53685
+ console.error(`\u{1F4CE} Resolved base branch: ${baseBranch} \u2192 ${resolvedBase}`);
53686
+ }
53687
+ const diffSummary = await this.git.diffSummary([resolvedBase]);
53028
53688
  const changes = [];
53029
53689
  if (!diffSummary || !diffSummary.files) {
53030
53690
  return [];
@@ -53052,7 +53712,7 @@ ${file.patch}`).join("\n\n");
53052
53712
  let truncated = false;
53053
53713
  if (includeContext && !isBinary) {
53054
53714
  try {
53055
- const rawPatch = await this.git.diff([baseBranch, "--", file.file]);
53715
+ const rawPatch = await this.git.diff([resolvedBase, "--", file.file]);
53056
53716
  if (rawPatch) {
53057
53717
  const result = this.truncatePatch(rawPatch, file.file);
53058
53718
  patch = result.patch;
@@ -53648,6 +54308,30 @@ var init_workspace_manager = __esm({
53648
54308
  if (cleanResult.exitCode !== 0) {
53649
54309
  logger.warn(`[Workspace] clean -fdx failed: ${cleanResult.stderr}`);
53650
54310
  }
54311
+ await this.deleteLocalBranches(worktreePath);
54312
+ }
54313
+ /**
54314
+ * Delete all local branches in a worktree.
54315
+ */
54316
+ async deleteLocalBranches(worktreePath) {
54317
+ const escapedPath = shellEscape(worktreePath);
54318
+ const listResult = await commandExecutor.execute(
54319
+ `git -C ${escapedPath} branch --list --format='%(refname:short)'`,
54320
+ { timeout: 1e4 }
54321
+ );
54322
+ if (listResult.exitCode !== 0 || !listResult.stdout.trim()) {
54323
+ return;
54324
+ }
54325
+ const branches = listResult.stdout.trim().split("\n").map((b) => b.trim()).filter((b) => b.length > 0);
54326
+ for (const branch of branches) {
54327
+ const deleteResult = await commandExecutor.execute(
54328
+ `git -C ${escapedPath} branch -D ${shellEscape(branch)}`,
54329
+ { timeout: 1e4 }
54330
+ );
54331
+ if (deleteResult.exitCode === 0) {
54332
+ logger.debug(`[Workspace] Deleted local branch '${branch}' from worktree`);
54333
+ }
54334
+ }
53651
54335
  }
53652
54336
  /**
53653
54337
  * Refresh an existing worktree to the latest upstream default branch