claude-ide-bridge 2.26.7 → 2.30.1

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 (78) hide show
  1. package/dist/automation.d.ts +65 -0
  2. package/dist/automation.js +187 -1
  3. package/dist/automation.js.map +1 -1
  4. package/dist/bridge.d.ts +2 -0
  5. package/dist/bridge.js +12 -2
  6. package/dist/bridge.js.map +1 -1
  7. package/dist/commands/install.d.ts +1 -0
  8. package/dist/commands/install.js +158 -0
  9. package/dist/commands/install.js.map +1 -0
  10. package/dist/commands/marketplace.d.ts +10 -0
  11. package/dist/commands/marketplace.js +116 -0
  12. package/dist/commands/marketplace.js.map +1 -0
  13. package/dist/companions/registry.d.ts +12 -0
  14. package/dist/companions/registry.js +71 -0
  15. package/dist/companions/registry.js.map +1 -0
  16. package/dist/dashboard.d.ts +12 -0
  17. package/dist/dashboard.js +109 -0
  18. package/dist/dashboard.js.map +1 -0
  19. package/dist/index.js +18 -3
  20. package/dist/index.js.map +1 -1
  21. package/dist/probe.d.ts +2 -0
  22. package/dist/probe.js +32 -1
  23. package/dist/probe.js.map +1 -1
  24. package/dist/prompts.js +33 -0
  25. package/dist/prompts.js.map +1 -1
  26. package/dist/server.d.ts +2 -0
  27. package/dist/server.js +33 -0
  28. package/dist/server.js.map +1 -1
  29. package/dist/streamableHttp.js +19 -2
  30. package/dist/streamableHttp.js.map +1 -1
  31. package/dist/tools/bridgeStatus.js +14 -0
  32. package/dist/tools/bridgeStatus.js.map +1 -1
  33. package/dist/tools/explainSymbol.d.ts +9 -0
  34. package/dist/tools/explainSymbol.js +37 -0
  35. package/dist/tools/explainSymbol.js.map +1 -1
  36. package/dist/tools/findRelatedTests.d.ts +83 -0
  37. package/dist/tools/findRelatedTests.js +196 -0
  38. package/dist/tools/findRelatedTests.js.map +1 -0
  39. package/dist/tools/getArchitectureContext.d.ts +85 -0
  40. package/dist/tools/getArchitectureContext.js +135 -0
  41. package/dist/tools/getArchitectureContext.js.map +1 -0
  42. package/dist/tools/getSymbolHistory.d.ts +157 -0
  43. package/dist/tools/getSymbolHistory.js +256 -0
  44. package/dist/tools/getSymbolHistory.js.map +1 -0
  45. package/dist/tools/getTypeSignature.d.ts +2 -2
  46. package/dist/tools/getTypeSignature.js +34 -2
  47. package/dist/tools/getTypeSignature.js.map +1 -1
  48. package/dist/tools/handoffNote.d.ts +3 -0
  49. package/dist/tools/handoffNote.js +1 -0
  50. package/dist/tools/handoffNote.js.map +1 -1
  51. package/dist/tools/headless/lspClient.d.ts +26 -0
  52. package/dist/tools/headless/lspClient.js +221 -0
  53. package/dist/tools/headless/lspClient.js.map +1 -0
  54. package/dist/tools/headless/lspFallback.d.ts +28 -0
  55. package/dist/tools/headless/lspFallback.js +122 -0
  56. package/dist/tools/headless/lspFallback.js.map +1 -0
  57. package/dist/tools/index.js +13 -5
  58. package/dist/tools/index.js.map +1 -1
  59. package/dist/tools/jumpToFirstError.js +2 -1
  60. package/dist/tools/jumpToFirstError.js.map +1 -1
  61. package/dist/tools/lsp.d.ts +6 -6
  62. package/dist/tools/lsp.js +165 -61
  63. package/dist/tools/lsp.js.map +1 -1
  64. package/dist/tools/navigateToSymbolByName.d.ts +2 -22
  65. package/dist/tools/navigateToSymbolByName.js +84 -4
  66. package/dist/tools/navigateToSymbolByName.js.map +1 -1
  67. package/dist/tools/openFile.js +4 -2
  68. package/dist/tools/openFile.js.map +1 -1
  69. package/dist/tools/runCommand.d.ts +15 -0
  70. package/dist/tools/runCommand.js +5 -0
  71. package/dist/tools/runCommand.js.map +1 -1
  72. package/dist/tools/screenshotAndAnnotate.d.ts +103 -0
  73. package/dist/tools/screenshotAndAnnotate.js +192 -0
  74. package/dist/tools/screenshotAndAnnotate.js.map +1 -0
  75. package/dist/transport.d.ts +17 -0
  76. package/dist/transport.js +40 -0
  77. package/dist/transport.js.map +1 -1
  78. package/package.json +4 -2
