jeopi-cli 16.4.2 → 16.4.4
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/CHANGELOG.md +74 -0
- package/dist/cli.js +3033 -2991
- package/dist/types/advisor/advise-tool.d.ts +5 -1
- package/dist/types/advisor/config.d.ts +4 -3
- package/dist/types/advisor/runtime.d.ts +15 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +4 -1
- package/dist/types/cli/usage-error.d.ts +4 -0
- package/dist/types/commit/agentic/agent.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +7 -1
- package/dist/types/config/models-config.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +3 -13
- package/dist/types/eval/py/tool-bridge.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/internal-urls/memory-protocol.d.ts +6 -6
- package/dist/types/internal-urls/types.d.ts +7 -0
- package/dist/types/irc/bus.d.ts +4 -0
- package/dist/types/lsp/deferred-diagnostics.d.ts +11 -0
- package/dist/types/modes/components/move-overlay.d.ts +1 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +2 -0
- package/dist/types/modes/components/welcome.d.ts +4 -0
- package/dist/types/modes/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/modes/orchestrate.d.ts +1 -1
- package/dist/types/modes/rpc/host-tools.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +27 -7
- package/dist/types/modes/ultrathink.d.ts +1 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +9 -0
- package/dist/types/modes/workflow.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/auth-broker-config.d.ts +2 -2
- package/dist/types/tools/bash-skill-urls.d.ts +3 -2
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +3 -1
- package/dist/types/tools/browser/cmux/rpc.d.ts +20 -0
- package/dist/types/tools/browser/launch.d.ts +1 -0
- package/dist/types/tools/browser/run-cancellation.d.ts +17 -2
- package/dist/types/tools/browser/run-output.d.ts +25 -0
- package/dist/types/tools/browser/tab-worker.d.ts +22 -0
- package/dist/types/utils/changelog.d.ts +35 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +13 -0
- package/src/advisor/__tests__/config.test.ts +36 -0
- package/src/advisor/advise-tool.ts +6 -1
- package/src/advisor/config.ts +11 -8
- package/src/advisor/runtime.ts +127 -0
- package/src/autolearn/controller.ts +15 -3
- package/src/cli/args.ts +12 -0
- package/src/cli/flag-tables.ts +21 -7
- package/src/cli/update-cli.ts +63 -13
- package/src/cli/usage-error.ts +7 -0
- package/src/commands/acp.ts +11 -2
- package/src/commands/launch.ts +12 -3
- package/src/commit/agentic/agent.ts +4 -0
- package/src/commit/agentic/index.ts +46 -21
- package/src/config/config-file.ts +30 -12
- package/src/config/model-registry.ts +7 -7
- package/src/config/model-resolver.ts +24 -6
- package/src/config/models-config.ts +1 -1
- package/src/config/settings-schema.ts +3 -15
- package/src/edit/index.ts +10 -76
- package/src/edit/renderer.ts +13 -10
- package/src/eval/__tests__/agent-bridge.test.ts +19 -14
- package/src/eval/__tests__/bridge-timeout.test.ts +60 -0
- package/src/eval/executor-base.ts +84 -7
- package/src/eval/js/shared/rewrite-imports.ts +31 -13
- package/src/eval/py/tool-bridge.ts +13 -14
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/ttsr.ts +0 -3
- package/src/extensibility/custom-tools/loader.ts +70 -37
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +9 -1
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +30 -3
- package/src/extensibility/plugins/manager.ts +21 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/memory-protocol.ts +13 -8
- package/src/internal-urls/skill-protocol.ts +1 -1
- package/src/internal-urls/types.ts +7 -0
- package/src/irc/bus.ts +48 -18
- package/src/lsp/client.ts +7 -1
- package/src/lsp/deferred-diagnostics.ts +66 -0
- package/src/main.ts +13 -13
- package/src/mcp/tool-bridge.ts +57 -6
- package/src/mcp/transports/stdio.test.ts +45 -1
- package/src/mcp/transports/stdio.ts +6 -3
- package/src/modes/acp/acp-agent.ts +73 -2
- package/src/modes/acp/acp-event-mapper.ts +5 -0
- package/src/modes/acp/acp-mode.ts +11 -0
- package/src/modes/components/__tests__/move-overlay.test.ts +16 -1
- package/src/modes/components/advisor-config.ts +15 -7
- package/src/modes/components/chat-transcript-builder.ts +31 -11
- package/src/modes/components/move-overlay.ts +2 -3
- package/src/modes/components/plan-review-overlay.ts +7 -0
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/components/welcome.ts +13 -14
- package/src/modes/controllers/command-controller.ts +8 -10
- package/src/modes/controllers/event-controller.ts +58 -42
- package/src/modes/controllers/input-controller.ts +9 -6
- package/src/modes/controllers/selector-controller.ts +3 -2
- package/src/modes/controllers/tan-command-controller.ts +40 -1
- package/src/modes/interactive-mode.ts +13 -0
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/orchestrate.ts +6 -5
- package/src/modes/print-mode.ts +9 -0
- package/src/modes/rpc/host-tools.ts +13 -0
- package/src/modes/rpc/rpc-mode.ts +130 -56
- package/src/modes/ultrathink.ts +6 -5
- package/src/modes/utils/transcript-render-helpers.ts +44 -0
- package/src/modes/utils/ui-helpers.ts +16 -1
- package/src/modes/workflow.ts +5 -4
- package/src/prompts/system/eager-task.md +2 -2
- package/src/prompts/system/tan-context-switch.md +17 -0
- package/src/prompts/tools/browser.md +5 -5
- package/src/sdk.ts +3 -0
- package/src/session/agent-session.ts +267 -36
- package/src/session/auth-broker-config.ts +4 -4
- package/src/session/snapcompact-inline.ts +3 -19
- package/src/session/streaming-output.ts +40 -12
- package/src/slash-commands/builtin-registry.ts +8 -8
- package/src/task/executor.ts +8 -0
- package/src/task/render.ts +51 -44
- package/src/tools/bash-skill-urls.ts +7 -4
- package/src/tools/bash.ts +30 -33
- package/src/tools/browser/cmux/cmux-tab.ts +49 -50
- package/src/tools/browser/cmux/rpc.ts +50 -0
- package/src/tools/browser/launch.ts +8 -4
- package/src/tools/browser/run-cancellation.ts +44 -5
- package/src/tools/browser/run-output.ts +76 -0
- package/src/tools/browser/tab-worker.ts +280 -133
- package/src/tools/eval-render.ts +16 -14
- package/src/tools/glob.ts +20 -6
- package/src/tools/grep.ts +1 -0
- package/src/tools/image-gen.ts +3 -4
- package/src/tools/irc.ts +27 -13
- package/src/tools/path-utils.ts +6 -1
- package/src/tools/read.ts +14 -7
- package/src/tools/write.ts +22 -5
- package/src/tools/yield.ts +29 -1
- package/src/utils/changelog.ts +106 -9
- package/src/utils/git.ts +0 -1
- package/src/utils/title-generator.ts +72 -7
- package/src/web/search/index.ts +14 -6
- package/src/web/search/providers/perplexity.ts +18 -2
- package/dist/types/tools/bash-command-fixup.d.ts +0 -11
- package/src/tools/bash-command-fixup.ts +0 -37
|
@@ -1262,7 +1262,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1262
1262
|
textVerbosity: {
|
|
1263
1263
|
type: "enum",
|
|
1264
1264
|
values: ["low", "medium", "high"] as const,
|
|
1265
|
-
default: "
|
|
1265
|
+
default: "medium",
|
|
1266
1266
|
ui: {
|
|
1267
1267
|
tab: "model",
|
|
1268
1268
|
group: "Sampling",
|
|
@@ -1270,8 +1270,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
1270
1270
|
description: "OpenAI Responses and Codex response verbosity (low, medium, or high)",
|
|
1271
1271
|
options: [
|
|
1272
1272
|
{ value: "low", label: "Low", description: "Prefer concise responses" },
|
|
1273
|
-
{ value: "medium", label: "Medium", description: "Balance brevity and detail" },
|
|
1274
|
-
{ value: "high", label: "High", description: "Prefer detailed responses
|
|
1273
|
+
{ value: "medium", label: "Medium", description: "Balance brevity and detail (default)" },
|
|
1274
|
+
{ value: "high", label: "High", description: "Prefer detailed responses" },
|
|
1275
1275
|
],
|
|
1276
1276
|
},
|
|
1277
1277
|
},
|
|
@@ -3189,18 +3189,6 @@ export const SETTINGS_SCHEMA = {
|
|
|
3189
3189
|
},
|
|
3190
3190
|
"bashInterceptor.patterns": { type: "array", default: DEFAULT_BASH_INTERCEPTOR_RULES },
|
|
3191
3191
|
|
|
3192
|
-
"bash.stripTrailingHeadTail": {
|
|
3193
|
-
type: "boolean",
|
|
3194
|
-
default: true,
|
|
3195
|
-
ui: {
|
|
3196
|
-
tab: "shell",
|
|
3197
|
-
group: "Bash",
|
|
3198
|
-
label: "Strip head/tail Pipes",
|
|
3199
|
-
description:
|
|
3200
|
-
"Silently drop trailing `| head`/`| tail` pipes from single-line bash commands. Output is already truncated automatically.",
|
|
3201
|
-
},
|
|
3202
|
-
},
|
|
3203
|
-
|
|
3204
3192
|
// Shell output minimizer
|
|
3205
3193
|
"shellMinimizer.enabled": {
|
|
3206
3194
|
type: "boolean",
|
package/src/edit/index.ts
CHANGED
|
@@ -4,19 +4,13 @@ import { MismatchError as HashlineMismatchError } from "jeopi-hashline";
|
|
|
4
4
|
import hashlineGrammar from "jeopi-hashline/grammar.lark" with { type: "text" };
|
|
5
5
|
import hashlineDescription from "jeopi-hashline/prompt.md" with { type: "text" };
|
|
6
6
|
import { prompt } from "jeopi-utils";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
type FileDiagnosticsResult,
|
|
10
|
-
flushLspWritethroughBatch,
|
|
11
|
-
type WritethroughCallback,
|
|
12
|
-
type WritethroughDeferredHandle,
|
|
13
|
-
writethroughNoop,
|
|
14
|
-
} from "../lsp";
|
|
7
|
+
import { createLspWritethrough, flushLspWritethroughBatch, type WritethroughCallback, writethroughNoop } from "../lsp";
|
|
8
|
+
import { DeferredDiagnostics } from "../lsp/deferred-diagnostics";
|
|
15
9
|
import { getDiagnosticsLedger } from "../lsp/diagnostics-ledger";
|
|
16
10
|
import applyPatchDescription from "../prompts/tools/apply-patch.md" with { type: "text" };
|
|
17
11
|
import patchDescription from "../prompts/tools/patch.md" with { type: "text" };
|
|
18
12
|
import replaceDescription from "../prompts/tools/replace.md" with { type: "text" };
|
|
19
|
-
import type {
|
|
13
|
+
import type { ToolSession } from "../tools";
|
|
20
14
|
import { truncateForPrompt } from "../tools/approval";
|
|
21
15
|
import { isInternalUrlPath } from "../tools/path-utils";
|
|
22
16
|
import { withPathWriteLock } from "../tools/path-write-lock";
|
|
@@ -385,12 +379,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
385
379
|
readonly #fuzzyThreshold: number;
|
|
386
380
|
readonly #writethrough: WritethroughCallback;
|
|
387
381
|
readonly #editMode?: EditMode;
|
|
388
|
-
readonly #
|
|
389
|
-
readonly #pendingDeferredFetches = new Map<string, AbortController>();
|
|
390
|
-
/** Fallback per-path mutation counter used only when the session does not expose
|
|
391
|
-
* a shared one. Prefer `session.bumpFileMutationVersion` so write (and any other
|
|
392
|
-
* tool) mutating the same file also invalidates pending late-diagnostics. */
|
|
393
|
-
readonly #editVersionByPath = new Map<string, number>();
|
|
382
|
+
readonly #deferredDiagnostics: DeferredDiagnostics;
|
|
394
383
|
|
|
395
384
|
constructor(private readonly session: ToolSession) {
|
|
396
385
|
const {
|
|
@@ -402,10 +391,11 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
402
391
|
this.#editMode = resolveConfiguredEditMode(envEditVariant);
|
|
403
392
|
this.#allowFuzzy = resolveAllowFuzzy(session, editFuzzy);
|
|
404
393
|
this.#fuzzyThreshold = resolveFuzzyThreshold(session, editFuzzyThreshold);
|
|
405
|
-
|
|
394
|
+
const deduplicateDiagnostics =
|
|
406
395
|
(session.enableLsp ?? true) &&
|
|
407
396
|
session.settings.get("lsp.diagnosticsOnEdit") &&
|
|
408
397
|
session.settings.get("lsp.diagnosticsDeduplicate");
|
|
398
|
+
this.#deferredDiagnostics = new DeferredDiagnostics(session, deduplicateDiagnostics);
|
|
409
399
|
this.#writethrough = withPathWriteLock(createEditWritethrough(session));
|
|
410
400
|
}
|
|
411
401
|
|
|
@@ -550,7 +540,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
550
540
|
// doubles as the documented full-file overwrite (patch.md <avoid>).
|
|
551
541
|
allowCreateOverwrite: true,
|
|
552
542
|
writethrough: tool.#writethrough,
|
|
553
|
-
beginDeferredDiagnosticsForPath: p => tool.#
|
|
543
|
+
beginDeferredDiagnosticsForPath: p => tool.#deferredDiagnostics.begin(p),
|
|
554
544
|
}),
|
|
555
545
|
);
|
|
556
546
|
return executeSinglePathEntries(path, runs, batchRequest, onUpdate, tool.session.cwd, signal);
|
|
@@ -589,7 +579,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
589
579
|
allowFuzzy: tool.#allowFuzzy,
|
|
590
580
|
fuzzyThreshold: tool.#fuzzyThreshold,
|
|
591
581
|
writethrough: tool.#writethrough,
|
|
592
|
-
beginDeferredDiagnosticsForPath: p => tool.#
|
|
582
|
+
beginDeferredDiagnosticsForPath: p => tool.#deferredDiagnostics.begin(p),
|
|
593
583
|
}),
|
|
594
584
|
};
|
|
595
585
|
});
|
|
@@ -613,7 +603,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
613
603
|
signal,
|
|
614
604
|
batchRequest,
|
|
615
605
|
writethrough: tool.#writethrough,
|
|
616
|
-
beginDeferredDiagnosticsForPath: p => tool.#
|
|
606
|
+
beginDeferredDiagnosticsForPath: p => tool.#deferredDiagnostics.begin(p),
|
|
617
607
|
});
|
|
618
608
|
},
|
|
619
609
|
},
|
|
@@ -639,7 +629,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
639
629
|
allowFuzzy: tool.#allowFuzzy,
|
|
640
630
|
fuzzyThreshold: tool.#fuzzyThreshold,
|
|
641
631
|
writethrough: tool.#writethrough,
|
|
642
|
-
beginDeferredDiagnosticsForPath: p => tool.#
|
|
632
|
+
beginDeferredDiagnosticsForPath: p => tool.#deferredDiagnostics.begin(p),
|
|
643
633
|
}),
|
|
644
634
|
);
|
|
645
635
|
return executeSinglePathEntries(path, runs, batchRequest, onUpdate, tool.session.cwd, signal);
|
|
@@ -647,60 +637,4 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
647
637
|
},
|
|
648
638
|
}[this.mode];
|
|
649
639
|
}
|
|
650
|
-
|
|
651
|
-
#beginDeferredDiagnosticsForPath(path: string): WritethroughDeferredHandle {
|
|
652
|
-
const existingDeferred = this.#pendingDeferredFetches.get(path);
|
|
653
|
-
if (existingDeferred) {
|
|
654
|
-
existingDeferred.abort();
|
|
655
|
-
this.#pendingDeferredFetches.delete(path);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const deferredController = new AbortController();
|
|
659
|
-
const editVersion = this.#bumpFileVersion(path);
|
|
660
|
-
return {
|
|
661
|
-
onDeferredDiagnostics: (lateDiagnostics: FileDiagnosticsResult) => {
|
|
662
|
-
this.#pendingDeferredFetches.delete(path);
|
|
663
|
-
this.#injectLateDiagnostics(path, lateDiagnostics, editVersion);
|
|
664
|
-
},
|
|
665
|
-
signal: deferredController.signal,
|
|
666
|
-
finalize: (diagnostics: FileDiagnosticsResult | undefined) => {
|
|
667
|
-
if (!diagnostics) {
|
|
668
|
-
this.#pendingDeferredFetches.set(path, deferredController);
|
|
669
|
-
} else {
|
|
670
|
-
deferredController.abort();
|
|
671
|
-
}
|
|
672
|
-
},
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
#injectLateDiagnostics(path: string, diagnostics: FileDiagnosticsResult, editVersion: number): void {
|
|
677
|
-
const effective = this.#dedupDiagnostics
|
|
678
|
-
? getDiagnosticsLedger(this.session).reduce(path, diagnostics)
|
|
679
|
-
: diagnostics;
|
|
680
|
-
if (this.#dedupDiagnostics && effective.messages.length === 0) return;
|
|
681
|
-
|
|
682
|
-
const entry: DeferredDiagnosticsEntry = {
|
|
683
|
-
path,
|
|
684
|
-
summary: effective.summary ?? "",
|
|
685
|
-
messages: effective.messages ?? [],
|
|
686
|
-
errored: effective.errored,
|
|
687
|
-
// Drop at flush time if a later edit to the same file superseded this fetch.
|
|
688
|
-
isStale: () => this.#fileVersion(path) !== editVersion,
|
|
689
|
-
};
|
|
690
|
-
this.session.queueDeferredDiagnostics?.(entry);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/** Bump the file's mutation counter (session-global when available). */
|
|
694
|
-
#bumpFileVersion(path: string): number {
|
|
695
|
-
if (this.session.bumpFileMutationVersion) return this.session.bumpFileMutationVersion(path);
|
|
696
|
-
const next = (this.#editVersionByPath.get(path) ?? 0) + 1;
|
|
697
|
-
this.#editVersionByPath.set(path, next);
|
|
698
|
-
return next;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
/** Read the file's current mutation counter (session-global when available). */
|
|
702
|
-
#fileVersion(path: string): number {
|
|
703
|
-
if (this.session.getFileMutationVersion) return this.session.getFileMutationVersion(path);
|
|
704
|
-
return this.#editVersionByPath.get(path) ?? 0;
|
|
705
|
-
}
|
|
706
640
|
}
|
package/src/edit/renderer.ts
CHANGED
|
@@ -419,22 +419,25 @@ function formatStreamingDiff(
|
|
|
419
419
|
cache?: RenderedStringCache,
|
|
420
420
|
): string {
|
|
421
421
|
if (!diff) return "";
|
|
422
|
-
// Clamp the
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
//
|
|
422
|
+
// Clamp the tail to the viewport so a tall or fast-growing diff cannot
|
|
423
|
+
// outgrow the live window. Otherwise its mutating rows scroll above the
|
|
424
|
+
// native-scrollback commit boundary mid-stream and freeze into immutable
|
|
425
|
+
// history as a stale preview snapshot; the finalize repair then recommits
|
|
426
|
+
// the final render below it — a duplicated block on the tape. Collapsed
|
|
427
|
+
// gets a short fixed tail; expanded widens it to the viewport-sized window,
|
|
428
|
+
// never unbounded. The budget is VISUAL rows (a long wrapped line counts
|
|
427
429
|
// for more than one) at the framed block's inner width (border only —
|
|
428
|
-
// contentPaddingLeft is 0); only the visible suffix is syntax-colored, so
|
|
429
|
-
// cheap raw-line wrap walk keeps the per-chunk cost bounded.
|
|
430
|
-
// are in the cache salt so a resize re-slices.
|
|
430
|
+
// contentPaddingLeft is 0); only the visible suffix is syntax-colored, so
|
|
431
|
+
// the cheap raw-line wrap walk keeps the per-chunk cost bounded.
|
|
432
|
+
// innerWidth/budget are in the cache salt so a resize re-slices.
|
|
431
433
|
const innerWidth = Math.max(1, width - 2);
|
|
432
|
-
const budget = expanded ?
|
|
434
|
+
const budget = expanded ? previewWindowRows() : Math.min(EDIT_STREAMING_PREVIEW_LINES, previewWindowRows());
|
|
433
435
|
let text = cachedRenderedString(cache, uiTheme, expanded, `${rawPath}:${innerWidth}:${budget}`, diff, () => {
|
|
434
436
|
// "Cursor" tail window: pin the last rows to the bottom so freshly streamed
|
|
435
437
|
// changes stay on screen. The whole-file diff is recomputed every chunk and
|
|
436
438
|
// its Myers alignment is not monotonic in payload length, so a hunk-aware
|
|
437
|
-
// window stutters as rows move between hunks. Expanded
|
|
439
|
+
// window stutters as rows move between hunks. Expanded widens the window
|
|
440
|
+
// to the viewport; the full diff appears once the result finalizes.
|
|
438
441
|
const allLines = diff.replace(/\n+$/u, "").split("\n");
|
|
439
442
|
let visualUsed = 0;
|
|
440
443
|
let cut = allLines.length;
|
|
@@ -603,21 +603,22 @@ describe("agent() through eval runtimes", () => {
|
|
|
603
603
|
});
|
|
604
604
|
const { session, sessionFile, sessionId } = makeEvalSession(tempDir, "py-agent-interrupt", settings);
|
|
605
605
|
mockAgents();
|
|
606
|
-
//
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
// unwind via KeyboardInterrupt instead of being hard-killed (which used to
|
|
611
|
-
// surface "[kernel] Python kernel shutdown" and lose all session state).
|
|
606
|
+
// Each kernel worker thread blocks in a synchronous `urllib` bridge call,
|
|
607
|
+
// joined by `parallel()`'s ThreadPoolExecutor exit. The host must keep
|
|
608
|
+
// those already-started calls attached until they settle, then interrupt
|
|
609
|
+
// the kernel before `parallel()` launches another wave.
|
|
612
610
|
let inFlight = 0;
|
|
611
|
+
let completed = 0;
|
|
613
612
|
let markSaturated: (() => void) | undefined;
|
|
614
613
|
const saturated = new Promise<void>(resolve => {
|
|
615
614
|
markSaturated = resolve;
|
|
616
615
|
});
|
|
617
|
-
|
|
616
|
+
const releaseAgents = Promise.withResolvers<void>();
|
|
617
|
+
const runSpy = vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => {
|
|
618
618
|
// task.maxConcurrency=6 → six bridge calls block at once; signal then.
|
|
619
619
|
if (++inFlight >= 6) markSaturated?.();
|
|
620
|
-
await
|
|
620
|
+
await releaseAgents.promise;
|
|
621
|
+
completed++;
|
|
621
622
|
return singleResult(options, { output: options.assignment ?? "" });
|
|
622
623
|
});
|
|
623
624
|
|
|
@@ -640,8 +641,7 @@ describe("agent() through eval runtimes", () => {
|
|
|
640
641
|
// bridge calls (condition-driven) instead of waiting a fixed wall second.
|
|
641
642
|
void saturated.then(() => ac.abort(new Error("external interrupt")));
|
|
642
643
|
|
|
643
|
-
const
|
|
644
|
-
const result = await executePython(
|
|
644
|
+
const resultPromise = executePython(
|
|
645
645
|
"import json\nprint(json.dumps(parallel([lambda n=n: agent(str(n)) for n in range(12)])))",
|
|
646
646
|
{
|
|
647
647
|
cwd: tempDir.path(),
|
|
@@ -653,13 +653,18 @@ describe("agent() through eval runtimes", () => {
|
|
|
653
653
|
signal: ac.signal,
|
|
654
654
|
},
|
|
655
655
|
);
|
|
656
|
-
|
|
656
|
+
await saturated;
|
|
657
|
+
await Promise.resolve();
|
|
658
|
+
expect(completed).toBe(0);
|
|
659
|
+
releaseAgents.resolve();
|
|
660
|
+
const result = await resultPromise;
|
|
657
661
|
|
|
658
|
-
// Cancelled, but cleanly: no hard-kill,
|
|
659
|
-
//
|
|
662
|
+
// Cancelled, but cleanly: no hard-kill, no orphaned bridge calls, and no
|
|
663
|
+
// second fan-out wave started after the deferred abort was delivered.
|
|
660
664
|
expect(result.cancelled).toBe(true);
|
|
661
665
|
expect(result.output).not.toContain("Python kernel shutdown");
|
|
662
|
-
expect(
|
|
666
|
+
expect(completed).toBe(6);
|
|
667
|
+
expect(runSpy).toHaveBeenCalledTimes(6);
|
|
663
668
|
|
|
664
669
|
// The persistent kernel survived the interrupt: prior state is intact.
|
|
665
670
|
const after = await executePython("print(PREP_MARKER)", {
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
isEvalTimeoutControlEvent,
|
|
6
6
|
withBridgeTimeoutPause,
|
|
7
7
|
} from "../bridge-timeout";
|
|
8
|
+
import { executeWithKernelBase, type GenericKernel } from "../executor-base";
|
|
8
9
|
import type { JsStatusEvent } from "../js/shared/types";
|
|
10
|
+
import type { KernelDisplayOutput } from "../py/display";
|
|
9
11
|
|
|
10
12
|
describe("withBridgeTimeoutPause", () => {
|
|
11
13
|
it("emits one pause before the operation and one resume after it settles", async () => {
|
|
@@ -62,3 +64,61 @@ describe("withBridgeTimeoutPause", () => {
|
|
|
62
64
|
expect(isEvalTimeoutControlEvent({ op: "agent", id: "subagent-1" })).toBe(false);
|
|
63
65
|
});
|
|
64
66
|
});
|
|
67
|
+
|
|
68
|
+
class TestCancelledError extends Error {
|
|
69
|
+
readonly timedOut: boolean;
|
|
70
|
+
|
|
71
|
+
constructor(timedOut: boolean) {
|
|
72
|
+
super(timedOut ? "timed out" : "cancelled");
|
|
73
|
+
this.name = "TestCancelledError";
|
|
74
|
+
this.timedOut = timedOut;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
it("defers external aborts until an in-flight bridge call resumes", async () => {
|
|
79
|
+
const abortController = new AbortController();
|
|
80
|
+
const release = Promise.withResolvers<void>();
|
|
81
|
+
let completed = false;
|
|
82
|
+
let kernelSignal: AbortSignal | undefined;
|
|
83
|
+
const kernel: GenericKernel<Record<string, string | null>> = {
|
|
84
|
+
async execute(_code, options) {
|
|
85
|
+
kernelSignal = options.signal;
|
|
86
|
+
options.onDisplay({
|
|
87
|
+
type: "status",
|
|
88
|
+
event: { op: EVAL_TIMEOUT_PAUSE_OP },
|
|
89
|
+
} satisfies KernelDisplayOutput);
|
|
90
|
+
|
|
91
|
+
abortController.abort(new Error("external interrupt"));
|
|
92
|
+
expect(kernelSignal?.aborted).toBe(false);
|
|
93
|
+
|
|
94
|
+
await release.promise;
|
|
95
|
+
completed = true;
|
|
96
|
+
options.onDisplay({
|
|
97
|
+
type: "status",
|
|
98
|
+
event: { op: EVAL_TIMEOUT_RESUME_OP },
|
|
99
|
+
} satisfies KernelDisplayOutput);
|
|
100
|
+
return { status: "ok", cancelled: false, timedOut: false };
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const resultPromise = executeWithKernelBase({
|
|
105
|
+
kernel,
|
|
106
|
+
code: "agent('slow')",
|
|
107
|
+
options: { signal: abortController.signal },
|
|
108
|
+
runIdPrefix: "test",
|
|
109
|
+
errorLogLabel: "test",
|
|
110
|
+
cancelledErrorClass: TestCancelledError,
|
|
111
|
+
buildKernelEnvPatch: () => ({}),
|
|
112
|
+
formatKernelTimeoutAnnotation: () => "kernel timed out",
|
|
113
|
+
formatTimeoutAnnotation: () => "timed out",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await Promise.resolve();
|
|
117
|
+
expect(completed).toBe(false);
|
|
118
|
+
|
|
119
|
+
release.resolve();
|
|
120
|
+
const result = await resultPromise;
|
|
121
|
+
expect(completed).toBe(true);
|
|
122
|
+
expect(result.cancelled).toBe(true);
|
|
123
|
+
expect(result.exitCode).toBeUndefined();
|
|
124
|
+
});
|
|
@@ -3,7 +3,7 @@ import { Settings } from "../config/settings";
|
|
|
3
3
|
import { OutputSink } from "../session/streaming-output";
|
|
4
4
|
import type { ToolSession } from "../tools";
|
|
5
5
|
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
|
|
6
|
-
import { isEvalTimeoutControlEvent } from "./bridge-timeout";
|
|
6
|
+
import { EVAL_TIMEOUT_PAUSE_OP, EVAL_TIMEOUT_RESUME_OP, isEvalTimeoutControlEvent } from "./bridge-timeout";
|
|
7
7
|
import type { JsStatusEvent } from "./js/shared/types";
|
|
8
8
|
import type { KernelDisplayOutput } from "./py/display";
|
|
9
9
|
import { registerPyToolBridge } from "./py/tool-bridge";
|
|
@@ -158,6 +158,76 @@ export async function waitForPromiseWithCancellation<T>(
|
|
|
158
158
|
return await resultPromise;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
interface BridgeAbortShield {
|
|
162
|
+
signal: AbortSignal | undefined;
|
|
163
|
+
abortRequested: boolean;
|
|
164
|
+
timedOut: boolean;
|
|
165
|
+
handleStatus?: (event: JsStatusEvent) => void;
|
|
166
|
+
dispose?: () => void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function createBridgeAbortShield(source: AbortSignal | undefined): BridgeAbortShield {
|
|
170
|
+
const shield: BridgeAbortShield = {
|
|
171
|
+
signal: undefined,
|
|
172
|
+
abortRequested: false,
|
|
173
|
+
timedOut: false,
|
|
174
|
+
};
|
|
175
|
+
if (!source) return shield;
|
|
176
|
+
|
|
177
|
+
const controller = new AbortController();
|
|
178
|
+
let pauseDepth = 0;
|
|
179
|
+
let abortReason: unknown;
|
|
180
|
+
let removeAbortListener: (() => void) | undefined;
|
|
181
|
+
|
|
182
|
+
const requestAbort = (reason: unknown): void => {
|
|
183
|
+
shield.abortRequested = true;
|
|
184
|
+
shield.timedOut =
|
|
185
|
+
shield.timedOut ||
|
|
186
|
+
(typeof DOMException !== "undefined" && reason instanceof DOMException
|
|
187
|
+
? reason.name === "TimeoutError"
|
|
188
|
+
: reason instanceof Error && reason.name === "TimeoutError");
|
|
189
|
+
abortReason = reason;
|
|
190
|
+
if (pauseDepth > 0 || controller.signal.aborted) return;
|
|
191
|
+
controller.abort(reason);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const onAbort = (): void => {
|
|
195
|
+
const reason = source.reason;
|
|
196
|
+
requestAbort(reason);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
shield.signal = controller.signal;
|
|
200
|
+
shield.handleStatus = (event: JsStatusEvent): void => {
|
|
201
|
+
if (event.op === EVAL_TIMEOUT_PAUSE_OP) {
|
|
202
|
+
pauseDepth++;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (event.op !== EVAL_TIMEOUT_RESUME_OP || pauseDepth === 0) return;
|
|
206
|
+
pauseDepth--;
|
|
207
|
+
if (shield.abortRequested && !controller.signal.aborted) controller.abort(abortReason);
|
|
208
|
+
};
|
|
209
|
+
shield.dispose = (): void => {
|
|
210
|
+
removeAbortListener?.();
|
|
211
|
+
removeAbortListener = undefined;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (source.aborted) {
|
|
215
|
+
requestAbort(source.reason);
|
|
216
|
+
} else {
|
|
217
|
+
source.addEventListener("abort", onAbort, { once: true });
|
|
218
|
+
removeAbortListener = () => {
|
|
219
|
+
source.removeEventListener("abort", onAbort);
|
|
220
|
+
};
|
|
221
|
+
if (source.aborted) {
|
|
222
|
+
source.removeEventListener("abort", onAbort);
|
|
223
|
+
removeAbortListener = undefined;
|
|
224
|
+
requestAbort(source.reason);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return shield;
|
|
229
|
+
}
|
|
230
|
+
|
|
161
231
|
export function createCancelledKernelResult(output: string): KernelExecutionResult {
|
|
162
232
|
const outputBytes = Buffer.byteLength(output, "utf-8");
|
|
163
233
|
const outputLines = output.length > 0 ? 1 : 0;
|
|
@@ -304,9 +374,11 @@ export async function executeWithKernelBase<
|
|
|
304
374
|
const displayOutputs: KernelDisplayOutput[] = [];
|
|
305
375
|
const deadlineMs = (resolveDeadlineMs ?? getExecutionDeadlineMs)(options);
|
|
306
376
|
let executionTimeoutMs: number | undefined;
|
|
377
|
+
const abortShield = createBridgeAbortShield(options?.signal);
|
|
307
378
|
|
|
308
379
|
const collectDisplay = (output: KernelDisplayOutput): void => {
|
|
309
380
|
if (output.type === "status") {
|
|
381
|
+
abortShield.handleStatus?.(output.event);
|
|
310
382
|
options?.onStatus?.(output.event);
|
|
311
383
|
if (!isJulia && isEvalTimeoutControlEvent(output.event)) return;
|
|
312
384
|
}
|
|
@@ -320,8 +392,11 @@ export async function executeWithKernelBase<
|
|
|
320
392
|
options?.toolSession && options?.bridgeSessionId
|
|
321
393
|
? registerPyToolBridge(options.bridgeSessionId, runId, {
|
|
322
394
|
toolSession: options.toolSession,
|
|
323
|
-
signal:
|
|
395
|
+
signal: abortShield.signal,
|
|
324
396
|
emitStatus,
|
|
397
|
+
abortRequested: () => {
|
|
398
|
+
return abortShield.abortRequested;
|
|
399
|
+
},
|
|
325
400
|
})
|
|
326
401
|
: null;
|
|
327
402
|
|
|
@@ -338,14 +413,15 @@ export async function executeWithKernelBase<
|
|
|
338
413
|
cwd: options?.cwd,
|
|
339
414
|
env: buildKernelEnvPatch(options ?? ({} as TOptions)),
|
|
340
415
|
id: runId,
|
|
341
|
-
signal:
|
|
416
|
+
signal: abortShield.signal,
|
|
342
417
|
timeoutMs: executionTimeoutMs,
|
|
343
418
|
onChunk: text => sink.push(text),
|
|
344
419
|
onDisplay: output => collectDisplay(output),
|
|
345
420
|
});
|
|
346
421
|
|
|
347
|
-
if (result.cancelled) {
|
|
348
|
-
const
|
|
422
|
+
if (result.cancelled || abortShield.abortRequested) {
|
|
423
|
+
const timedOut = result.timedOut || abortShield.timedOut;
|
|
424
|
+
const annotation = timedOut
|
|
349
425
|
? formatKernelTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs, result.kernelKilled ?? false)
|
|
350
426
|
: undefined;
|
|
351
427
|
const dumped = await sink.dump(annotation);
|
|
@@ -397,8 +473,8 @@ export async function executeWithKernelBase<
|
|
|
397
473
|
stdinRequested: false,
|
|
398
474
|
};
|
|
399
475
|
} catch (err) {
|
|
400
|
-
if (isCancellationError(err, cancelledErrorClass) ||
|
|
401
|
-
const timedOut = isTimedOutCancellation(err, cancelledErrorClass,
|
|
476
|
+
if (isCancellationError(err, cancelledErrorClass) || abortShield.abortRequested || abortShield.signal?.aborted) {
|
|
477
|
+
const timedOut = abortShield.timedOut || isTimedOutCancellation(err, cancelledErrorClass, abortShield.signal);
|
|
402
478
|
const dumped = await sink.dump(
|
|
403
479
|
timedOut ? formatTimeoutAnnotation(executionTimeoutMs ?? options?.idleTimeoutMs) : undefined,
|
|
404
480
|
);
|
|
@@ -421,5 +497,6 @@ export async function executeWithKernelBase<
|
|
|
421
497
|
throw error;
|
|
422
498
|
} finally {
|
|
423
499
|
unregisterBridge?.();
|
|
500
|
+
abortShield.dispose?.();
|
|
424
501
|
}
|
|
425
502
|
}
|
|
@@ -48,6 +48,15 @@ type BabelClassDeclaration = {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
type BabelLexicalDecl = BabelVariableDeclaration | BabelClassDeclaration;
|
|
51
|
+
type BabelFunctionDeclaration = {
|
|
52
|
+
type: "FunctionDeclaration";
|
|
53
|
+
start: number;
|
|
54
|
+
end: number;
|
|
55
|
+
id: { start: number; end: number; name: string } | null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Top-level declarations whose bindings must survive the cell (demoted and/or published). */
|
|
59
|
+
type BabelPublishableDecl = BabelLexicalDecl | BabelFunctionDeclaration;
|
|
51
60
|
|
|
52
61
|
type BabelExpressionStatement = {
|
|
53
62
|
type: "ExpressionStatement";
|
|
@@ -322,7 +331,7 @@ function collectBindingNames(pattern: unknown, names: string[]): void {
|
|
|
322
331
|
}
|
|
323
332
|
}
|
|
324
333
|
|
|
325
|
-
function getLexicalBindingNames(node:
|
|
334
|
+
function getLexicalBindingNames(node: BabelPublishableDecl): string[] {
|
|
326
335
|
const names: string[] = [];
|
|
327
336
|
if (node.type === "VariableDeclaration") {
|
|
328
337
|
for (const declaration of node.declarations ?? []) collectBindingNames(declaration.id, names);
|
|
@@ -348,40 +357,49 @@ function appendGlobalBindingPublish(source: string, names: readonly string[]): s
|
|
|
348
357
|
* let { a, b } = obj; -> var { a, b } = obj;
|
|
349
358
|
* class Foo extends Bar {} -> var Foo = class extends Bar {};
|
|
350
359
|
*
|
|
351
|
-
* When the source must run inside the async wrapper, demoted `var`s
|
|
352
|
-
*
|
|
353
|
-
*
|
|
360
|
+
* When the source must run inside the async wrapper (top-level `await`), demoted `var`s —
|
|
361
|
+
* and the user's own top-level `var` and `function` declarations — would be scoped to the
|
|
362
|
+
* wrapper function and die with the cell. In that mode we publish every top-level binding
|
|
363
|
+
* back to the wrapper's lexical `this`, which is the worker global object.
|
|
354
364
|
*
|
|
355
|
-
* Nested declarations (inside functions, blocks, classes) are left alone
|
|
365
|
+
* Nested declarations (inside functions, blocks, classes) are left alone — they're
|
|
356
366
|
* scoped to their enclosing function/block regardless of `var` vs `let`/`const`.
|
|
357
367
|
*/
|
|
358
368
|
async function demoteTopLevelLexicals(code: string, options: { publishGlobals?: boolean } = {}): Promise<string> {
|
|
359
|
-
|
|
369
|
+
const publishGlobals = options.publishGlobals === true;
|
|
370
|
+
const fastPath = publishGlobals ? /\b(?:const|let|class|var|function)\b/ : /\b(?:const|let|class)\b/;
|
|
371
|
+
if (!fastPath.test(code)) return code;
|
|
360
372
|
|
|
361
373
|
const ast = await parseProgram(code);
|
|
362
374
|
if (!ast) {
|
|
363
375
|
return code;
|
|
364
376
|
}
|
|
365
377
|
|
|
366
|
-
const targets:
|
|
378
|
+
const targets: Array<{ node: BabelPublishableDecl; demote: boolean }> = [];
|
|
367
379
|
for (const node of ast.program.body) {
|
|
368
380
|
if (node.type === "VariableDeclaration") {
|
|
369
381
|
const decl = node as unknown as BabelVariableDeclaration;
|
|
370
|
-
if (decl.kind === "const" || decl.kind === "let") targets.push(decl);
|
|
382
|
+
if (decl.kind === "const" || decl.kind === "let") targets.push({ node: decl, demote: true });
|
|
383
|
+
else if (publishGlobals) targets.push({ node: decl, demote: false });
|
|
371
384
|
} else if (node.type === "ClassDeclaration") {
|
|
372
385
|
const decl = node as unknown as BabelClassDeclaration;
|
|
373
|
-
if (decl.id) targets.push(decl);
|
|
386
|
+
if (decl.id) targets.push({ node: decl, demote: true });
|
|
387
|
+
} else if (publishGlobals && node.type === "FunctionDeclaration") {
|
|
388
|
+
const decl = node as unknown as BabelFunctionDeclaration;
|
|
389
|
+
if (decl.id) targets.push({ node: decl, demote: false });
|
|
374
390
|
}
|
|
375
391
|
}
|
|
376
392
|
if (targets.length === 0) return code;
|
|
377
393
|
|
|
378
|
-
targets.sort((a, b) => b.start - a.start);
|
|
394
|
+
targets.sort((a, b) => b.node.start - a.node.start);
|
|
379
395
|
let result = code;
|
|
380
|
-
for (const node of targets) {
|
|
396
|
+
for (const { node, demote } of targets) {
|
|
381
397
|
const segment = result.slice(node.start, node.end);
|
|
382
|
-
const bindingNames =
|
|
398
|
+
const bindingNames = publishGlobals ? getLexicalBindingNames(node) : [];
|
|
383
399
|
let replacement: string;
|
|
384
|
-
if (
|
|
400
|
+
if (!demote) {
|
|
401
|
+
replacement = segment;
|
|
402
|
+
} else if (node.type === "VariableDeclaration") {
|
|
385
403
|
replacement = `var${segment.slice(node.kind.length)}`;
|
|
386
404
|
} else {
|
|
387
405
|
const id = node.id;
|
|
@@ -15,6 +15,7 @@ export interface PyToolBridgeEntry {
|
|
|
15
15
|
toolSession: ToolSession;
|
|
16
16
|
signal?: AbortSignal;
|
|
17
17
|
emitStatus?: (event: JsStatusEvent) => void;
|
|
18
|
+
abortRequested?: () => boolean;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export interface PyToolBridgeInfo {
|
|
@@ -31,23 +32,21 @@ const registrations = new Map<string, PyToolBridgeEntry>();
|
|
|
31
32
|
let serverPromise: Promise<BridgeServer> | null = null;
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
|
-
* Forward a bridge call to {@link callSessionTool}
|
|
35
|
-
*
|
|
36
|
-
* to fully tear down.
|
|
35
|
+
* Forward a bridge call to {@link callSessionTool} while respecting eval abort
|
|
36
|
+
* shielding.
|
|
37
37
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* persistent state lost while the subagents were still winding down. Responding
|
|
46
|
-
* immediately on abort lets the kernel raise through the blocked call and settle
|
|
47
|
-
* cleanly (preserving state); the already-signaled call keeps tearing down in
|
|
48
|
-
* the background, its eventual result/rejection swallowed.
|
|
38
|
+
* Python invokes this bridge with blocking `urllib` requests from worker threads
|
|
39
|
+
* (each `agent()` / `tool.*` call). The base executor defers the registered
|
|
40
|
+
* signal while a bridge call is already paused so in-flight subagents can finish
|
|
41
|
+
* and persist output instead of being orphaned. Once an abort has been requested,
|
|
42
|
+
* later bridge calls are rejected before starting; once the shielded signal
|
|
43
|
+
* finally aborts, this handler still resolves the HTTP request promptly so the
|
|
44
|
+
* kernel can unwind without being hard-killed.
|
|
49
45
|
*/
|
|
50
46
|
async function callSessionToolPromptOnAbort(name: string, args: unknown, entry: PyToolBridgeEntry): Promise<unknown> {
|
|
47
|
+
if (entry.abortRequested?.()) {
|
|
48
|
+
throw new Error(`bridge call ${JSON.stringify(name)} aborted: eval cell was interrupted`);
|
|
49
|
+
}
|
|
51
50
|
const call = callSessionTool(name, args, {
|
|
52
51
|
session: entry.toolSession,
|
|
53
52
|
signal: entry.signal,
|
|
@@ -15,7 +15,6 @@ export const NON_INTERACTIVE_ENV: Readonly<Record<string, string>> = {
|
|
|
15
15
|
LESS: "FRX",
|
|
16
16
|
// Disable terminal features that can block the process.
|
|
17
17
|
TERM: "dumb",
|
|
18
|
-
GPG_TTY: "not a tty",
|
|
19
18
|
NO_COLOR: "1",
|
|
20
19
|
PYTHONUNBUFFERED: "1",
|
|
21
20
|
// Disable editor and terminal credential prompts.
|
package/src/export/ttsr.ts
CHANGED
|
@@ -447,9 +447,6 @@ export class TtsrManager {
|
|
|
447
447
|
strictness: AstMatchStrictness.Smart,
|
|
448
448
|
limit: 1,
|
|
449
449
|
});
|
|
450
|
-
if (result.parseErrors && result.parseErrors.length > 0) {
|
|
451
|
-
logger.debug("TTSR ast match reported parse errors", { parseErrors: result.parseErrors });
|
|
452
|
-
}
|
|
453
450
|
return result.totalMatches > 0;
|
|
454
451
|
} catch (error) {
|
|
455
452
|
logger.warn("TTSR ast match failed, treating as no match", {
|