@oh-my-pi/pi-tui 18.4.0 → 18.4.2
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 +42 -0
- package/dist/types/chat/image-loading.d.ts +24 -0
- package/dist/types/chat/transcript-entry.d.ts +9 -0
- package/dist/types/chrome/transcript-container.d.ts +24 -3
- package/dist/types/overlays/agents-hub.d.ts +7 -4
- package/dist/types/overlays/model-browser.d.ts +15 -0
- package/dist/types/overlays/rewind-selector.d.ts +4 -3
- package/dist/types/overlays/usage-dashboard.d.ts +9 -2
- package/dist/types/prompt/composer-attachments.d.ts +7 -0
- package/dist/types/prompt/composer-cache.d.ts +34 -16
- package/dist/types/prompt/composer.d.ts +15 -18
- package/dist/types/prompt/model-mention-autocomplete.d.ts +4 -1
- package/dist/types/prompt/welcome.d.ts +5 -2
- package/dist/types/render/width-aware-text.d.ts +6 -0
- package/dist/types/status-line/component.d.ts +13 -4
- package/dist/types/status-line/metrics.d.ts +0 -1
- package/dist/types/status-line/startup.d.ts +38 -0
- package/dist/types/status-line/types.d.ts +0 -2
- package/dist/types/terminal-capabilities.d.ts +10 -6
- package/dist/types/terminal.d.ts +7 -0
- package/dist/types/theme/color.d.ts +1 -0
- package/dist/types/theme/session-color.d.ts +3 -3
- package/dist/types/theme/shimmer.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +1 -1
- package/dist/types/tools/output-meta.d.ts +2 -1
- package/dist/types/tools/vibe.d.ts +4 -0
- package/dist/types/tui.d.ts +3 -2
- package/package.json +9 -9
- package/src/chat/assistant-message.ts +27 -21
- package/src/chat/image-loading.ts +81 -0
- package/src/chat/tool-execution.ts +53 -17
- package/src/chat/transcript-entry.ts +16 -0
- package/src/chrome/transcript-container.ts +282 -86
- package/src/components/editor.ts +28 -6
- package/src/components/image.ts +25 -9
- package/src/components/loader.ts +37 -17
- package/src/overlays/agent-hub.ts +16 -6
- package/src/overlays/agents-hub.ts +7 -18
- package/src/overlays/copy-selector.ts +8 -31
- package/src/overlays/model-browser.ts +106 -25
- package/src/overlays/model-hub.ts +49 -16
- package/src/overlays/rewind-selector.ts +60 -13
- package/src/overlays/usage-dashboard.ts +47 -6
- package/src/prompt/composer-attachments.ts +54 -7
- package/src/prompt/composer-cache.ts +217 -196
- package/src/prompt/composer.ts +45 -72
- package/src/prompt/custom-editor.ts +24 -6
- package/src/prompt/model-mention-autocomplete.ts +22 -10
- package/src/prompt/welcome.ts +31 -28
- package/src/render/width-aware-text.ts +9 -0
- package/src/status-line/component.ts +286 -182
- package/src/status-line/metrics.ts +3 -21
- package/src/status-line/segments.ts +68 -79
- package/src/status-line/startup.ts +181 -0
- package/src/status-line/types.ts +0 -2
- package/src/terminal-capabilities.ts +12 -6
- package/src/terminal.ts +75 -20
- package/src/theme/color.ts +15 -0
- package/src/theme/session-color.ts +26 -4
- package/src/theme/shimmer.ts +55 -76
- package/src/theme/theme-class.ts +29 -12
- package/src/tools/output-meta.ts +11 -3
- package/src/tools/vibe.ts +15 -2
- package/src/tui.ts +208 -154
- package/src/utils.ts +15 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "@oh-my-pi/pi-ai/usage/google-antigravity";
|
|
8
8
|
import { getNextTimeBasedPricingTransition } from "@oh-my-pi/pi-catalog/models";
|
|
9
9
|
import type { Model, ModelCost } from "@oh-my-pi/pi-catalog/types";
|
|
10
|
-
import type { VcsRepo } from "@oh-my-pi/pi-natives";
|
|
10
|
+
import type { VcsGitRepo, VcsRepo } from "@oh-my-pi/pi-natives";
|
|
11
11
|
import * as vcs from "@oh-my-pi/pi-natives/vcs";
|
|
12
12
|
import {
|
|
13
13
|
type Component,
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
StatusLineHost,
|
|
26
26
|
StatusLineSession,
|
|
27
27
|
} from "./host";
|
|
28
|
+
import type { Editor } from "../components/editor";
|
|
28
29
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../theme/session-color";
|
|
29
30
|
import { sanitizeStatusText } from "../chrome/shared";
|
|
30
31
|
import { getThemeEpoch, theme } from "../theme";
|
|
@@ -247,62 +248,69 @@ interface ContextUsageMemo {
|
|
|
247
248
|
tokenizerRef: unknown;
|
|
248
249
|
usedTokens: number;
|
|
249
250
|
contextWindow: number;
|
|
251
|
+
/** The session reported `percent: null`: usage is unknown, so no percent is shown. */
|
|
252
|
+
percentUnknown: boolean;
|
|
250
253
|
systemPromptRef: readonly string[] | undefined;
|
|
251
254
|
toolsRef: readonly any[] | undefined;
|
|
252
255
|
skillsRef: readonly any[] | undefined;
|
|
253
256
|
}
|
|
254
257
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Snapshot of the cheap external inputs a cached bar depends on. Mutable and
|
|
260
|
+
* refilled in place: the per-frame cache probe writes into a recycled
|
|
261
|
+
* instance instead of allocating a fresh ~50-field object every frame.
|
|
262
|
+
*/
|
|
263
|
+
class StatusLineExternalInputs {
|
|
264
|
+
themeRef: unknown = undefined;
|
|
265
|
+
themeEpoch = 0;
|
|
266
|
+
projectDir = "";
|
|
267
|
+
sessionRef: StatusLineSession | undefined = undefined;
|
|
268
|
+
sessionFile: string | undefined = undefined;
|
|
269
|
+
sessionSettingsRef: unknown = undefined;
|
|
270
|
+
sessionSettingsRevision = 0;
|
|
271
|
+
globalSettingsRevision = 0;
|
|
272
|
+
stateRef: unknown = undefined;
|
|
273
|
+
stateMessagesRef: readonly AgentMessage[] = EMPTY_MESSAGES;
|
|
274
|
+
stateMessagesLength = 0;
|
|
275
|
+
stateLastMessageRef: AgentMessage | undefined = undefined;
|
|
276
|
+
stateLastMessageContentRef: unknown = undefined;
|
|
277
|
+
messagesRef: readonly AgentMessage[] = EMPTY_MESSAGES;
|
|
278
|
+
messagesLength = 0;
|
|
279
|
+
lastMessageRef: AgentMessage | undefined = undefined;
|
|
280
|
+
lastMessageRole: string | undefined = undefined;
|
|
281
|
+
lastMessageTimestamp = 0;
|
|
282
|
+
lastMessageContentSize = 0;
|
|
283
|
+
lastMessageBlockCount = 0;
|
|
284
|
+
lastMessageUsageTotal = 0;
|
|
285
|
+
lastMessageStopReason: string | undefined = undefined;
|
|
286
|
+
modelRef: unknown = undefined;
|
|
287
|
+
sessionModelRef: unknown = undefined;
|
|
288
|
+
sessionModelId: string | undefined = undefined;
|
|
289
|
+
sessionModelContextWindow: Model["contextWindow"] | undefined = undefined;
|
|
290
|
+
modelId: string | undefined = undefined;
|
|
291
|
+
modelName: string | undefined = undefined;
|
|
292
|
+
modelProvider: string | undefined = undefined;
|
|
293
|
+
modelContextWindow: Model["contextWindow"] | undefined = undefined;
|
|
294
|
+
modelThinking: Model["thinking"] = undefined;
|
|
295
|
+
thinkingLevel: unknown = undefined;
|
|
296
|
+
modelCostRef: unknown = undefined;
|
|
297
|
+
contextUsageRevision = 0;
|
|
298
|
+
systemPromptRef: unknown = undefined;
|
|
299
|
+
systemPromptLength = 0;
|
|
300
|
+
systemPromptContentSize = 0;
|
|
301
|
+
toolsRef: readonly unknown[] | undefined = undefined;
|
|
302
|
+
toolsLength = 0;
|
|
303
|
+
toolSchemaMetadataRevision = 0;
|
|
304
|
+
tokenizerRef: unknown = undefined;
|
|
305
|
+
skillsRef: unknown = undefined;
|
|
306
|
+
skillsLength = 0;
|
|
307
|
+
sessionName: string | undefined = undefined;
|
|
308
|
+
sessionId: string | undefined = undefined;
|
|
309
|
+
isStreaming: boolean | undefined = undefined;
|
|
310
|
+
isAutoThinking: boolean | undefined = undefined;
|
|
311
|
+
isFastModeActive = false;
|
|
312
|
+
anthropicSlowModeLabel: string | undefined = undefined;
|
|
313
|
+
compactionSpeculation: unknown = undefined;
|
|
306
314
|
}
|
|
307
315
|
|
|
308
316
|
interface CachedStatusLine {
|
|
@@ -312,7 +320,6 @@ interface CachedStatusLine {
|
|
|
312
320
|
availableWidth: number;
|
|
313
321
|
renderRevision: number;
|
|
314
322
|
inputRevision: number;
|
|
315
|
-
placeholders: boolean;
|
|
316
323
|
previewTitle: string | undefined;
|
|
317
324
|
externalInputs: StatusLineExternalInputs;
|
|
318
325
|
}
|
|
@@ -420,8 +427,10 @@ function formatEmbeddedContextPercent(percent: number): string {
|
|
|
420
427
|
return `${percent > 0 && percent < 1 ? percent.toFixed(1) : Math.round(percent)}%`;
|
|
421
428
|
}
|
|
422
429
|
|
|
423
|
-
|
|
424
|
-
|
|
430
|
+
/** Gap width the embedded gauge needs for its labels; an unknown percent (`null`) shows the window label alone. */
|
|
431
|
+
function embeddedContextGaugeMinWidth(percent: number | null, contextWindow: number): number {
|
|
432
|
+
const percentWidth = percent === null ? 0 : formatEmbeddedContextPercent(percent).length + 2;
|
|
433
|
+
return percentWidth + formatNumber(contextWindow).length + 2;
|
|
425
434
|
}
|
|
426
435
|
|
|
427
436
|
function hasGitSegment(segments: readonly StatusLineSegmentId[]): boolean {
|
|
@@ -441,6 +450,17 @@ function hasGitBackedSegment(segments: readonly StatusLineSegmentId[]): boolean
|
|
|
441
450
|
|
|
442
451
|
type StatusLineLayout = "box" | "band" | "plain-full" | "plain-left" | "plain-right";
|
|
443
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Git views of immutable `VcsRepo` handles. `asGit()` mints a fresh napi
|
|
455
|
+
* wrapper around the same backend handle on every call, which the per-frame
|
|
456
|
+
* branch lookup would otherwise pay each render.
|
|
457
|
+
*/
|
|
458
|
+
const GIT_VIEW = Symbol("status-line.git-view");
|
|
459
|
+
|
|
460
|
+
interface GitViewTaggedRepo extends VcsRepo {
|
|
461
|
+
[GIT_VIEW]?: VcsGitRepo | null;
|
|
462
|
+
}
|
|
463
|
+
|
|
444
464
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
445
465
|
// StatusLineComponent
|
|
446
466
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -461,6 +481,22 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
461
481
|
};
|
|
462
482
|
#statusLineInputRevision = 0;
|
|
463
483
|
#statusLineClockTick = 0;
|
|
484
|
+
/** Reused probe buffer for {@link #readStatusLineExternalInputs}; handed to the new cache entry on a miss. */
|
|
485
|
+
#externalInputsProbe = new StatusLineExternalInputs();
|
|
486
|
+
/** `adjustHsv` result for the gauge's threshold tint; pure in its source hex. */
|
|
487
|
+
#dimmedAccentMemo: { sourceHex: string; hex: string } | undefined;
|
|
488
|
+
/** Gauge boundary markers; a pure function of the session's compaction settings, model, and window. */
|
|
489
|
+
#compactionBoundariesMemo:
|
|
490
|
+
| {
|
|
491
|
+
sessionRef: StatusLineSession;
|
|
492
|
+
contextWindow: number;
|
|
493
|
+
modelRef: unknown;
|
|
494
|
+
settingsRef: unknown;
|
|
495
|
+
settingsRevision: number;
|
|
496
|
+
globalSettingsRevision: number;
|
|
497
|
+
boundaries: CompactionBoundaries | null;
|
|
498
|
+
}
|
|
499
|
+
| undefined;
|
|
464
500
|
#settings: StatusLineSettings = {};
|
|
465
501
|
#effectiveSettings: EffectiveStatusLineSettings | undefined;
|
|
466
502
|
#cachedBranch: string | null | undefined = undefined;
|
|
@@ -1066,8 +1102,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1066
1102
|
const sessionName = sessionAccentEnabled ? this.session.sessionManager?.getSessionName() : undefined;
|
|
1067
1103
|
const idleHex = theme.getColorHex("dim");
|
|
1068
1104
|
const workingHex =
|
|
1069
|
-
(sessionName
|
|
1070
|
-
theme.getColorHex("accent");
|
|
1105
|
+
(sessionName && getSessionAccentHex(sessionName, theme.sessionAccentInputs)) || theme.getColorHex("accent");
|
|
1071
1106
|
const now = Date.now();
|
|
1072
1107
|
if (working !== this.#brandWorking) {
|
|
1073
1108
|
const previousTargetHex = this.#brandWorking ? workingHex : idleHex;
|
|
@@ -1263,7 +1298,12 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1263
1298
|
|
|
1264
1299
|
const gitCwd = activeRepoCache.effectiveGitCwd;
|
|
1265
1300
|
if (!repository) return null;
|
|
1266
|
-
const
|
|
1301
|
+
const taggedRepository: GitViewTaggedRepo = repository;
|
|
1302
|
+
let gitRepository = taggedRepository[GIT_VIEW];
|
|
1303
|
+
if (gitRepository === undefined) {
|
|
1304
|
+
gitRepository = repository.asGit();
|
|
1305
|
+
taggedRepository[GIT_VIEW] = gitRepository;
|
|
1306
|
+
}
|
|
1267
1307
|
if (!gitRepository) {
|
|
1268
1308
|
if (this.#jjBranchActive || Date.now() - this.#jjBranchLastFetch < JJ_REFRESH_TTL_MS) {
|
|
1269
1309
|
return this.#cachedJjBranch;
|
|
@@ -1483,6 +1523,24 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1483
1523
|
return this.#cachedGitStatusCwd === gitCwd ? this.#cachedGitStatus : null;
|
|
1484
1524
|
}
|
|
1485
1525
|
|
|
1526
|
+
/**
|
|
1527
|
+
* Seed working-tree status from the status line this one replaces (the
|
|
1528
|
+
* startup bar), so the dirty counts stay on screen instead of blanking until
|
|
1529
|
+
* this instance's first fetch lands. Normal TTL refreshes follow.
|
|
1530
|
+
*/
|
|
1531
|
+
adoptGitStatus(source: StatusLineComponent): void {
|
|
1532
|
+
if (this.#cachedGitStatusCwd === undefined && source.#cachedGitStatusCwd !== undefined) {
|
|
1533
|
+
this.#cachedGitStatus = source.#cachedGitStatus;
|
|
1534
|
+
this.#cachedGitStatusCwd = source.#cachedGitStatusCwd;
|
|
1535
|
+
this.#gitStatusLastFetch = source.#gitStatusLastFetch;
|
|
1536
|
+
}
|
|
1537
|
+
if (this.#jjStatusLastFetch === 0 && source.#jjStatusLastFetch !== 0) {
|
|
1538
|
+
this.#cachedJjStatus = source.#cachedJjStatus;
|
|
1539
|
+
this.#jjStatusLastFetch = source.#jjStatusLastFetch;
|
|
1540
|
+
}
|
|
1541
|
+
this.#invalidateStatusLineRenderCache();
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1486
1544
|
#lookupPr(activeRepoCache: ActiveRepoCache = this.#resolveActiveRepoCache()): {
|
|
1487
1545
|
number: number;
|
|
1488
1546
|
url: string;
|
|
@@ -2088,6 +2146,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2088
2146
|
const usedTokens = usage?.tokens ?? 0;
|
|
2089
2147
|
const contextWindow = usage?.contextWindow ?? modelContextWindow;
|
|
2090
2148
|
this.#contextUsageCache = {
|
|
2149
|
+
percentUnknown: usage?.percent === null,
|
|
2091
2150
|
messagesRef: messages,
|
|
2092
2151
|
length,
|
|
2093
2152
|
lastFingerprint,
|
|
@@ -2141,7 +2200,10 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2141
2200
|
const breakdown = this.getCachedContextBreakdown();
|
|
2142
2201
|
let contextTokens = breakdown.usedTokens;
|
|
2143
2202
|
contextWindow = breakdown.contextWindow || contextWindow;
|
|
2144
|
-
let contextPercent: number | null =
|
|
2203
|
+
let contextPercent: number | null =
|
|
2204
|
+
contextWindow > 0 && !this.#contextUsageCache?.percentUnknown
|
|
2205
|
+
? (breakdown.usedTokens / contextWindow) * 100
|
|
2206
|
+
: null;
|
|
2145
2207
|
// Collab guest: context comes from the host's state frames — the local
|
|
2146
2208
|
// replica does no accounting of its own.
|
|
2147
2209
|
const collabState = this.#collabStatus?.stateOverride;
|
|
@@ -2268,8 +2330,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2268
2330
|
* going through this component's setters. This deliberately uses refs,
|
|
2269
2331
|
* scalars, and string lengths only: no schema estimation, serialization,
|
|
2270
2332
|
* usage aggregation, VCS probing, or segment getters run on a cache hit.
|
|
2333
|
+
* Fills `target` in place so the per-frame probe allocates nothing.
|
|
2271
2334
|
*/
|
|
2272
|
-
#readStatusLineExternalInputs(
|
|
2335
|
+
#readStatusLineExternalInputs(target: StatusLineExternalInputs): void {
|
|
2273
2336
|
const state = this.session.state;
|
|
2274
2337
|
const stateMessages = state.messages ?? EMPTY_MESSAGES;
|
|
2275
2338
|
const stateLastMessage = stateMessages[stateMessages.length - 1];
|
|
@@ -2320,62 +2383,61 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2320
2383
|
if (typeof part === "string") systemPromptContentSize += part.length;
|
|
2321
2384
|
}
|
|
2322
2385
|
const tools = this.session.agent?.state?.tools;
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
};
|
|
2386
|
+
const skills = this.session.skills;
|
|
2387
|
+
target.themeRef = theme;
|
|
2388
|
+
target.themeEpoch = getThemeEpoch();
|
|
2389
|
+
target.projectDir = getProjectDir();
|
|
2390
|
+
target.sessionRef = this.session;
|
|
2391
|
+
target.sessionFile = this.session.sessionFile;
|
|
2392
|
+
target.sessionSettingsRef = this.host.getSessionSettingsIdentity(this.session);
|
|
2393
|
+
target.sessionSettingsRevision = this.host.getSessionSettingsRevision(this.session);
|
|
2394
|
+
target.globalSettingsRevision = this.host.getSettingsRevision();
|
|
2395
|
+
target.stateRef = state;
|
|
2396
|
+
target.stateMessagesRef = stateMessages;
|
|
2397
|
+
target.stateMessagesLength = stateMessages.length;
|
|
2398
|
+
target.stateLastMessageRef = stateLastMessage;
|
|
2399
|
+
target.stateLastMessageContentRef = stateLastMessageContent;
|
|
2400
|
+
target.messagesRef = messages;
|
|
2401
|
+
target.messagesLength = messages.length;
|
|
2402
|
+
target.lastMessageRef = lastMessage;
|
|
2403
|
+
target.lastMessageRole = lastMessageView?.role;
|
|
2404
|
+
target.lastMessageTimestamp = lastMessageView?.timestamp ?? 0;
|
|
2405
|
+
target.lastMessageContentSize = lastMessageContentSize;
|
|
2406
|
+
target.lastMessageBlockCount = lastMessageBlockCount;
|
|
2407
|
+
target.lastMessageUsageTotal = lastMessageView?.usage?.totalTokens ?? 0;
|
|
2408
|
+
target.lastMessageStopReason = lastMessageView?.stopReason;
|
|
2409
|
+
target.modelRef = model;
|
|
2410
|
+
target.sessionModelRef = sessionModel;
|
|
2411
|
+
target.sessionModelId = sessionModel?.id;
|
|
2412
|
+
target.sessionModelContextWindow = sessionModel?.contextWindow;
|
|
2413
|
+
target.modelId = model?.id;
|
|
2414
|
+
target.modelName = model?.name;
|
|
2415
|
+
target.modelProvider = model?.provider;
|
|
2416
|
+
target.modelContextWindow = model?.contextWindow;
|
|
2417
|
+
target.modelThinking = model?.thinking;
|
|
2418
|
+
target.thinkingLevel = state.thinkingLevel;
|
|
2419
|
+
target.modelCostRef = model?.cost;
|
|
2420
|
+
target.contextUsageRevision = this.session.contextUsageRevision ?? 0;
|
|
2421
|
+
target.systemPromptRef = systemPrompt;
|
|
2422
|
+
target.systemPromptLength = systemPrompt?.length ?? 0;
|
|
2423
|
+
target.systemPromptContentSize = systemPromptContentSize;
|
|
2424
|
+
target.toolsRef = tools;
|
|
2425
|
+
target.toolsLength = tools?.length ?? 0;
|
|
2426
|
+
target.toolSchemaMetadataRevision = tools ? getToolSchemaMetadataRevision(tools) : 0;
|
|
2427
|
+
target.tokenizerRef = this.session.agent?.tokenizer;
|
|
2428
|
+
target.skillsRef = skills;
|
|
2429
|
+
target.skillsLength = skills?.length ?? 0;
|
|
2430
|
+
target.sessionName = this.session.sessionManager?.getSessionName?.();
|
|
2431
|
+
target.sessionId = this.session.sessionManager?.getSessionId?.();
|
|
2432
|
+
target.isStreaming = this.session.isStreaming;
|
|
2433
|
+
target.isAutoThinking = this.session.isAutoThinking;
|
|
2434
|
+
target.isFastModeActive =
|
|
2435
|
+
typeof this.session.isFastModeActive === "function" ? this.session.isFastModeActive() : false;
|
|
2436
|
+
target.anthropicSlowModeLabel =
|
|
2437
|
+
typeof this.session.getAnthropicSlowModeLabel === "function"
|
|
2438
|
+
? this.session.getAnthropicSlowModeLabel()
|
|
2439
|
+
: undefined;
|
|
2440
|
+
target.compactionSpeculation = this.session.compactionSpeculation;
|
|
2379
2441
|
}
|
|
2380
2442
|
|
|
2381
2443
|
#sameStatusLineExternalInputs(left: StatusLineExternalInputs, right: StatusLineExternalInputs): boolean {
|
|
@@ -2439,8 +2501,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2439
2501
|
* clocks, countdowns, and VCS fallback polling advance only at their own
|
|
2440
2502
|
* display/probe cadence.
|
|
2441
2503
|
*/
|
|
2442
|
-
#statusLineClock(nowMs: number, effectiveSettings: EffectiveStatusLineSettings
|
|
2443
|
-
if (placeholders) return 0;
|
|
2504
|
+
#statusLineClock(nowMs: number, effectiveSettings: EffectiveStatusLineSettings): number {
|
|
2444
2505
|
const leftSegments = effectiveSettings.leftSegments;
|
|
2445
2506
|
const rightSegments = effectiveSettings.rightSegments;
|
|
2446
2507
|
const meter = this.#meter();
|
|
@@ -2469,17 +2530,12 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2469
2530
|
return 0;
|
|
2470
2531
|
}
|
|
2471
2532
|
|
|
2472
|
-
#buildStatusLine(
|
|
2473
|
-
width: number,
|
|
2474
|
-
layout: StatusLineLayout = "box",
|
|
2475
|
-
previewTitle?: string,
|
|
2476
|
-
options?: { readonly placeholders?: boolean },
|
|
2477
|
-
): CachedStatusLine {
|
|
2533
|
+
#buildStatusLine(width: number, layout: StatusLineLayout = "box", previewTitle?: string): CachedStatusLine {
|
|
2478
2534
|
const effectiveSettings = this.#resolveSettings();
|
|
2479
|
-
const
|
|
2480
|
-
|
|
2535
|
+
const externalInputs = this.#externalInputsProbe;
|
|
2536
|
+
this.#readStatusLineExternalInputs(externalInputs);
|
|
2481
2537
|
const nowMs = Date.now();
|
|
2482
|
-
const clockTick = this.#statusLineClock(nowMs, effectiveSettings
|
|
2538
|
+
const clockTick = this.#statusLineClock(nowMs, effectiveSettings);
|
|
2483
2539
|
if (clockTick !== this.#statusLineClockTick) {
|
|
2484
2540
|
this.#statusLineClockTick = clockTick;
|
|
2485
2541
|
this.#invalidateStatusLineRenderCache();
|
|
@@ -2491,14 +2547,15 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2491
2547
|
cached.availableWidth === width &&
|
|
2492
2548
|
cached.renderRevision === this.#renderRevision &&
|
|
2493
2549
|
cached.inputRevision === this.#statusLineInputRevision &&
|
|
2494
|
-
cached.placeholders === placeholders &&
|
|
2495
2550
|
cached.previewTitle === previewTitle &&
|
|
2496
2551
|
this.#sameStatusLineExternalInputs(cached.externalInputs, externalInputs)
|
|
2497
2552
|
) {
|
|
2498
2553
|
return cached;
|
|
2499
2554
|
}
|
|
2500
2555
|
|
|
2501
|
-
|
|
2556
|
+
// The probe now belongs to the new cache entry; later probes need their own buffer.
|
|
2557
|
+
this.#externalInputsProbe = new StatusLineExternalInputs();
|
|
2558
|
+
const content = this.#renderStatusLine(width, layout, previewTitle, nowMs);
|
|
2502
2559
|
const result = {
|
|
2503
2560
|
content,
|
|
2504
2561
|
dimmedContent: this.#dimWhileFocusProxied(content),
|
|
@@ -2506,7 +2563,6 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2506
2563
|
availableWidth: width,
|
|
2507
2564
|
renderRevision: this.#renderRevision,
|
|
2508
2565
|
inputRevision: this.#statusLineInputRevision,
|
|
2509
|
-
placeholders,
|
|
2510
2566
|
previewTitle,
|
|
2511
2567
|
externalInputs,
|
|
2512
2568
|
};
|
|
@@ -2529,16 +2585,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2529
2585
|
* `previewTitle` is a stand-in session title for composer previews; the
|
|
2530
2586
|
* `session_name` segment renders it when the session is unnamed.
|
|
2531
2587
|
*/
|
|
2532
|
-
#renderStatusLine(
|
|
2533
|
-
width: number,
|
|
2534
|
-
layout: StatusLineLayout,
|
|
2535
|
-
previewTitle: string | undefined,
|
|
2536
|
-
options: { readonly placeholders?: boolean } | undefined,
|
|
2537
|
-
nowMs: number,
|
|
2538
|
-
): string {
|
|
2588
|
+
#renderStatusLine(width: number, layout: StatusLineLayout, previewTitle: string | undefined, nowMs: number): string {
|
|
2539
2589
|
const effectiveSettings = this.#resolveSettings();
|
|
2540
2590
|
this.#syncPricingTimer();
|
|
2541
|
-
const placeholders = options?.placeholders === true;
|
|
2542
2591
|
const plain = layout !== "box" && layout !== "band";
|
|
2543
2592
|
const includePath =
|
|
2544
2593
|
hasPathSegment(effectiveSettings.leftSegments) || hasPathSegment(effectiveSettings.rightSegments);
|
|
@@ -2548,7 +2597,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2548
2597
|
(hasGitSegment(effectiveSettings.leftSegments) || hasGitSegment(effectiveSettings.rightSegments));
|
|
2549
2598
|
const includePr =
|
|
2550
2599
|
gitEnabled && (hasPrSegment(effectiveSettings.leftSegments) || hasPrSegment(effectiveSettings.rightSegments));
|
|
2551
|
-
const
|
|
2600
|
+
const ctx = this.#buildSegmentContext(
|
|
2552
2601
|
width,
|
|
2553
2602
|
effectiveSettings.segmentOptions,
|
|
2554
2603
|
includePath,
|
|
@@ -2557,7 +2606,6 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2557
2606
|
nowMs,
|
|
2558
2607
|
previewTitle,
|
|
2559
2608
|
);
|
|
2560
|
-
const ctx: SegmentContext = placeholders ? { ...liveCtx, startupPlaceholder: true } : liveCtx;
|
|
2561
2609
|
const separatorDef = plain
|
|
2562
2610
|
? { left: "·", right: "·" }
|
|
2563
2611
|
: getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
|
|
@@ -2609,8 +2657,6 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2609
2657
|
const embedContext =
|
|
2610
2658
|
!plain &&
|
|
2611
2659
|
effectiveSettings.contextLine === "embedded" &&
|
|
2612
|
-
ctx.contextPercent !== null &&
|
|
2613
|
-
ctx.contextPercent !== undefined &&
|
|
2614
2660
|
ctx.contextWindow > 0 &&
|
|
2615
2661
|
(hasContextSegment(leftSegIds) || hasContextSegment(rightSegIds)) &&
|
|
2616
2662
|
(hasNonContextSegment(leftSegIds) || hasNonContextSegment(rightSegIds));
|
|
@@ -2630,17 +2676,16 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2630
2676
|
job => job.type !== "task" || job.agentId === undefined || !this.#runningSubagentIds.has(job.agentId),
|
|
2631
2677
|
).length ?? 0;
|
|
2632
2678
|
if (runningBackgroundJobs > 0) {
|
|
2633
|
-
|
|
2634
|
-
rightParts.unshift(theme.fg("statusLineSubagents", `${theme.icon.job} ${count}`));
|
|
2635
|
-
}
|
|
2636
|
-
if (subagentBadge) {
|
|
2637
|
-
const content = placeholders ? [theme.icon.agents, "…"].filter(Boolean).join(" ") : subagentBadge;
|
|
2638
|
-
rightParts.unshift(placeholders ? theme.fg("statusLineSubagents", content) : content);
|
|
2679
|
+
rightParts.unshift(theme.fg("statusLineSubagents", `${theme.icon.job} ${runningBackgroundJobs}`));
|
|
2639
2680
|
}
|
|
2681
|
+
if (subagentBadge) rightParts.unshift(subagentBadge);
|
|
2640
2682
|
}
|
|
2641
2683
|
const topFillWidth = Math.max(0, width);
|
|
2642
|
-
|
|
2643
|
-
const
|
|
2684
|
+
// These arrays are local to this render; overflow handling can mutate them.
|
|
2685
|
+
const left = leftParts;
|
|
2686
|
+
const right = rightParts;
|
|
2687
|
+
const leftWidths = left.map(part => visibleWidth(part));
|
|
2688
|
+
const rightWidths = right.map(part => visibleWidth(part));
|
|
2644
2689
|
|
|
2645
2690
|
const leftSepWidth = visibleWidth(separatorDef.left);
|
|
2646
2691
|
const rightSepWidth = visibleWidth(separatorDef.right);
|
|
@@ -2654,23 +2699,21 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2654
2699
|
const bandCap = layout === "band" && separatorDef.endCaps && !transparentBg ? theme.sep.powerlineCapLeft : "";
|
|
2655
2700
|
const bandCapWidth = visibleWidth(bandCap);
|
|
2656
2701
|
|
|
2657
|
-
const groupWidth = (
|
|
2658
|
-
if (
|
|
2659
|
-
const partsWidth =
|
|
2660
|
-
const sepTotal =
|
|
2702
|
+
const groupWidth = (widths: number[], capWidth: number, sepWidth: number): number => {
|
|
2703
|
+
if (widths.length === 0) return 0;
|
|
2704
|
+
const partsWidth = widths.reduce((sum, partWidth) => sum + partWidth, 0);
|
|
2705
|
+
const sepTotal = (widths.length - 1) * (sepWidth + 2);
|
|
2661
2706
|
return partsWidth + sepTotal + 2 + capWidth;
|
|
2662
2707
|
};
|
|
2663
2708
|
|
|
2664
|
-
let leftWidth = groupWidth(
|
|
2665
|
-
let rightWidth = groupWidth(
|
|
2709
|
+
let leftWidth = groupWidth(leftWidths, leftCapWidth + bandCapWidth, leftSepWidth);
|
|
2710
|
+
let rightWidth = groupWidth(rightWidths, rightCapWidth, rightSepWidth);
|
|
2666
2711
|
// Embedded mode removes the standalone context segment before overflow
|
|
2667
2712
|
// handling, so the gauge must reserve enough room for both labels. Without
|
|
2668
2713
|
// this budget a long path/session title can leave a one-cell gap: the
|
|
2669
2714
|
// context segment is gone, and the gauge silently omits its labels too.
|
|
2670
2715
|
const embeddedContextWidth = embedContext
|
|
2671
|
-
? ctx.
|
|
2672
|
-
? "…%".length + "…".length + 4
|
|
2673
|
-
: embeddedContextGaugeMinWidth(ctx.contextPercent ?? 0, ctx.contextWindow)
|
|
2716
|
+
? embeddedContextGaugeMinWidth(ctx.contextPercent, ctx.contextWindow)
|
|
2674
2717
|
: 0;
|
|
2675
2718
|
const minimumGapWidth = (): number => {
|
|
2676
2719
|
if (!embeddedContextWidth) return left.length > 0 && right.length > 0 ? 1 : 0;
|
|
@@ -2693,23 +2736,27 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2693
2736
|
if (nameSegIdx >= 0 && totalWidth() > topFillWidth) {
|
|
2694
2737
|
// Badge/job parts were unshifted ahead of the tracked segment ids.
|
|
2695
2738
|
const nameIdx = nameSegIdx + (right.length - rightSegIds.length);
|
|
2696
|
-
const currentNameVW =
|
|
2739
|
+
const currentNameVW = rightWidths[nameIdx];
|
|
2697
2740
|
const minNameVW = 8;
|
|
2698
2741
|
const shrinkBy = Math.min(Math.max(0, currentNameVW - minNameVW), totalWidth() - topFillWidth);
|
|
2699
2742
|
if (shrinkBy > 0) {
|
|
2700
2743
|
right[nameIdx] = truncateToWidth(right[nameIdx], currentNameVW - shrinkBy);
|
|
2701
|
-
|
|
2744
|
+
const nameWidth = visibleWidth(right[nameIdx]);
|
|
2745
|
+
rightWidth += nameWidth - currentNameVW;
|
|
2746
|
+
rightWidths[nameIdx] = nameWidth;
|
|
2702
2747
|
}
|
|
2703
2748
|
}
|
|
2704
2749
|
while (totalWidth() > topFillWidth && right.length > 0) {
|
|
2750
|
+
const removedWidth = rightWidths[right.length - 1];
|
|
2705
2751
|
right.pop();
|
|
2706
|
-
|
|
2752
|
+
rightWidths.pop();
|
|
2753
|
+
rightWidth = right.length > 0 ? rightWidth - removedWidth - rightSepWidth - 2 : 0;
|
|
2707
2754
|
}
|
|
2708
2755
|
// Shrink path before dropping left segments — path is the only elastic segment
|
|
2709
2756
|
const pathIdx = leftSegIds.indexOf("path");
|
|
2710
2757
|
if (pathIdx >= 0 && totalWidth() > topFillWidth) {
|
|
2711
2758
|
const overflow = totalWidth() - topFillWidth;
|
|
2712
|
-
const currentPathVW =
|
|
2759
|
+
const currentPathVW = leftWidths[pathIdx];
|
|
2713
2760
|
const minPathVW = 8; // icon + ellipsis + a few chars
|
|
2714
2761
|
const shrinkable = currentPathVW - minPathVW;
|
|
2715
2762
|
if (shrinkable > 0) {
|
|
@@ -2722,9 +2769,10 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2722
2769
|
});
|
|
2723
2770
|
let reRendered = renderSegment("path", pathCtx(newMaxLen));
|
|
2724
2771
|
if (reRendered.visible && reRendered.content) {
|
|
2772
|
+
let pathWidth = visibleWidth(reRendered.content);
|
|
2725
2773
|
// maxLength governs path text, not icon prefix; iterate to compensate
|
|
2726
2774
|
for (let i = 0; i < 8; i++) {
|
|
2727
|
-
const saved = currentPathVW -
|
|
2775
|
+
const saved = currentPathVW - pathWidth;
|
|
2728
2776
|
if (saved >= shrinkBy) break;
|
|
2729
2777
|
const nextMaxLen = Math.max(4, newMaxLen - (shrinkBy - saved));
|
|
2730
2778
|
if (nextMaxLen >= newMaxLen) break; // no progress or hit floor
|
|
@@ -2732,9 +2780,11 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2732
2780
|
const adjusted = renderSegment("path", pathCtx(newMaxLen));
|
|
2733
2781
|
if (!adjusted.visible || !adjusted.content) break;
|
|
2734
2782
|
reRendered = adjusted;
|
|
2783
|
+
pathWidth = visibleWidth(adjusted.content);
|
|
2735
2784
|
}
|
|
2736
2785
|
left[pathIdx] = reRendered.content;
|
|
2737
|
-
leftWidth
|
|
2786
|
+
leftWidth += pathWidth - currentPathVW;
|
|
2787
|
+
leftWidths[pathIdx] = pathWidth;
|
|
2738
2788
|
}
|
|
2739
2789
|
}
|
|
2740
2790
|
}
|
|
@@ -2751,9 +2801,11 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2751
2801
|
|
|
2752
2802
|
while (totalWidth() > topFillWidth && left.length > 0) {
|
|
2753
2803
|
const dropIdx = leftOverflowDropIndex();
|
|
2804
|
+
const removedWidth = leftWidths[dropIdx];
|
|
2754
2805
|
left.splice(dropIdx, 1);
|
|
2806
|
+
leftWidths.splice(dropIdx, 1);
|
|
2755
2807
|
leftSegIds.splice(dropIdx, 1);
|
|
2756
|
-
leftWidth =
|
|
2808
|
+
leftWidth = left.length > 0 ? leftWidth - removedWidth - leftSepWidth - 2 : 0;
|
|
2757
2809
|
}
|
|
2758
2810
|
}
|
|
2759
2811
|
|
|
@@ -2824,11 +2876,13 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2824
2876
|
const horizontal = theme.boxRound.horizontal;
|
|
2825
2877
|
const mode = effectiveSettings.contextLine ?? "embedded";
|
|
2826
2878
|
const pct = ctx.contextPercent;
|
|
2827
|
-
if (mode === "off" || pct === null
|
|
2879
|
+
if (mode === "off" || (pct === null && ctx.contextWindow <= 0)) {
|
|
2828
2880
|
return `\x1b[49m${usedColor}${horizontal.repeat(gapWidth)}\x1b[39m`;
|
|
2829
2881
|
}
|
|
2830
2882
|
|
|
2831
|
-
|
|
2883
|
+
// Unknown usage against a known window (startup prepaint) draws the fresh-session
|
|
2884
|
+
// gauge: one lit cell and the window label, no percent.
|
|
2885
|
+
const clampedPct = pct === null ? 0 : Math.min(100, Math.max(0, pct));
|
|
2832
2886
|
let percentLabel = "";
|
|
2833
2887
|
let windowLabel = "";
|
|
2834
2888
|
let percentStart = -1;
|
|
@@ -2837,14 +2891,11 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2837
2891
|
// >100%: usage anchored past the active window (e.g. model switch to a
|
|
2838
2892
|
// smaller window). The bar clamps full, but the embedded label breaks
|
|
2839
2893
|
// past the window label — `──200K─120%` with the percent in error color.
|
|
2840
|
-
const percentOverflow = pct > 100;
|
|
2894
|
+
const percentOverflow = pct !== null && pct > 100;
|
|
2841
2895
|
if (embedContext) {
|
|
2842
|
-
const candidatePercent =
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
const candidateWindow = ctx.startupPlaceholder ? "…" : formatNumber(ctx.contextWindow);
|
|
2846
|
-
const minimumLabelWidth = candidatePercent.length + candidateWindow.length + 4;
|
|
2847
|
-
if (gapWidth >= minimumLabelWidth) {
|
|
2896
|
+
const candidatePercent = pct === null ? "" : formatEmbeddedContextPercent(percentOverflow ? pct : clampedPct);
|
|
2897
|
+
const candidateWindow = formatNumber(ctx.contextWindow);
|
|
2898
|
+
if (gapWidth >= embeddedContextGaugeMinWidth(pct, ctx.contextWindow)) {
|
|
2848
2899
|
percentLabel = candidatePercent;
|
|
2849
2900
|
windowLabel = candidateWindow;
|
|
2850
2901
|
if (percentOverflow) {
|
|
@@ -2906,8 +2957,12 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2906
2957
|
const speculationColor = theme.getFgAnsi("muted");
|
|
2907
2958
|
const overflowColor = theme.getFgAnsi("error");
|
|
2908
2959
|
const rawAccentHex = accentHex ?? theme.getColorHex("borderAccent");
|
|
2909
|
-
|
|
2910
|
-
|
|
2960
|
+
let dimmedAccent = this.#dimmedAccentMemo;
|
|
2961
|
+
if (dimmedAccent?.sourceHex !== rawAccentHex) {
|
|
2962
|
+
dimmedAccent = { sourceHex: rawAccentHex, hex: adjustHsv(rawAccentHex, { s: 0.7, v: 0.75 }) };
|
|
2963
|
+
this.#dimmedAccentMemo = dimmedAccent;
|
|
2964
|
+
}
|
|
2965
|
+
const thresholdColor = getSessionAccentAnsi(dimmedAccent.hex) ?? usedColor;
|
|
2911
2966
|
|
|
2912
2967
|
let out = "\x1b[49m";
|
|
2913
2968
|
let activeColor = "";
|
|
@@ -2938,17 +2993,66 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2938
2993
|
|
|
2939
2994
|
/** Auto-compaction boundary percents, or null when unavailable (disabled, no window). */
|
|
2940
2995
|
#compactionBoundaries(contextWindow: number): CompactionBoundaries | null {
|
|
2941
|
-
const
|
|
2996
|
+
const session = this.session;
|
|
2997
|
+
const model = session.state?.model ?? session.model;
|
|
2998
|
+
const settingsRef = this.host.getSessionSettingsIdentity(session);
|
|
2999
|
+
const settingsRevision = this.host.getSessionSettingsRevision(session);
|
|
3000
|
+
const globalSettingsRevision = this.host.getSettingsRevision();
|
|
3001
|
+
const memo = this.#compactionBoundariesMemo;
|
|
3002
|
+
if (
|
|
3003
|
+
memo &&
|
|
3004
|
+
memo.sessionRef === session &&
|
|
3005
|
+
memo.contextWindow === contextWindow &&
|
|
3006
|
+
memo.modelRef === model &&
|
|
3007
|
+
memo.settingsRef === settingsRef &&
|
|
3008
|
+
memo.settingsRevision === settingsRevision &&
|
|
3009
|
+
memo.globalSettingsRevision === globalSettingsRevision
|
|
3010
|
+
) {
|
|
3011
|
+
return memo.boundaries;
|
|
3012
|
+
}
|
|
2942
3013
|
try {
|
|
2943
|
-
|
|
3014
|
+
const boundaries = this.host.computeCompactionBoundaries(session, contextWindow, model);
|
|
3015
|
+
this.#compactionBoundariesMemo = {
|
|
3016
|
+
sessionRef: session,
|
|
3017
|
+
contextWindow,
|
|
3018
|
+
modelRef: model,
|
|
3019
|
+
settingsRef,
|
|
3020
|
+
settingsRevision,
|
|
3021
|
+
globalSettingsRevision,
|
|
3022
|
+
boundaries,
|
|
3023
|
+
};
|
|
3024
|
+
return boundaries;
|
|
2944
3025
|
} catch {
|
|
2945
3026
|
return null;
|
|
2946
3027
|
}
|
|
2947
3028
|
}
|
|
2948
3029
|
|
|
2949
|
-
/**
|
|
2950
|
-
|
|
2951
|
-
|
|
3030
|
+
/**
|
|
3031
|
+
* Wire this bar into `editor` for a composer layout: the matching top-border
|
|
3032
|
+
* provider, the autocomplete probe, and the standalone bottom-bar placement.
|
|
3033
|
+
* Callers re-run it whenever the composer shape changes.
|
|
3034
|
+
*/
|
|
3035
|
+
attachToEditor(
|
|
3036
|
+
editor: Pick<Editor, "isAutocompleteActive" | "setTopBorderProvider" | "setTopBorder">,
|
|
3037
|
+
style: Pick<ComposerStyle, "statusAttachment" | "bottomBar" | "bottomBarGap">,
|
|
3038
|
+
): void {
|
|
3039
|
+
this.setAutocompleteActiveProbe(() => editor.isAutocompleteActive());
|
|
3040
|
+
switch (style.statusAttachment) {
|
|
3041
|
+
case "top-border":
|
|
3042
|
+
editor.setTopBorderProvider(availableWidth => this.getTopBorder(availableWidth));
|
|
3043
|
+
break;
|
|
3044
|
+
case "top-band":
|
|
3045
|
+
editor.setTopBorderProvider(availableWidth => this.getBandTopBorder(availableWidth));
|
|
3046
|
+
break;
|
|
3047
|
+
case "top-rule-chip":
|
|
3048
|
+
editor.setTopBorderProvider(availableWidth => this.getStandaloneTopBorder(availableWidth));
|
|
3049
|
+
break;
|
|
3050
|
+
case "none":
|
|
3051
|
+
editor.setTopBorderProvider(undefined);
|
|
3052
|
+
editor.setTopBorder(undefined);
|
|
3053
|
+
break;
|
|
3054
|
+
}
|
|
3055
|
+
this.setComposerStyle(style);
|
|
2952
3056
|
}
|
|
2953
3057
|
|
|
2954
3058
|
getTopBorder(width: number, previewTitle?: string): { content: string; width: number; revision: number } {
|