@@ -13,15 +13,40 @@ import type { ExtensionClient } from "./extensionClient.js";
13
13
  * primary event value (file path, branch name, tool name, etc.) matches.
14
14
  * Prefix with `!` to negate: `"!**\/*.test.ts"` fires for all non-test files.
15
15
  */
16
+ /**
17
+ * Optional runtime condition checked before a hook fires.
18
+ * All specified fields must pass for the hook to trigger.
19
+ */
20
+ export interface AutomationCondition {
21
+ /** Only fire if the active file's diagnostic count meets this threshold. */
22
+ minDiagnosticCount?: number;
23
+ /** Only fire if the active file has a diagnostic of at least this severity. */
24
+ diagnosticsMinSeverity?: "error" | "warning";
25
+ /** Only fire if the last test run for any runner had this outcome. */
26
+ testRunnerLastStatus?: "passed" | "failed";
27
+ }
16
28
  export interface PromptSource {
17
29
  prompt?: string;
18
30
  promptName?: string;
19
31
  promptArgs?: Record<string, string>;
20
32
  condition?: string;
33
+ /** Optional runtime condition evaluated before the hook fires. */
34
+ when?: AutomationCondition;
21
35
  /** Per-hook model override. Falls back to policy.defaultModel (Haiku). */
22
36
  model?: string;
23
37
  /** Per-hook effort override. Falls back to policy.defaultEffort ("low"). */
24
38
  effort?: "low" | "medium" | "high" | "max";
39
+ /**
40
+ * Number of times to re-enqueue on task error. Default: 0 (no retry).
41
+ * Retries only fire when the task reaches status "error" — cancellations
42
+ * and timeouts do not trigger a retry.
43
+ */
44
+ retryCount?: number;
45
+ /**
46
+ * Milliseconds to wait between retries. Default: 30_000.
47
+ * Enforced minimum: 5_000.
48
+ */
49
+ retryDelayMs?: number;
25
50
  }
26
51
  export interface OnDiagnosticsErrorPolicy extends PromptSource {
27
52
  enabled: boolean;
@@ -136,6 +161,18 @@ export interface TestRunResult {
136
161
  message: string;
137
162
  }>;
138
163
  }
164
+ export interface OnDebugSessionEndPolicy extends PromptSource {
165
+ enabled: boolean;
166
+ /**
167
+ * Placeholders (inline prompt only): {{sessionName}}, {{sessionType}}
168
+ */
169
+ cooldownMs: number;
170
+ }
171
+ /** Minimal debug session result passed to handleDebugSessionEnd. */
172
+ export interface DebugSessionEndResult {
173
+ sessionName: string;
174
+ sessionType: string;
175
+ }
139
176
  export interface OnGitCommitPolicy extends PromptSource {
140
177
  enabled: boolean;
141
178
  /**
@@ -309,6 +346,8 @@ export interface AutomationPolicy {
309
346
  onDiagnosticsCleared?: OnDiagnosticsClearedPolicy;
310
347
  /** Fired when a Claude orchestrator task completes with status done. */
311
348
  onTaskSuccess?: OnTaskSuccessPolicy;
349
+ /** Fired when a VS Code debug session terminates (hasActiveSession transitions true→false). */
350
+ onDebugSessionEnd?: OnDebugSessionEndPolicy;
312
351
  }
