@oh-my-pi/pi-coding-agent 15.12.3 → 15.12.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 +43 -1
- package/dist/cli.js +1120 -870
- package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/run-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/update-notes.d.ts +1 -1
- package/dist/types/cli/args.d.ts +0 -1
- package/dist/types/cli/models-cli.d.ts +49 -0
- package/dist/types/commands/launch.d.ts +0 -3
- package/dist/types/commands/models.d.ts +33 -0
- package/dist/types/commands/token.d.ts +25 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-overview.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +1 -1
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +1 -1
- package/dist/types/commit/agentic/tools/schemas.d.ts +1 -1
- package/dist/types/commit/agentic/tools/split-commit.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/shared-llm.d.ts +1 -1
- package/dist/types/config/model-registry.d.ts +7 -0
- package/dist/types/config/models-config-schema.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +20 -0
- package/dist/types/edit/hashline/params.d.ts +1 -1
- package/dist/types/edit/modes/apply-patch.d.ts +1 -1
- package/dist/types/edit/modes/patch.d.ts +1 -1
- package/dist/types/edit/modes/replace.d.ts +1 -1
- package/dist/types/extensibility/custom-commands/types.d.ts +2 -2
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +2 -2
- package/dist/types/extensibility/hooks/types.d.ts +2 -2
- package/dist/types/goals/tools/goal-tool.d.ts +1 -1
- package/dist/types/lsp/types.d.ts +1 -1
- package/dist/types/mcp/manager.d.ts +8 -0
- package/dist/types/mnemopi/config.d.ts +28 -0
- package/dist/types/modes/acp/acp-agent.d.ts +1 -2
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/logout-account-selector.d.ts +8 -0
- package/dist/types/modes/components/status-line/component.d.ts +9 -5
- package/dist/types/modes/components/status-line/types.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +0 -17
- package/dist/types/modes/interactive-mode.d.ts +0 -3
- package/dist/types/modes/types.d.ts +0 -5
- package/dist/types/session/agent-session.d.ts +14 -33
- package/dist/types/session/agent-storage.d.ts +2 -1
- package/dist/types/session/indexed-session-storage.d.ts +1 -0
- package/dist/types/session/messages.d.ts +8 -10
- package/dist/types/session/session-manager.d.ts +15 -0
- package/dist/types/session/session-storage.d.ts +5 -0
- package/dist/types/slash-commands/helpers/logout.d.ts +15 -0
- package/dist/types/task/types.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +1 -1
- package/dist/types/tools/ast-edit.d.ts +1 -1
- package/dist/types/tools/ast-grep.d.ts +1 -1
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +202 -0
- package/dist/types/tools/browser/cmux/rpc.d.ts +70 -0
- package/dist/types/tools/browser/cmux/socket-client.d.ts +19 -0
- package/dist/types/tools/browser/registry.d.ts +16 -3
- package/dist/types/tools/browser/render.d.ts +2 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +16 -4
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/checkpoint.d.ts +1 -1
- package/dist/types/tools/debug.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +1 -1
- package/dist/types/tools/find.d.ts +1 -1
- package/dist/types/tools/gh.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/tools/inspect-image.d.ts +1 -1
- package/dist/types/tools/irc.d.ts +1 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/dist/types/tools/memory-recall.d.ts +1 -1
- package/dist/types/tools/memory-reflect.d.ts +1 -1
- package/dist/types/tools/memory-retain.d.ts +1 -1
- package/dist/types/tools/read.d.ts +1 -1
- package/dist/types/tools/render-mermaid.d.ts +1 -1
- package/dist/types/tools/resolve.d.ts +1 -1
- package/dist/types/tools/review.d.ts +1 -1
- package/dist/types/tools/search-tool-bm25.d.ts +1 -1
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +1 -1
- package/dist/types/tools/tts.d.ts +1 -1
- package/dist/types/tools/write.d.ts +1 -1
- package/dist/types/utils/clipboard.d.ts +4 -3
- package/dist/types/utils/image-loading.d.ts +18 -1
- package/dist/types/utils/thinking-display.d.ts +17 -0
- package/dist/types/web/search/index.d.ts +1 -1
- package/package.json +14 -14
- package/src/autoresearch/storage.ts +2 -1
- package/src/autoresearch/tools/init-experiment.ts +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +1 -1
- package/src/cli/args.ts +0 -8
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/bench-cli.ts +1 -1
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/models-cli.ts +427 -0
- package/src/cli-commands.ts +2 -0
- package/src/collab/host.ts +9 -12
- package/src/commands/launch.ts +0 -3
- package/src/commands/models.ts +61 -0
- package/src/commands/token.ts +89 -0
- package/src/commit/agentic/tools/analyze-file.ts +1 -1
- package/src/commit/agentic/tools/git-file-diff.ts +1 -1
- package/src/commit/agentic/tools/git-hunk.ts +1 -1
- package/src/commit/agentic/tools/git-overview.ts +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +1 -1
- package/src/commit/agentic/tools/propose-commit.ts +1 -1
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -1
- package/src/commit/agentic/tools/split-commit.ts +1 -1
- package/src/commit/analysis/summary.ts +1 -1
- package/src/commit/changelog/generate.ts +1 -1
- package/src/commit/shared-llm.ts +1 -1
- package/src/config/model-registry.ts +15 -12
- package/src/config/model-resolver.ts +2 -2
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +18 -0
- package/src/edit/hashline/params.ts +1 -1
- package/src/edit/modes/apply-patch.ts +1 -1
- package/src/edit/modes/patch.ts +1 -1
- package/src/edit/modes/replace.ts +1 -1
- package/src/eval/agent-bridge.ts +1 -1
- package/src/eval/completion-bridge.ts +1 -1
- package/src/export/html/template.js +24 -2
- package/src/export/html/tool-views.generated.js +2 -2
- package/src/extensibility/custom-commands/loader.ts +1 -1
- package/src/extensibility/custom-commands/types.ts +2 -2
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +2 -2
- package/src/extensibility/extensions/types.ts +2 -2
- package/src/extensibility/hooks/loader.ts +1 -1
- package/src/extensibility/hooks/types.ts +2 -2
- package/src/extensibility/skills.ts +18 -3
- package/src/goals/tools/goal-tool.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +5 -2
- package/src/lsp/types.ts +1 -1
- package/src/main.ts +0 -25
- package/src/mcp/config-writer.ts +7 -3
- package/src/mcp/manager.ts +11 -0
- package/src/memories/index.ts +3 -1
- package/src/memories/storage.ts +2 -1
- package/src/mnemopi/config.ts +95 -11
- package/src/modes/acp/acp-agent.ts +5 -48
- package/src/modes/acp/acp-event-mapper.ts +5 -1
- package/src/modes/components/agent-hub.ts +2 -1
- package/src/modes/components/assistant-message.ts +8 -7
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +1 -1
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/status-line/component.ts +54 -157
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line/types.ts +2 -1
- package/src/modes/controllers/command-controller.ts +0 -12
- package/src/modes/controllers/event-controller.ts +23 -62
- package/src/modes/controllers/input-controller.ts +53 -30
- package/src/modes/controllers/mcp-command-controller.ts +44 -3
- package/src/modes/controllers/selector-controller.ts +56 -10
- package/src/modes/controllers/streaming-reveal.ts +4 -3
- package/src/modes/interactive-mode.ts +2 -8
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +0 -5
- package/src/modes/utils/ui-helpers.ts +2 -1
- package/src/prompts/system/empty-stop-retry.md +4 -6
- package/src/sdk.ts +15 -19
- package/src/session/agent-session.ts +125 -234
- package/src/session/agent-storage.ts +18 -9
- package/src/session/history-storage.ts +2 -1
- package/src/session/indexed-session-storage.ts +7 -0
- package/src/session/messages.ts +9 -11
- package/src/session/session-dump-format.ts +4 -2
- package/src/session/session-manager.ts +116 -0
- package/src/session/session-storage.ts +20 -0
- package/src/slash-commands/builtin-registry.ts +15 -1
- package/src/slash-commands/helpers/logout.ts +88 -0
- package/src/task/types.ts +1 -1
- package/src/tools/ask.ts +1 -1
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/ast-grep.ts +1 -1
- package/src/tools/bash.ts +1 -1
- package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
- package/src/tools/browser/cmux/rpc.ts +156 -0
- package/src/tools/browser/cmux/socket-client.ts +309 -0
- package/src/tools/browser/registry.ts +37 -3
- package/src/tools/browser/render.ts +6 -1
- package/src/tools/browser/tab-protocol.ts +2 -0
- package/src/tools/browser/tab-supervisor.ts +189 -18
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/browser.ts +16 -1
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval.ts +11 -6
- package/src/tools/fetch.ts +13 -2
- package/src/tools/find.ts +1 -1
- package/src/tools/gh.ts +1 -1
- package/src/tools/github-cache.ts +2 -1
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -1
- package/src/tools/inspect-image.ts +3 -1
- package/src/tools/irc.ts +1 -1
- package/src/tools/job.ts +1 -1
- package/src/tools/memory-edit.ts +1 -1
- package/src/tools/memory-recall.ts +1 -1
- package/src/tools/memory-reflect.ts +1 -1
- package/src/tools/memory-retain.ts +1 -1
- package/src/tools/read.ts +8 -2
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/report-tool-issue.ts +3 -2
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +1 -1
- package/src/tools/search-tool-bm25.ts +1 -1
- package/src/tools/search.ts +1 -1
- package/src/tools/ssh.ts +1 -1
- package/src/tools/todo.ts +1 -1
- package/src/tools/tts.ts +1 -1
- package/src/tools/write.ts +1 -1
- package/src/utils/clipboard.ts +35 -18
- package/src/utils/image-loading.ts +35 -4
- package/src/utils/thinking-display.ts +37 -0
- package/src/web/search/index.ts +1 -1
- package/dist/types/cli/list-models.d.ts +0 -30
- package/src/cli/list-models.ts +0 -194
|
@@ -262,7 +262,7 @@ export class MCPAddWizard extends Container {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
this.#contentContainer.addChild(
|
|
265
|
-
new Text(theme.fg("muted", "[Only letters, numbers, dash, underscore, dot]"), 0, 0),
|
|
265
|
+
new Text(theme.fg("muted", "[Only letters, numbers, dash, underscore, dot, colon]"), 0, 0),
|
|
266
266
|
);
|
|
267
267
|
this.#contentContainer.addChild(new Text(theme.fg("muted", "[Enter to continue, Esc to cancel]"), 0, 0));
|
|
268
268
|
}
|
|
@@ -711,7 +711,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
711
711
|
if (!this.#isModelOverContextLimit(model)) {
|
|
712
712
|
return "";
|
|
713
713
|
}
|
|
714
|
-
return ` ${theme.status.disabled} context>${formatNumber(model.contextWindow).toLowerCase()}`;
|
|
714
|
+
return ` ${theme.status.disabled} context>${formatNumber(model.contextWindow ?? 0).toLowerCase()}`;
|
|
715
715
|
}
|
|
716
716
|
|
|
717
717
|
#getVisibleItems(): ReadonlyArray<ModelItem | CanonicalModelItem> {
|
|
@@ -1016,7 +1016,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1016
1016
|
const limitWarning = this.#isItemDisabled(selected)
|
|
1017
1017
|
? theme.fg(
|
|
1018
1018
|
"dim",
|
|
1019
|
-
` — current context ${formatNumber(this.#currentContextTokens).toLowerCase()} > ${formatNumber(selected.model.contextWindow).toLowerCase()} limit`,
|
|
1019
|
+
` — current context ${formatNumber(this.#currentContextTokens).toLowerCase()} > ${formatNumber(selected.model.contextWindow ?? 0).toLowerCase()} limit`,
|
|
1020
1020
|
)
|
|
1021
1021
|
: "";
|
|
1022
1022
|
this.#listContainer.addChild(
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
4
|
-
import { estimateTokens } from "@oh-my-pi/pi-agent-core/compaction";
|
|
5
4
|
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
6
5
|
import { getProjectDir } from "@oh-my-pi/pi-utils";
|
|
7
6
|
import { $ } from "bun";
|
|
@@ -11,7 +10,6 @@ import * as git from "../../../utils/git";
|
|
|
11
10
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../../../utils/session-color";
|
|
12
11
|
import { sanitizeStatusText } from "../../shared";
|
|
13
12
|
import { theme } from "../../theme/theme";
|
|
14
|
-
import { computeNonMessageTokens } from "../../utils/context-usage";
|
|
15
13
|
import { canReuseCachedPr, createPrCacheContext, isSamePrCacheContext, type PrCacheContext } from "./git-utils";
|
|
16
14
|
import { getPreset } from "./presets";
|
|
17
15
|
import { renderSegment, type SegmentContext } from "./segments";
|
|
@@ -26,30 +24,15 @@ import type {
|
|
|
26
24
|
} from "./types";
|
|
27
25
|
|
|
28
26
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
-
//
|
|
27
|
+
// Context-usage memo
|
|
30
28
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
31
29
|
|
|
32
30
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* Cache lives on the message — multiple `StatusLineComponent` instances
|
|
39
|
-
* share it for free, and entries collect with the message itself when the
|
|
40
|
-
* conversation is replaced or compacted.
|
|
41
|
-
*/
|
|
42
|
-
const kTokenCache = Symbol("statusLine.tokenCache");
|
|
43
|
-
interface TaggedMessage {
|
|
44
|
-
[kTokenCache]?: { fingerprint: string; tokens: number };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Cheap structural fingerprint mirroring `estimateTokens`'s content walk.
|
|
49
|
-
* O(blocks) — only reads string `.length` and primitives, never copies or
|
|
50
|
-
* serializes content. Any in-place mutation that alters total tokenized
|
|
51
|
-
* content also alters one of the byte-length sums or block counts captured
|
|
52
|
-
* here, forcing the cached `estimateTokens` value to be recomputed.
|
|
31
|
+
* Cheap structural fingerprint of a message's tokenizable content. O(blocks) —
|
|
32
|
+
* only reads string `.length` and primitives, never copies or serializes.
|
|
33
|
+
* Detects in-place growth of the streaming tail (and other in-place mutations)
|
|
34
|
+
* so the cached `getContextUsage()` result is recomputed when — and only when —
|
|
35
|
+
* the numbers it depends on change.
|
|
53
36
|
*/
|
|
54
37
|
function messageFingerprint(msg: AgentMessage): string {
|
|
55
38
|
const role = (msg as { role?: string }).role ?? "";
|
|
@@ -107,29 +90,17 @@ function messageFingerprint(msg: AgentMessage): string {
|
|
|
107
90
|
return `${role}:${ts}:${textLen}:${blocks}:${images}`;
|
|
108
91
|
}
|
|
109
92
|
|
|
110
|
-
|
|
111
|
-
* Token count for a single message, using the per-message sidecar cache.
|
|
112
|
-
* The caller MUST skip caching for the last message during streaming —
|
|
113
|
-
* it may still be growing and its tokens belong recomputed each refresh.
|
|
114
|
-
*/
|
|
115
|
-
function tokensForMessage(msg: AgentMessage): number {
|
|
116
|
-
const fp = messageFingerprint(msg);
|
|
117
|
-
const tagged = msg as TaggedMessage;
|
|
118
|
-
const cached = tagged[kTokenCache];
|
|
119
|
-
if (cached && cached.fingerprint === fp) return cached.tokens;
|
|
120
|
-
const tokens = estimateTokens(msg);
|
|
121
|
-
tagged[kTokenCache] = { fingerprint: fp, tokens };
|
|
122
|
-
return tokens;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
interface MessageTokenTotalsCache {
|
|
93
|
+
interface ContextUsageMemo {
|
|
126
94
|
messagesRef: readonly AgentMessage[];
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
95
|
+
length: number;
|
|
96
|
+
lastFingerprint: string | undefined;
|
|
97
|
+
modelContextWindow: number;
|
|
98
|
+
usedTokens: number | null;
|
|
99
|
+
contextWindow: number;
|
|
131
100
|
}
|
|
132
101
|
|
|
102
|
+
const EMPTY_MESSAGES: readonly AgentMessage[] = [];
|
|
103
|
+
|
|
133
104
|
function hasContextSegment(segments: readonly StatusLineSegmentId[]): boolean {
|
|
134
105
|
return segments.includes("context_pct") || segments.includes("context_total");
|
|
135
106
|
}
|
|
@@ -176,19 +147,12 @@ export class StatusLineComponent implements Component {
|
|
|
176
147
|
} | null = null;
|
|
177
148
|
#usageFetchedAt = 0;
|
|
178
149
|
#usageInFlight = false;
|
|
179
|
-
// Context
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
// promotion, matching the normal append-only session flow.
|
|
186
|
-
// Cached non-message total (system prompt + tools + skills). Invalidated
|
|
187
|
-
// when the inputs-identity fingerprint changes (model swap, skill toggle,
|
|
188
|
-
// tool registration).
|
|
189
|
-
#nonMessageTokensCache: number | undefined;
|
|
190
|
-
#nonMessageInputsKey: string | undefined;
|
|
191
|
-
#messageTokenTotalsCache: MessageTokenTotalsCache | undefined;
|
|
150
|
+
// Context-usage memo. The status line redraws on every agent event, so the
|
|
151
|
+
// hot path must not recompute context tokens unless an input changed.
|
|
152
|
+
// `getContextUsage()` anchors on the last assistant's real prompt-token
|
|
153
|
+
// count (matching the provider and the `/context` panel), so a stable
|
|
154
|
+
// message list + model window yields a stable result we can return verbatim.
|
|
155
|
+
#contextUsageCache: ContextUsageMemo | undefined;
|
|
192
156
|
|
|
193
157
|
constructor(private session: AgentSession) {
|
|
194
158
|
this.#settings = {
|
|
@@ -310,9 +274,7 @@ export class StatusLineComponent implements Component {
|
|
|
310
274
|
this.#cachedUsage = null;
|
|
311
275
|
this.#usageFetchedAt = 0;
|
|
312
276
|
this.#usageInFlight = false;
|
|
313
|
-
this.#
|
|
314
|
-
this.#nonMessageInputsKey = undefined;
|
|
315
|
-
this.#messageTokenTotalsCache = undefined;
|
|
277
|
+
this.#contextUsageCache = undefined;
|
|
316
278
|
this.#lastTokensPerSecond = null;
|
|
317
279
|
this.#lastTokensPerSecondTimestamp = null;
|
|
318
280
|
}
|
|
@@ -544,109 +506,44 @@ export class StatusLineComponent implements Component {
|
|
|
544
506
|
}
|
|
545
507
|
|
|
546
508
|
/**
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
509
|
+
* Used-tokens / context-window totals for the status-line context% segment,
|
|
510
|
+
* memoized so the per-event redraw stays O(1) when nothing changed.
|
|
511
|
+
*
|
|
512
|
+
* The numerator comes from `session.getContextUsage()`, which anchors on the
|
|
513
|
+
* last assistant's real prompt-token count — so the bar matches the provider
|
|
514
|
+
* and the `/context` panel — and reports `null` while that count is unknown
|
|
515
|
+
* (right after compaction, before the next response). Exposed (non-private)
|
|
516
|
+
* for unit tests and the collab host's state broadcast.
|
|
551
517
|
*/
|
|
552
|
-
getCachedContextBreakdown(): { usedTokens: number; contextWindow: number } {
|
|
553
|
-
const messages = this.session.messages ??
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
// when the inputs identity fingerprint changes — usually never
|
|
558
|
-
// during a streaming turn. ~10-30 ms when it does refresh.
|
|
559
|
-
const inputsKey = this.#computeNonMessageInputsKey();
|
|
560
|
-
if (this.#nonMessageTokensCache === undefined || this.#nonMessageInputsKey !== inputsKey) {
|
|
561
|
-
this.#nonMessageTokensCache = computeNonMessageTokens(this.session);
|
|
562
|
-
this.#nonMessageInputsKey = inputsKey;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// 2) Message tokens — incremental rolling total. The sidecar cache lives
|
|
566
|
-
// on each stable message object (all but the current tail). Normal
|
|
567
|
-
// streaming turns only recompute the last message and newly appended
|
|
568
|
-
// entries. Full rebuild only when the message array is replaced,
|
|
569
|
-
// shrinks, or the recently-promoted stable tail mutates in place.
|
|
570
|
-
const messagesTokens = this.#getCachedMessageTokens(messages);
|
|
571
|
-
|
|
572
|
-
const usedTokens = this.#nonMessageTokensCache + messagesTokens;
|
|
573
|
-
return { usedTokens, contextWindow };
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
#getCachedMessageTokens(messages: readonly AgentMessage[]): number {
|
|
577
|
-
const cache = this.#messageTokenTotalsCache;
|
|
578
|
-
if (!cache || cache.messagesRef !== messages || messages.length <= cache.stableCount) {
|
|
579
|
-
return this.#rebuildMessageTokenTotals(messages);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
let stableTokens = cache.stableTokens;
|
|
583
|
-
let stableCount = cache.stableCount;
|
|
584
|
-
const stableLimit = Math.max(0, messages.length - 1);
|
|
518
|
+
getCachedContextBreakdown(): { usedTokens: number | null; contextWindow: number } {
|
|
519
|
+
const messages = this.session.messages ?? EMPTY_MESSAGES;
|
|
520
|
+
const modelContextWindow = this.session.model?.contextWindow ?? 0;
|
|
521
|
+
const length = messages.length;
|
|
522
|
+
const lastFingerprint = length > 0 ? messageFingerprint(messages[length - 1]!) : undefined;
|
|
585
523
|
|
|
524
|
+
const cache = this.#contextUsageCache;
|
|
586
525
|
if (
|
|
587
|
-
cache
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
cache.
|
|
591
|
-
cache.
|
|
526
|
+
cache &&
|
|
527
|
+
cache.messagesRef === messages &&
|
|
528
|
+
cache.length === length &&
|
|
529
|
+
cache.lastFingerprint === lastFingerprint &&
|
|
530
|
+
cache.modelContextWindow === modelContextWindow
|
|
592
531
|
) {
|
|
593
|
-
return
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
while (stableCount < stableLimit) {
|
|
597
|
-
const promoted = messages[stableCount]!;
|
|
598
|
-
stableTokens += tokensForMessage(promoted);
|
|
599
|
-
stableCount++;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const lastStableMessage = stableCount > 0 ? messages[stableCount - 1] : undefined;
|
|
603
|
-
const lastStableFingerprint = lastStableMessage ? messageFingerprint(lastStableMessage) : undefined;
|
|
604
|
-
const lastMessage = messages.at(-1);
|
|
605
|
-
const lastTokens = lastMessage ? estimateTokens(lastMessage) : 0;
|
|
606
|
-
this.#messageTokenTotalsCache = {
|
|
607
|
-
messagesRef: messages,
|
|
608
|
-
stableCount,
|
|
609
|
-
stableTokens,
|
|
610
|
-
lastStableMessage,
|
|
611
|
-
lastStableFingerprint,
|
|
612
|
-
};
|
|
613
|
-
return stableTokens + lastTokens;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
#rebuildMessageTokenTotals(messages: readonly AgentMessage[]): number {
|
|
617
|
-
let stableTokens = 0;
|
|
618
|
-
const stableLimit = Math.max(0, messages.length - 1);
|
|
619
|
-
for (let i = 0; i < stableLimit; i++) {
|
|
620
|
-
stableTokens += tokensForMessage(messages[i]!);
|
|
532
|
+
return { usedTokens: cache.usedTokens, contextWindow: cache.contextWindow };
|
|
621
533
|
}
|
|
622
534
|
|
|
623
|
-
const
|
|
624
|
-
const
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
this.#messageTokenTotalsCache = {
|
|
535
|
+
const usage = this.session.getContextUsage();
|
|
536
|
+
const usedTokens = usage?.tokens ?? null;
|
|
537
|
+
const contextWindow = usage?.contextWindow ?? modelContextWindow;
|
|
538
|
+
this.#contextUsageCache = {
|
|
629
539
|
messagesRef: messages,
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
540
|
+
length,
|
|
541
|
+
lastFingerprint,
|
|
542
|
+
modelContextWindow,
|
|
543
|
+
usedTokens,
|
|
544
|
+
contextWindow,
|
|
634
545
|
};
|
|
635
|
-
return
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* Build an identity fingerprint for the non-message inputs (system prompt,
|
|
640
|
-
* tools, skills). When this changes, the non-message token cache must be
|
|
641
|
-
* recomputed. Cheap: just lengths + first-string-length. Doesn't need to
|
|
642
|
-
* be cryptographically unique — only stable for the same inputs.
|
|
643
|
-
*/
|
|
644
|
-
#computeNonMessageInputsKey(): string {
|
|
645
|
-
const sp = this.session.systemPrompt ?? [];
|
|
646
|
-
const tools = this.session.agent?.state?.tools ?? [];
|
|
647
|
-
const skills = this.session.skills ?? [];
|
|
648
|
-
const modelId = this.session.model?.id ?? "";
|
|
649
|
-
return `${modelId}|${sp.length}:${sp[0]?.length ?? 0}|${tools.length}|${skills.length}`;
|
|
546
|
+
return { usedTokens, contextWindow };
|
|
650
547
|
}
|
|
651
548
|
|
|
652
549
|
#buildSegmentContext(
|
|
@@ -673,14 +570,14 @@ export class StatusLineComponent implements Component {
|
|
|
673
570
|
tokensPerSecond: this.#getTokensPerSecond(),
|
|
674
571
|
};
|
|
675
572
|
|
|
676
|
-
let contextTokens = 0;
|
|
677
573
|
let contextWindow = state.model?.contextWindow ?? this.session.model?.contextWindow ?? 0;
|
|
574
|
+
let contextPercent: number | null = 0;
|
|
678
575
|
if (includeContext) {
|
|
679
576
|
const breakdown = this.getCachedContextBreakdown();
|
|
680
|
-
contextTokens = breakdown.usedTokens;
|
|
681
577
|
contextWindow = breakdown.contextWindow || contextWindow;
|
|
578
|
+
contextPercent =
|
|
579
|
+
breakdown.usedTokens === null ? null : contextWindow > 0 ? (breakdown.usedTokens / contextWindow) * 100 : 0;
|
|
682
580
|
}
|
|
683
|
-
let contextPercent = contextWindow > 0 ? (contextTokens / contextWindow) * 100 : 0;
|
|
684
581
|
|
|
685
582
|
// Collab guest: context comes from the host's state frames — the local
|
|
686
583
|
// replica does no accounting of its own.
|
|
@@ -364,7 +364,7 @@ const contextPctSegment: StatusLineSegment = {
|
|
|
364
364
|
const autoIcon = ctx.autoCompactEnabled && theme.icon.auto ? ` ${theme.icon.auto}` : "";
|
|
365
365
|
const text = `${formatContextUsage(pct, window)}${autoIcon}`;
|
|
366
366
|
|
|
367
|
-
const color = getContextUsageThemeColor(getContextUsageLevel(pct, window));
|
|
367
|
+
const color = getContextUsageThemeColor(getContextUsageLevel(pct ?? 0, window));
|
|
368
368
|
const content = withIcon(theme.icon.context, theme.fg(color, text));
|
|
369
369
|
|
|
370
370
|
return { content, visible: true };
|
|
@@ -71,7 +71,8 @@ export interface SegmentContext {
|
|
|
71
71
|
cost: number;
|
|
72
72
|
tokensPerSecond: number | null;
|
|
73
73
|
};
|
|
74
|
-
|
|
74
|
+
/** Context usage percent, or null when unknown (e.g. right after compaction). */
|
|
75
|
+
contextPercent: number | null;
|
|
75
76
|
contextWindow: number;
|
|
76
77
|
autoCompactEnabled: boolean;
|
|
77
78
|
subagentCount: number;
|
|
@@ -1026,11 +1026,6 @@ export class CommandController {
|
|
|
1026
1026
|
}
|
|
1027
1027
|
this.ctx.statusContainer.clear();
|
|
1028
1028
|
|
|
1029
|
-
const originalOnEscape = this.ctx.editor.onEscape;
|
|
1030
|
-
this.ctx.editor.onEscape = () => {
|
|
1031
|
-
this.ctx.session.abortCompaction();
|
|
1032
|
-
};
|
|
1033
|
-
|
|
1034
1029
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
1035
1030
|
const label = isAuto ? "Auto-compacting context... (esc to cancel)" : "Compacting context... (esc to cancel)";
|
|
1036
1031
|
const compactingLoader = new Loader(
|
|
@@ -1068,7 +1063,6 @@ export class CommandController {
|
|
|
1068
1063
|
} finally {
|
|
1069
1064
|
compactingLoader.stop();
|
|
1070
1065
|
this.ctx.statusContainer.clear();
|
|
1071
|
-
this.ctx.editor.onEscape = originalOnEscape;
|
|
1072
1066
|
}
|
|
1073
1067
|
await this.ctx.flushCompactionQueue({ willRetry: false });
|
|
1074
1068
|
return outcome;
|
|
@@ -1089,11 +1083,6 @@ export class CommandController {
|
|
|
1089
1083
|
}
|
|
1090
1084
|
this.ctx.statusContainer.clear();
|
|
1091
1085
|
|
|
1092
|
-
const originalOnEscape = this.ctx.editor.onEscape;
|
|
1093
|
-
this.ctx.editor.onEscape = () => {
|
|
1094
|
-
this.ctx.session.abortHandoff();
|
|
1095
|
-
};
|
|
1096
|
-
|
|
1097
1086
|
const handoffLoader = new Loader(
|
|
1098
1087
|
this.ctx.ui,
|
|
1099
1088
|
spinner => theme.fg("accent", spinner),
|
|
@@ -1138,7 +1127,6 @@ export class CommandController {
|
|
|
1138
1127
|
} finally {
|
|
1139
1128
|
handoffLoader.stop();
|
|
1140
1129
|
this.ctx.statusContainer.clear();
|
|
1141
|
-
this.ctx.editor.onEscape = originalOnEscape;
|
|
1142
1130
|
}
|
|
1143
1131
|
this.ctx.ui.requestRender();
|
|
1144
1132
|
}
|
|
@@ -17,8 +17,9 @@ import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
|
17
17
|
import type { InteractiveModeContext, TodoPhase } from "../../modes/types";
|
|
18
18
|
import type { PlanApprovalDetails } from "../../plan-mode/approved-plan";
|
|
19
19
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
20
|
-
import { isSilentAbort,
|
|
20
|
+
import { isSilentAbort, readQueueChipText, resolveAbortLabel } from "../../session/messages";
|
|
21
21
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
22
|
+
import { hasVisibleThinking } from "../../utils/thinking-display";
|
|
22
23
|
import { interruptHint } from "../shared";
|
|
23
24
|
import { StreamingRevealController } from "./streaming-reveal";
|
|
24
25
|
import { ToolArgsRevealController } from "./tool-args-reveal";
|
|
@@ -37,16 +38,6 @@ const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
|
37
38
|
*/
|
|
38
39
|
const MAX_LIVE_IRC_CARDS = 4;
|
|
39
40
|
|
|
40
|
-
/**
|
|
41
|
-
* Loader label shown the instant a user interrupt (Esc) is requested, kept until
|
|
42
|
-
* the agent turn fully unwinds. Esc fires the abort synchronously, but the loop
|
|
43
|
-
* only stops the spinner at `agent_end`, which it cannot reach until every
|
|
44
|
-
* in-flight tool settles its abort in `executeToolCalls` (`Promise.allSettled`).
|
|
45
|
-
* Swapping the steady "Working…" for this acknowledges the keypress instead of
|
|
46
|
-
* reading as an ignored Esc for the seconds a slow tool takes to tear down.
|
|
47
|
-
*/
|
|
48
|
-
export const INTERRUPTING_WORKING_MESSAGE = "Interrupting…";
|
|
49
|
-
|
|
50
41
|
type AgentSessionEventHandlers = {
|
|
51
42
|
[E in AgentSessionEventKind]: (event: Extract<AgentSessionEvent, { type: E }>) => Promise<void>;
|
|
52
43
|
};
|
|
@@ -63,8 +54,6 @@ export class EventController {
|
|
|
63
54
|
#renderedCustomMessages = new Set<string>();
|
|
64
55
|
#lastIntent: string | undefined = undefined;
|
|
65
56
|
#backgroundToolCallIds = new Set<string>();
|
|
66
|
-
#agentTurnActive = false;
|
|
67
|
-
#interrupting = false;
|
|
68
57
|
#readToolCallArgs = new Map<string, Record<string, unknown>>();
|
|
69
58
|
#readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
70
59
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
@@ -193,7 +182,7 @@ export class EventController {
|
|
|
193
182
|
return true;
|
|
194
183
|
}
|
|
195
184
|
#updateWorkingMessageFromIntent(intent: unknown): void {
|
|
196
|
-
if (this
|
|
185
|
+
if (this.ctx.session.isAborting) return;
|
|
197
186
|
// Streamed JSON can deliver non-string `_i` (object, number, boolean) before
|
|
198
187
|
// schema validation; `?.` only guards null/undefined, so guard the type too.
|
|
199
188
|
if (typeof intent !== "string") return;
|
|
@@ -203,19 +192,6 @@ export class EventController {
|
|
|
203
192
|
this.ctx.setWorkingMessage(`${trimmed}${interruptHint()}`);
|
|
204
193
|
}
|
|
205
194
|
|
|
206
|
-
/**
|
|
207
|
-
* Acknowledge a user interrupt (Esc) immediately: switch the loader to
|
|
208
|
-
* `INTERRUPTING_WORKING_MESSAGE` and freeze intent-driven working-message
|
|
209
|
-
* updates for the rest of the turn so a late `tool_execution_start` intent
|
|
210
|
-
* cannot repaint a "Working…/<intent>" line over the acknowledgment. Reset at
|
|
211
|
-
* the next `agent_start`. No-op outside an active turn or if already set.
|
|
212
|
-
*/
|
|
213
|
-
notifyInterrupting(): void {
|
|
214
|
-
if (!this.#agentTurnActive || this.#interrupting) return;
|
|
215
|
-
this.#interrupting = true;
|
|
216
|
-
this.ctx.setWorkingMessage(INTERRUPTING_WORKING_MESSAGE);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
195
|
subscribeToAgent(): void {
|
|
220
196
|
this.ctx.unsubscribe = this.ctx.session.subscribe(async (event: AgentSessionEvent) => {
|
|
221
197
|
await this.handleEvent(event);
|
|
@@ -233,8 +209,6 @@ export class EventController {
|
|
|
233
209
|
this.#renderedCustomMessages.clear();
|
|
234
210
|
this.#lastIntent = undefined;
|
|
235
211
|
this.#backgroundToolCallIds.clear();
|
|
236
|
-
this.#agentTurnActive = false;
|
|
237
|
-
this.#interrupting = false;
|
|
238
212
|
this.#readToolCallArgs.clear();
|
|
239
213
|
this.#readToolCallAssistantComponents.clear();
|
|
240
214
|
this.#lastAssistantComponent = undefined;
|
|
@@ -264,8 +238,6 @@ export class EventController {
|
|
|
264
238
|
}
|
|
265
239
|
|
|
266
240
|
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
|
|
267
|
-
this.#agentTurnActive = true;
|
|
268
|
-
this.#interrupting = false;
|
|
269
241
|
this.#lastIntent = undefined;
|
|
270
242
|
this.#readToolCallArgs.clear();
|
|
271
243
|
this.#readToolCallAssistantComponents.clear();
|
|
@@ -276,10 +248,6 @@ export class EventController {
|
|
|
276
248
|
this.#pinnedErrorComponent?.setErrorPinned(false);
|
|
277
249
|
this.#pinnedErrorComponent = undefined;
|
|
278
250
|
this.ctx.clearPinnedError();
|
|
279
|
-
if (this.ctx.retryEscapeHandler) {
|
|
280
|
-
this.ctx.editor.onEscape = this.ctx.retryEscapeHandler;
|
|
281
|
-
this.ctx.retryEscapeHandler = undefined;
|
|
282
|
-
}
|
|
283
251
|
if (this.ctx.retryLoader) {
|
|
284
252
|
this.ctx.retryLoader.stop();
|
|
285
253
|
this.ctx.retryLoader = undefined;
|
|
@@ -299,15 +267,10 @@ export class EventController {
|
|
|
299
267
|
this.#renderedCustomMessages.add(signature);
|
|
300
268
|
this.#resetReadGroup();
|
|
301
269
|
this.ctx.addMessageToChat(event.message);
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
// re-rendered to match. Gated on tag presence so non-queued customs
|
|
307
|
-
// (ttsr-injection, irc:*, async-result, hookMessage) skip the
|
|
308
|
-
// rebuild; their dispatch path never registered a pending chip.
|
|
309
|
-
// Mirrors the user-role refresh at the bottom of this function.
|
|
310
|
-
if (event.message.role === "custom" && readPendingDisplayTag(event.message.details)) {
|
|
270
|
+
// Queued custom-message chips are derived from the agent queue; refresh the
|
|
271
|
+
// pending bar when the queued custom is consumed so the chip disappears
|
|
272
|
+
// immediately.
|
|
273
|
+
if (event.message.role === "custom" && readQueueChipText(event.message.details)) {
|
|
311
274
|
this.ctx.updatePendingMessagesDisplay();
|
|
312
275
|
}
|
|
313
276
|
this.ctx.ui.requestRender();
|
|
@@ -475,7 +438,7 @@ export class EventController {
|
|
|
475
438
|
const visibleBlockCount = this.ctx.streamingMessage.content.filter(
|
|
476
439
|
content =>
|
|
477
440
|
(content.type === "text" && content.text.trim().length > 0) ||
|
|
478
|
-
(content.type === "thinking" && content
|
|
441
|
+
(content.type === "thinking" && hasVisibleThinking(content)),
|
|
479
442
|
).length;
|
|
480
443
|
if (visibleBlockCount > this.#lastVisibleBlockCount) {
|
|
481
444
|
this.#resetReadGroup();
|
|
@@ -823,7 +786,21 @@ export class EventController {
|
|
|
823
786
|
}
|
|
824
787
|
}
|
|
825
788
|
async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
826
|
-
|
|
789
|
+
// A superseded agent_end: the agent is already streaming a fresh turn, so
|
|
790
|
+
// this event belongs to a turn that has already been replaced. The session
|
|
791
|
+
// dispatches to listeners fire-and-forget across an async extension-emit hop
|
|
792
|
+
// (#emitSessionEvent), so an interrupted turn's agent_end can land AFTER the
|
|
793
|
+
// resumed turn's agent_start (e.g. any post-turn agent.continue()). Running
|
|
794
|
+
// the turn-end teardown now would stop the loader the live turn just created,
|
|
795
|
+
// leaving "Working…" gone while the agent keeps running. The live turn owns
|
|
796
|
+
// the loader and finalizes it at its own agent_end (isStreaming === false by
|
|
797
|
+
// then). Mirrors the collab guest's !isStreaming loader reconciler.
|
|
798
|
+
if (this.ctx.session.isStreaming) return;
|
|
799
|
+
|
|
800
|
+
await this.#finishAgentEnd();
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
async #finishAgentEnd(): Promise<void> {
|
|
827
804
|
this.#streamingReveal.stop();
|
|
828
805
|
this.#toolArgsReveal.flushAll();
|
|
829
806
|
if (this.ctx.loadingAnimation) {
|
|
@@ -871,10 +848,6 @@ export class EventController {
|
|
|
871
848
|
event: Extract<AgentSessionEvent, { type: "auto_compaction_start" }>,
|
|
872
849
|
): Promise<void> {
|
|
873
850
|
this.#cancelIdleCompaction();
|
|
874
|
-
this.ctx.autoCompactionEscapeHandler = this.ctx.editor.onEscape;
|
|
875
|
-
this.ctx.editor.onEscape = () => {
|
|
876
|
-
this.ctx.viewSession.abortCompaction();
|
|
877
|
-
};
|
|
878
851
|
this.ctx.statusContainer.clear();
|
|
879
852
|
const reasonText =
|
|
880
853
|
event.reason === "overflow"
|
|
@@ -903,10 +876,6 @@ export class EventController {
|
|
|
903
876
|
|
|
904
877
|
async #handleAutoCompactionEnd(event: Extract<AgentSessionEvent, { type: "auto_compaction_end" }>): Promise<void> {
|
|
905
878
|
this.#cancelIdleCompaction();
|
|
906
|
-
if (this.ctx.autoCompactionEscapeHandler) {
|
|
907
|
-
this.ctx.editor.onEscape = this.ctx.autoCompactionEscapeHandler;
|
|
908
|
-
this.ctx.autoCompactionEscapeHandler = undefined;
|
|
909
|
-
}
|
|
910
879
|
if (this.ctx.autoCompactionLoader) {
|
|
911
880
|
this.ctx.autoCompactionLoader.stop();
|
|
912
881
|
this.ctx.autoCompactionLoader = undefined;
|
|
@@ -964,10 +933,6 @@ export class EventController {
|
|
|
964
933
|
}
|
|
965
934
|
|
|
966
935
|
async #handleAutoRetryStart(event: Extract<AgentSessionEvent, { type: "auto_retry_start" }>): Promise<void> {
|
|
967
|
-
this.ctx.retryEscapeHandler = this.ctx.editor.onEscape;
|
|
968
|
-
this.ctx.editor.onEscape = () => {
|
|
969
|
-
this.ctx.viewSession.abortRetry();
|
|
970
|
-
};
|
|
971
936
|
this.ctx.statusContainer.clear();
|
|
972
937
|
const delaySeconds = Math.round(event.delayMs / 1000);
|
|
973
938
|
this.ctx.retryLoader = new Loader(
|
|
@@ -982,10 +947,6 @@ export class EventController {
|
|
|
982
947
|
}
|
|
983
948
|
|
|
984
949
|
async #handleAutoRetryEnd(event: Extract<AgentSessionEvent, { type: "auto_retry_end" }>): Promise<void> {
|
|
985
|
-
if (this.ctx.retryEscapeHandler) {
|
|
986
|
-
this.ctx.editor.onEscape = this.ctx.retryEscapeHandler;
|
|
987
|
-
this.ctx.retryEscapeHandler = undefined;
|
|
988
|
-
}
|
|
989
950
|
if (this.ctx.retryLoader) {
|
|
990
951
|
this.ctx.retryLoader.stop();
|
|
991
952
|
this.ctx.retryLoader = undefined;
|