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.
- package/dist/automation.d.ts +65 -0
- package/dist/automation.js +187 -1
- package/dist/automation.js.map +1 -1
- package/dist/bridge.d.ts +2 -0
- package/dist/bridge.js +12 -2
- package/dist/bridge.js.map +1 -1
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +158 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/marketplace.d.ts +10 -0
- package/dist/commands/marketplace.js +116 -0
- package/dist/commands/marketplace.js.map +1 -0
- package/dist/companions/registry.d.ts +12 -0
- package/dist/companions/registry.js +71 -0
- package/dist/companions/registry.js.map +1 -0
- package/dist/dashboard.d.ts +12 -0
- package/dist/dashboard.js +109 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/index.js +18 -3
- package/dist/index.js.map +1 -1
- package/dist/probe.d.ts +2 -0
- package/dist/probe.js +32 -1
- package/dist/probe.js.map +1 -1
- package/dist/prompts.js +33 -0
- package/dist/prompts.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +33 -0
- package/dist/server.js.map +1 -1
- package/dist/streamableHttp.js +19 -2
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/bridgeStatus.js +14 -0
- package/dist/tools/bridgeStatus.js.map +1 -1
- package/dist/tools/explainSymbol.d.ts +9 -0
- package/dist/tools/explainSymbol.js +37 -0
- package/dist/tools/explainSymbol.js.map +1 -1
- package/dist/tools/findRelatedTests.d.ts +83 -0
- package/dist/tools/findRelatedTests.js +196 -0
- package/dist/tools/findRelatedTests.js.map +1 -0
- package/dist/tools/getArchitectureContext.d.ts +85 -0
- package/dist/tools/getArchitectureContext.js +135 -0
- package/dist/tools/getArchitectureContext.js.map +1 -0
- package/dist/tools/getSymbolHistory.d.ts +157 -0
- package/dist/tools/getSymbolHistory.js +256 -0
- package/dist/tools/getSymbolHistory.js.map +1 -0
- package/dist/tools/getTypeSignature.d.ts +2 -2
- package/dist/tools/getTypeSignature.js +34 -2
- package/dist/tools/getTypeSignature.js.map +1 -1
- package/dist/tools/handoffNote.d.ts +3 -0
- package/dist/tools/handoffNote.js +1 -0
- package/dist/tools/handoffNote.js.map +1 -1
- package/dist/tools/headless/lspClient.d.ts +26 -0
- package/dist/tools/headless/lspClient.js +221 -0
- package/dist/tools/headless/lspClient.js.map +1 -0
- package/dist/tools/headless/lspFallback.d.ts +28 -0
- package/dist/tools/headless/lspFallback.js +122 -0
- package/dist/tools/headless/lspFallback.js.map +1 -0
- package/dist/tools/index.js +13 -5
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/jumpToFirstError.js +2 -1
- package/dist/tools/jumpToFirstError.js.map +1 -1
- package/dist/tools/lsp.d.ts +6 -6
- package/dist/tools/lsp.js +165 -61
- package/dist/tools/lsp.js.map +1 -1
- package/dist/tools/navigateToSymbolByName.d.ts +2 -22
- package/dist/tools/navigateToSymbolByName.js +84 -4
- package/dist/tools/navigateToSymbolByName.js.map +1 -1
- package/dist/tools/openFile.js +4 -2
- package/dist/tools/openFile.js.map +1 -1
- package/dist/tools/runCommand.d.ts +15 -0
- package/dist/tools/runCommand.js +5 -0
- package/dist/tools/runCommand.js.map +1 -1
- package/dist/tools/screenshotAndAnnotate.d.ts +103 -0
- package/dist/tools/screenshotAndAnnotate.js +192 -0
- package/dist/tools/screenshotAndAnnotate.js.map +1 -0
- package/dist/transport.d.ts +17 -0
- package/dist/transport.js +40 -0
- package/dist/transport.js.map +1 -1
- package/package.json +4 -2
package/dist/automation.d.ts
CHANGED
|
@@ -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;
|
package/dist/automation.js
CHANGED
|
@@ -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
|
|
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,
|