313
352
  export interface Diagnostic {
314
353
  message: string;
@@ -378,8 +417,14 @@ export declare class AutomationHooks {
378
417
  private activeDiagnosticsClearedTasks;
379
418
  /** Tracks previous error count per normalized file path for zero-transition detection. */
380
419
  private prevDiagnosticErrors;
420
+ /** Latest diagnostics by file — used by _evaluateWhen() for conditional hooks. */
421
+ private latestDiagnosticsByFile;
422
+ /** Last test runner outcome per runner name — used by _evaluateWhen(). */
423
+ private lastTestRunnerStatusByRunner;
381
424
  /** Active task ID for the task-success handler (workspace-global). */
382
425
  private activeTaskSuccessTaskId;
426
+ /** Active task ID for the debug-session-end handler (workspace-global). */
427
+ private activeDebugSessionEndTaskId;
383
428
  /** Active task ID for the post-compact handler (workspace-global). */
384
429
  private activePostCompactTaskId;
385
430
  /** Active task ID for the instructions-loaded handler (workspace-global). */
@@ -397,6 +442,11 @@ export declare class AutomationHooks {
397
442
  * callers can handle it identically.
398
443
  */
399
444
  private _enqueueAutomationTask;
445
+ /**
446
+ * Poll a task until it reaches a terminal state. If the task ends with
447
+ * status "error" and retries remain, re-enqueue after `retryDelayMs`.
448
+ */
449
+ private _watchForRetry;
400
450
  /**
401
451
  * Resolve a named prompt, substituting any `{{placeholder}}` tokens in
402
452
  * `promptArgs` values with sanitized event data before calling `getPrompt()`.
@@ -405,6 +455,12 @@ export declare class AutomationHooks {
405
455
  * or has missing required arguments.
406
456
  */
407
457
  private _resolveNamedPrompt;
458
+ /**
459
+ * Evaluate the optional `when` condition block on a hook.
460
+ * Called after _matchesCondition() succeeds and before cooldown checks.
461
+ * Returns true if all specified conditions pass (or no `when` block present).
462
+ */
463
+ private _evaluateWhen;
408
464
  private _matchesCondition;
409
465
  handleDiagnosticsChanged(file: string, diagnostics: Diagnostic[]): void;
410
466
  /** Prune lastTrigger entries older than LAST_TRIGGER_MAX_AGE_MS to prevent unbounded growth. */
@@ -476,6 +532,11 @@ export declare class AutomationHooks {
476
532
  * Call from bridge.ts when a task transitions to done.
477
533
  */
478
534
  handleTaskSuccess(result: TaskSuccessResult): void;
535
+ /**
536
+ * Called when a VS Code debug session ends (hasActiveSession transitions true→false).
537
+ * Fires the onDebugSessionEnd automation hook if configured.
538
+ */
539
+ handleDebugSessionEnd(result: DebugSessionEndResult): Promise<void>;
479
540
  /** Summary of automation policy for getBridgeStatus. */
480
541
  getStatus(): {
481
542
  onPostCompact: {
@@ -546,6 +607,10 @@ export declare class AutomationHooks {
546
607
  enabled: boolean;
547
608
  cooldownMs: number;
548
609
  } | null;
610
+ onDebugSessionEnd: {
611
+ enabled: boolean;
612
+ cooldownMs: number;
613
+ } | null;
549
614
  unwiredEnabledHooks: string[];
550
615
  defaultModel: string;
551
616
  maxTasksPerHour: number;
@@ -498,6 +498,24 @@ export function loadPolicy(filePath) {
498
498
  ts.cooldownMs = MIN_COOLDOWN_MS;
499
499
  }
500
500
  }
501
+ // Validate onDebugSessionEnd
502
+ if (policy.onDebugSessionEnd !== undefined) {
503
+ const dse = policy.onDebugSessionEnd;
504
+ if (typeof dse !== "object" || dse === null) {
505
+ throw new Error(`"onDebugSessionEnd" must be an object`);
506
+ }
507
+ if (typeof dse.enabled !== "boolean") {
508
+ throw new Error(`"onDebugSessionEnd.enabled" must be a boolean`);
509
+ }
510
+ validatePromptSource("onDebugSessionEnd", dse);
511
+ if (typeof dse.cooldownMs !== "number" ||
512
+ !Number.isFinite(dse.cooldownMs)) {
513
+ throw new Error(`"onDebugSessionEnd.cooldownMs" must be a number`);
514
+ }
515
+ if (dse.cooldownMs < MIN_COOLDOWN_MS) {
516
+ dse.cooldownMs = MIN_COOLDOWN_MS;
517
+ }
518
+ }
501
519
  return policy;
502
520
  }
503
521
  /**
@@ -543,7 +561,7 @@ export function checkCcHookWiring() {
543
561
  result[ccEvent] = entries.some((e) => {
544
562
  // New format: { matcher, hooks: [{ type, command }] }
545
563
  if (e && Array.isArray(e.hooks)) {
546
- return e.hooks.some((h) => commandMatches(h.command, ccEvent, toolName));
564
+ return e.hooks?.some((h) => commandMatches(h.command, ccEvent, toolName));
547
565
  }
548
566
  // Legacy flat format: { type, command }
549
567
  return commandMatches(e.command, ccEvent, toolName);
@@ -603,8 +621,14 @@ export class AutomationHooks {
603
621
  activeDiagnosticsClearedTasks = new Map();
604
622
  /** Tracks previous error count per normalized file path for zero-transition detection. */
605
623
  prevDiagnosticErrors = new Map();
624
+ /** Latest diagnostics by file — used by _evaluateWhen() for conditional hooks. */
625
+ latestDiagnosticsByFile = new Map();
626
+ /** Last test runner outcome per runner name — used by _evaluateWhen(). */
627
+ lastTestRunnerStatusByRunner = new Map();
606
628
  /** Active task ID for the task-success handler (workspace-global). */
607
629
  activeTaskSuccessTaskId = null;
630
+ /** Active task ID for the debug-session-end handler (workspace-global). */
631
+ activeDebugSessionEndTaskId = null;
608
632
  /** Active task ID for the post-compact handler (workspace-global). */
609
633
  activePostCompactTaskId = null;
610
634
  /** Active task ID for the instructions-loaded handler (workspace-global). */
@@ -661,8 +685,50 @@ export class AutomationHooks {
661
685
  if (maxPerHour > 0) {
662
686
  this.taskTimestamps.push(Date.now());
663
687
  }
688
+ // Schedule retry watcher if retryCount > 0.
689
+ const retryCount = opts.hookCfg?.retryCount ?? 0;
690
+ const retryAttempt = opts._retryAttempt ?? 0;
691
+ if (retryCount > 0) {
692
+ const retryDelayMs = Math.max(5_000, opts.hookCfg?.retryDelayMs ?? 30_000);
693
+ this._watchForRetry(taskId, opts, retryAttempt, retryCount, retryDelayMs);
694
+ }
664
695
  return taskId;
665
696
  }
697
+ /**
698
+ * Poll a task until it reaches a terminal state. If the task ends with
699
+ * status "error" and retries remain, re-enqueue after `retryDelayMs`.
700
+ */
701
+ _watchForRetry(taskId, opts, retryAttempt, retryCount, retryDelayMs) {
702
+ const interval = setInterval(() => {
703
+ const task = this.orchestrator.getTask(taskId);
704
+ if (!task) {
705
+ clearInterval(interval);
706
+ return;
707
+ }
708
+ if (task.status === "pending" || task.status === "running")
709
+ return;
710
+ clearInterval(interval);
711
+ if (task.status !== "error")
712
+ return; // cancelled/done → no retry
713
+ const nextAttempt = retryAttempt + 1;
714
+ if (nextAttempt > retryCount) {
715
+ this.log(`[automation] ${opts.triggerSource}: max retries (${retryCount}) reached, dropping`);
716
+ return;
717
+ }
718
+ this.log(`[automation] ${opts.triggerSource}: retry ${nextAttempt}/${retryCount} in ${retryDelayMs}ms`);
719
+ setTimeout(() => {
720
+ try {
721
+ this._enqueueAutomationTask({
722
+ ...opts,
723
+ _retryAttempt: nextAttempt,
724
+ });
725
+ }
726
+ catch (e) {
727
+ this.log(`[automation] ${opts.triggerSource}: retry enqueue failed: ${e}`);
728
+ }
729
+ }, retryDelayMs);
730
+ }, 2_000);
731
+ }
666
732
  /**
667
733
  * Resolve a named prompt, substituting any `{{placeholder}}` tokens in
668
734
  * `promptArgs` values with sanitized event data before calling `getPrompt()`.
@@ -693,6 +759,47 @@ export class AutomationHooks {
693
759
  .map((m) => m.content.text)
694
760
  .join("\n\n");
695
761
  }
762
+ /**
763
+ * Evaluate the optional `when` condition block on a hook.
764
+ * Called after _matchesCondition() succeeds and before cooldown checks.
765
+ * Returns true if all specified conditions pass (or no `when` block present).
766
+ */
767
+ _evaluateWhen(cfg, file) {
768
+ const when = cfg.when;
769
+ if (!when)
770
+ return true;
771
+ if (when.minDiagnosticCount !== undefined ||
772
+ when.diagnosticsMinSeverity !== undefined) {
773
+ const diags = (file ? this.latestDiagnosticsByFile.get(file) : undefined) ?? [];
774
+ if (when.minDiagnosticCount !== undefined &&
775
+ diags.length < when.minDiagnosticCount) {
776
+ return false;
777
+ }
778
+ if (when.diagnosticsMinSeverity !== undefined) {
779
+ const targetRank = when.diagnosticsMinSeverity === "error" ? 2 : 1;
780
+ const severityRank = {
781
+ error: 2,
782
+ warning: 1,
783
+ info: 0,
784
+ information: 0,
785
+ hint: 0,
786
+ };
787
+ const hasMatchingSeverity = diags.some((d) => (severityRank[d.severity] ?? 0) >= targetRank);
788
+ if (!hasMatchingSeverity)
789
+ return false;
790
+ }
791
+ }
792
+ if (when.testRunnerLastStatus !== undefined) {
793
+ // Check any runner's last status (wildcard: first match wins)
794
+ const statuses = Array.from(this.lastTestRunnerStatusByRunner.values());
795
+ if (statuses.length === 0)
796
+ return false;
797
+ const hasMatch = statuses.some((s) => s === when.testRunnerLastStatus);
798
+ if (!hasMatch)
799
+ return false;
800
+ }
801
+ return true;
802
+ }
696
803
  _matchesCondition(cfg, primaryValue) {
697
804
  if (!cfg.condition)
698
805
  return true;
@@ -717,6 +824,8 @@ export class AutomationHooks {
717
824
  const currentErrorCount = diagnostics.filter((d) => (severityRankForClear[d.severity] ?? 0) >= 1).length;
718
825
  const prevErrorCount = this.prevDiagnosticErrors.get(normalizedFile) ?? 0;
719
826
  this.prevDiagnosticErrors.set(normalizedFile, currentErrorCount);
827
+ // Keep latest diagnostics for _evaluateWhen() condition checks
828
+ this.latestDiagnosticsByFile.set(normalizedFile, diagnostics);
720
829
  // Fire onDiagnosticsCleared if transitioning from non-zero → zero
721
830
  if (prevErrorCount > 0 && currentErrorCount === 0) {
722
831
  this.handleDiagnosticsCleared(normalizedFile);
@@ -727,6 +836,8 @@ export class AutomationHooks {
727
836
  // Condition filter
728
837
  if (!this._matchesCondition(cfg, normalizedFile))
729
838
  return;
839
+ if (!this._evaluateWhen(cfg, normalizedFile))
840
+ return;
730
841
  // Skip onDiagnosticsError if there are no errors to report
731
842
  if (currentErrorCount === 0)
732
843
  return;
@@ -1011,6 +1122,8 @@ export class AutomationHooks {
1011
1122
  // Condition filter
1012
1123
  if (!this._matchesCondition(cfg, normalizedFile))
1013
1124
  return;
1125
+ if (!this._evaluateWhen(cfg, normalizedFile))
1126
+ return;
1014
1127
  // Pattern matching — also try workspace-relative path so patterns like
1015
1128
  // "src/**/*.ts" work when VS Code sends absolute paths.
1016
1129
  const relFile = this.workspace && path.isAbsolute(normalizedFile)
@@ -1151,10 +1264,13 @@ export class AutomationHooks {
1151
1264
  const failureCount = result.summary.failed + result.summary.errored;
1152
1265
  // Update per-runner outcome state unconditionally so onTestPassAfterFailure
1153
1266
  // can detect fail→pass transitions even when onTestRun is disabled/absent.
1267
+ const testStatus = failureCount === 0 ? "passed" : "failed";
1154
1268
  for (const runner of result.runners) {
1155
1269
  const prev = this.lastTestOutcomeByRunner.get(runner);
1156
1270
  const current = failureCount === 0 ? "pass" : "fail";
1157
1271
  this.lastTestOutcomeByRunner.set(runner, current);
1272
+ // Update lastTestRunnerStatusByRunner for _evaluateWhen() condition checks
1273
+ this.lastTestRunnerStatusByRunner.set(runner, testStatus);
1158
1274
  if (prev === "fail" && current === "pass") {
1159
1275
  this._handleTestPassAfterFailure(result, runner);
1160
1276
  }
@@ -1175,6 +1291,9 @@ export class AutomationHooks {
1175
1291
  this.log(`[automation] skipping test-run trigger — duration ${result.summary.durationMs}ms < minDuration ${cfg.minDuration}ms`);
1176
1292
  return;
1177
1293
  }
1294
+ // Evaluate optional when condition
1295
+ if (!this._evaluateWhen(cfg))
1296
+ return;
1178
1297
  // Loop guard: skip if a task is still pending/running
1179
1298
  if (this.activeTestRunTaskId) {
1180
1299
  const existing = this.orchestrator.getTask(this.activeTestRunTaskId);
@@ -1882,6 +2001,67 @@ export class AutomationHooks {
1882
2001
  this.log(`[automation] failed to enqueue task-success task: ${err instanceof Error ? err.message : String(err)}`);
1883
2002
  }
1884
2003
  }
2004
+ /**
2005
+ * Called when a VS Code debug session ends (hasActiveSession transitions true→false).
2006
+ * Fires the onDebugSessionEnd automation hook if configured.
2007
+ */
2008
+ async handleDebugSessionEnd(result) {
2009
+ const cfg = this.policy.onDebugSessionEnd;
2010
+ if (!cfg?.enabled)
2011
+ return;
2012
+ // Loop guard: skip if a task is still pending/running
2013
+ if (this.activeDebugSessionEndTaskId) {
2014
+ const existing = this.orchestrator.getTask(this.activeDebugSessionEndTaskId);
2015
+ if (existing &&
2016
+ (existing.status === "pending" || existing.status === "running")) {
2017
+ this.log(`[automation] skipping debug-session-end trigger — task ${this.activeDebugSessionEndTaskId.slice(0, 8)} still active`);
2018
+ return;
2019
+ }
2020
+ this.activeDebugSessionEndTaskId = null;
2021
+ }
2022
+ // Cooldown check (workspace-global)
2023
+ const key = "debugSessionEnd:global";
2024
+ const now = Date.now();
2025
+ const last = this.lastTrigger.get(key) ?? 0;
2026
+ if (now - last < cfg.cooldownMs) {
2027
+ this.log(`[automation] cooldown active for debug-session-end (${cfg.cooldownMs - (now - last)}ms remaining)`);
2028
+ return;
2029
+ }
2030
+ this._pruneLastTrigger(now);
2031
+ const safeSessionName = result.sessionName.slice(0, MAX_FILE_PATH_CHARS);
2032
+ const safeSessionType = result.sessionType.slice(0, MAX_FILE_PATH_CHARS);
2033
+ let prompt;
2034
+ if (cfg.promptName) {
2035
+ const resolved = this._resolveNamedPrompt(cfg.promptName, cfg.promptArgs ?? {}, {
2036
+ sessionName: safeSessionName,
2037
+ sessionType: safeSessionType,
2038
+ });
2039
+ if (resolved === null)
2040
+ return;
2041
+ prompt = resolved;
2042
+ }
2043
+ else {
2044
+ const nonce = crypto.randomBytes(6).toString("hex");
2045
+ prompt =
2046
+ (cfg.prompt ?? "")
2047
+ .replace(/\{\{sessionName\}\}/g, untrustedBlock("SESSION NAME", safeSessionName, nonce))
2048
+ .replace(/\{\{sessionType\}\}/g, untrustedBlock("SESSION TYPE", safeSessionType, nonce)) ?? "";
2049
+ }
2050
+ prompt = truncatePrompt(buildHookMetadata("onDebugSessionEnd") + prompt);
2051
+ try {
2052
+ const taskId = this._enqueueAutomationTask({
2053
+ prompt,
2054
+ triggerSource: "onDebugSessionEnd",
2055
+ hookCfg: cfg,
2056
+ });
2057
+ this.lastTrigger.set(key, now);
2058
+ this.activeDebugSessionEndTaskId = taskId;
2059
+ this.log(`[automation] triggered debug-session-end task ${taskId.slice(0, 8)} (session: ${result.sessionName}, type: ${result.sessionType})`);
2060
+ }
2061
+ catch (err) {
2062
+ this.log(`[automation] failed to enqueue debug-session-end task: ${err instanceof Error ? err.message : String(err)}`);
2063
+ }
2064
+ }
1885
2065
  /** Summary of automation policy for getBridgeStatus. */
1886
2066
  getStatus() {
1887
2067
  const p = this.policy;
@@ -1993,6 +2173,12 @@ export class AutomationHooks {
1993
2173
  cooldownMs: p.onGitPull.cooldownMs,
1994
2174
  }
1995
2175
  : null,
2176
+ onDebugSessionEnd: p.onDebugSessionEnd
2177
+ ? {
2178
+ enabled: p.onDebugSessionEnd.enabled,
2179
+ cooldownMs: p.onDebugSessionEnd.cooldownMs,
2180
+ }
2181
+ : null,
1996
2182
  unwiredEnabledHooks,
1997
2183
  defaultModel: p.defaultModel ?? "claude-haiku-4-5-20251001",
1998
2184
  maxTasksPerHour: p.maxTasksPerHour ?? 20,