@oh-my-pi/pi-coding-agent 16.5.0 → 16.5.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 +91 -0
- package/dist/cli.js +3556 -3504
- package/dist/types/advisor/advise-tool.d.ts +12 -1
- package/dist/types/advisor/runtime.d.ts +41 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +4 -1
- package/dist/types/cli/usage-cli.d.ts +3 -0
- package/dist/types/cli/usage-error.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +2 -2
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +8 -1
- package/dist/types/config/models-config.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/discovery/substitute-plugin-root.d.ts +22 -0
- package/dist/types/eval/__tests__/process-entry-import.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +3 -3
- package/dist/types/eval/bridge-timeout.d.ts +9 -1
- package/dist/types/eval/js/context-manager.d.ts +5 -3
- package/dist/types/eval/js/process-entry.d.ts +6 -0
- package/dist/types/eval/js/worker-core.d.ts +15 -1
- package/dist/types/eval/py/spawn-options.d.ts +10 -0
- 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/extensibility/extensions/wrapper.d.ts +3 -6
- package/dist/types/extensibility/plugins/bun-git-cache.d.ts +3 -0
- package/dist/types/goals/guided-setup.d.ts +12 -0
- package/dist/types/internal-urls/history-protocol.d.ts +3 -2
- package/dist/types/internal-urls/memory-protocol.d.ts +6 -7
- package/dist/types/internal-urls/registry-helpers.d.ts +19 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +2 -0
- package/dist/types/modes/components/__tests__/dynamic-border.test.d.ts +1 -0
- package/dist/types/modes/components/agent-hub.d.ts +10 -0
- package/dist/types/modes/components/dynamic-border.d.ts +5 -3
- package/dist/types/modes/components/login-dialog.d.ts +2 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +0 -2
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -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 +26 -6
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/ultrathink.d.ts +1 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +12 -0
- package/dist/types/modes/workflow.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/exit-diagnostics.d.ts +11 -0
- package/dist/types/session/messages.d.ts +15 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +11 -0
- package/dist/types/subprocess/worker-client.d.ts +6 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -0
- package/dist/types/tools/grep.d.ts +0 -2
- package/dist/types/tools/read.d.ts +0 -4
- package/dist/types/web/search/provider.d.ts +10 -3
- package/dist/types/web/search/providers/codex.d.ts +5 -4
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +917 -42
- package/src/advisor/advise-tool.ts +17 -1
- package/src/advisor/runtime.ts +270 -67
- package/src/autolearn/controller.ts +15 -3
- package/src/autoresearch/dashboard.ts +2 -2
- package/src/cli/args.ts +12 -0
- package/src/cli/auth-broker-cli.ts +30 -11
- package/src/cli/auth-gateway-cli.ts +5 -1
- package/src/cli/config-cli.ts +15 -3
- package/src/cli/dry-balance-cli.ts +14 -4
- package/src/cli/flag-tables.ts +21 -7
- package/src/cli/update-cli.ts +62 -11
- package/src/cli/usage-cli.ts +58 -5
- package/src/cli/usage-error.ts +7 -0
- package/src/cli.ts +23 -1
- package/src/commands/acp.ts +11 -2
- package/src/commands/launch.ts +12 -3
- package/src/commands/token.ts +3 -1
- package/src/config/api-key-resolver.ts +12 -3
- package/src/config/config-file.ts +30 -12
- package/src/config/model-registry.ts +7 -7
- package/src/config/model-resolver.ts +21 -7
- package/src/config/models-config.ts +1 -1
- package/src/config/settings-schema.ts +10 -0
- package/src/cursor.ts +2 -0
- package/src/discovery/claude-plugins.ts +9 -3
- package/src/discovery/omp-plugins.ts +6 -2
- package/src/discovery/substitute-plugin-root.ts +32 -0
- package/src/eval/__tests__/agent-bridge.test.ts +19 -14
- package/src/eval/__tests__/bridge-timeout.test.ts +106 -0
- package/src/eval/__tests__/js-context-manager.test.ts +158 -1
- package/src/eval/__tests__/kernel-spawn.test.ts +12 -0
- package/src/eval/__tests__/prelude-agent.test.ts +20 -0
- package/src/eval/__tests__/process-entry-import.test.ts +27 -0
- package/src/eval/agent-bridge.ts +121 -116
- package/src/eval/backend.ts +3 -3
- package/src/eval/bridge-timeout.ts +20 -2
- package/src/eval/executor-base.ts +85 -7
- package/src/eval/jl/kernel.ts +2 -1
- package/src/eval/js/context-manager.ts +109 -32
- package/src/eval/js/process-entry.ts +27 -0
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/js/worker-core.ts +70 -9
- package/src/eval/js/worker-entry.ts +1 -1
- package/src/eval/py/__tests__/prelude.test.ts +72 -0
- package/src/eval/py/kernel.ts +2 -1
- package/src/eval/py/prelude.py +28 -1
- package/src/eval/py/spawn-options.ts +13 -0
- package/src/eval/py/tool-bridge.ts +13 -14
- package/src/eval/rb/kernel.ts +2 -1
- package/src/exec/bash-executor.ts +30 -43
- 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/extensions/wrapper.ts +18 -18
- package/src/extensibility/plugins/bun-git-cache.ts +91 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +32 -16
- package/src/extensibility/plugins/manager.ts +28 -7
- package/src/goals/guided-setup.ts +29 -1
- package/src/internal-urls/history-protocol.ts +95 -15
- package/src/internal-urls/memory-protocol.ts +13 -9
- package/src/internal-urls/registry-helpers.ts +50 -1
- package/src/launch/broker.ts +38 -25
- package/src/lsp/client.ts +7 -1
- package/src/main.ts +29 -0
- package/src/mcp/oauth-discovery.ts +20 -1
- package/src/mcp/oauth-flow.ts +3 -1
- package/src/mcp/tool-bridge.ts +57 -6
- package/src/modes/components/__tests__/dynamic-border.test.ts +55 -0
- package/src/modes/components/agent-dashboard.ts +2 -2
- package/src/modes/components/agent-hub.ts +15 -2
- package/src/modes/components/agent-transcript-viewer.ts +2 -2
- package/src/modes/components/chat-transcript-builder.ts +26 -4
- package/src/modes/components/dynamic-border.ts +9 -6
- package/src/modes/components/extensions/extension-list.ts +2 -2
- package/src/modes/components/hook-selector.ts +10 -4
- package/src/modes/components/login-dialog.ts +5 -0
- package/src/modes/components/mcp-add-wizard.ts +5 -0
- package/src/modes/components/plan-review-overlay.ts +11 -11
- package/src/modes/components/read-tool-group.ts +1 -8
- package/src/modes/components/status-line/component.ts +10 -1
- package/src/modes/components/transcript-container.ts +110 -7
- package/src/modes/controllers/command-controller.ts +12 -4
- package/src/modes/controllers/event-controller.ts +80 -15
- package/src/modes/controllers/input-controller.ts +4 -2
- package/src/modes/controllers/mcp-command-controller.ts +6 -7
- package/src/modes/controllers/selector-controller.ts +24 -5
- package/src/modes/controllers/todo-command-controller.ts +18 -14
- package/src/modes/interactive-mode.ts +7 -3
- 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/prompt-action-autocomplete.ts +6 -1
- package/src/modes/rpc/host-tools.ts +15 -0
- package/src/modes/rpc/rpc-mode.ts +123 -48
- package/src/modes/types.ts +1 -1
- package/src/modes/ultrathink.ts +6 -5
- package/src/modes/utils/transcript-render-helpers.ts +54 -0
- package/src/modes/utils/ui-helpers.ts +27 -1
- package/src/modes/workflow.ts +6 -5
- package/src/prompts/advisor/system.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/grep.md +1 -2
- package/src/prompts/tools/read.md +2 -4
- package/src/sdk.ts +61 -34
- package/src/session/agent-session.ts +283 -39
- package/src/session/exit-diagnostics.ts +108 -0
- package/src/session/messages.test.ts +66 -0
- package/src/session/messages.ts +37 -0
- package/src/session/streaming-output.ts +40 -12
- package/src/slash-commands/helpers/active-oauth-account.ts +22 -2
- package/src/slash-commands/helpers/logout.ts +23 -3
- package/src/slash-commands/helpers/usage-report.ts +14 -2
- package/src/subprocess/worker-client.ts +9 -2
- package/src/system-prompt.test.ts +36 -0
- package/src/system-prompt.ts +1 -1
- package/src/task/executor.ts +8 -0
- package/src/task/render.test.ts +36 -0
- package/src/task/render.ts +55 -43
- package/src/tools/bash-skill-urls.ts +4 -1
- package/src/tools/bash.ts +1 -0
- package/src/tools/browser/registry.ts +17 -3
- package/src/tools/eval.ts +14 -9
- package/src/tools/gh.ts +3 -1
- package/src/tools/grep.ts +5 -45
- package/src/tools/path-utils.ts +7 -1
- package/src/tools/read.ts +23 -74
- package/src/tools/write.ts +82 -9
- package/src/tools/yield.ts +29 -1
- package/src/utils/title-generator.ts +10 -6
- package/src/web/search/index.ts +39 -22
- package/src/web/search/provider.ts +33 -16
- package/src/web/search/providers/codex.ts +68 -21
- package/src/web/search/providers/perplexity-auth.ts +20 -11
- package/src/web/search/providers/perplexity.ts +14 -2
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Container,
|
|
4
4
|
type NativeScrollbackCommittedRows,
|
|
5
5
|
type NativeScrollbackLiveRegion,
|
|
6
|
+
type NativeScrollbackReplay,
|
|
6
7
|
type RenderStablePrefix,
|
|
7
8
|
type ViewportTailProvider,
|
|
8
9
|
} from "@oh-my-pi/pi-tui";
|
|
@@ -119,6 +120,8 @@ interface BlockSegment {
|
|
|
119
120
|
sep: number;
|
|
120
121
|
/** Whether the block reported finalized when this segment was rendered. */
|
|
121
122
|
finalized: boolean;
|
|
123
|
+
/** Safe to drop after commit: produced while finalized, without post-finalize version tracking. */
|
|
124
|
+
compactable: boolean;
|
|
122
125
|
/** Block version observed when this segment was rendered (see {@link FinalizableBlock}). */
|
|
123
126
|
version: number | undefined;
|
|
124
127
|
}
|
|
@@ -154,7 +157,12 @@ const EMPTY_TAIL: readonly string[] = [];
|
|
|
154
157
|
*/
|
|
155
158
|
export class TranscriptContainer
|
|
156
159
|
extends Container
|
|
157
|
-
implements
|
|
160
|
+
implements
|
|
161
|
+
NativeScrollbackLiveRegion,
|
|
162
|
+
NativeScrollbackCommittedRows,
|
|
163
|
+
NativeScrollbackReplay,
|
|
164
|
+
RenderStablePrefix,
|
|
165
|
+
ViewportTailProvider
|
|
158
166
|
{
|
|
159
167
|
// Bumped to retire every block segment at once (theme change / clear); a
|
|
160
168
|
// segment is only reused when its stored generation matches.
|
|
@@ -172,6 +180,14 @@ export class TranscriptContainer
|
|
|
172
180
|
// Finalized blocks wholly before this boundary are immutable on-screen history;
|
|
173
181
|
// their previous contribution can be replayed without calling render().
|
|
174
182
|
#committedRows = 0;
|
|
183
|
+
// Leading children whose rows were handed to native scrollback and dropped
|
|
184
|
+
// from the local frame. Children remain owned by the session and can be
|
|
185
|
+
// re-rendered when the TUI prepares a destructive full replay.
|
|
186
|
+
#compactedChildStart = 0;
|
|
187
|
+
// Suppresses re-compaction for the rehydrating render. The TUI feeds its old
|
|
188
|
+
// committed-row count immediately before render, so resetting that count
|
|
189
|
+
// alone cannot distinguish a replay from an ordinary update.
|
|
190
|
+
#replayPending = false;
|
|
175
191
|
// Stable-prefix floor accumulated across renders since the last
|
|
176
192
|
// getRenderStablePrefixRows() read (see RenderStablePrefix: reading
|
|
177
193
|
// consumes the report and re-bases the baseline). Out-of-band renders
|
|
@@ -187,12 +203,24 @@ export class TranscriptContainer
|
|
|
187
203
|
override clear(): void {
|
|
188
204
|
this.#generation++;
|
|
189
205
|
super.clear();
|
|
206
|
+
this.#compactedChildStart = 0;
|
|
207
|
+
this.#committedRows = 0;
|
|
208
|
+
this.#replayPending = false;
|
|
190
209
|
}
|
|
191
210
|
|
|
192
211
|
setNativeScrollbackCommittedRows(rows: number): void {
|
|
193
212
|
this.#committedRows = Number.isFinite(rows) ? Math.max(0, Math.trunc(rows)) : 0;
|
|
194
213
|
}
|
|
195
214
|
|
|
215
|
+
prepareNativeScrollbackReplay(): void {
|
|
216
|
+
if (this.#compactedChildStart === 0) return;
|
|
217
|
+
this.#compactedChildStart = 0;
|
|
218
|
+
this.#replayPending = true;
|
|
219
|
+
this.#generation++;
|
|
220
|
+
this.#lines.length = 0;
|
|
221
|
+
this.#stableRowsFloor = 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
196
224
|
getRenderStablePrefixRows(): number {
|
|
197
225
|
const value = Math.min(this.#stableRowsFloor, this.#lines.length);
|
|
198
226
|
this.#stableRowsFloor = this.#lines.length;
|
|
@@ -213,8 +241,14 @@ export class TranscriptContainer
|
|
|
213
241
|
* committed rows and is safely removable.
|
|
214
242
|
*/
|
|
215
243
|
isBlockUncommitted(component: Component): boolean {
|
|
244
|
+
const index = this.children.indexOf(component);
|
|
245
|
+
// Compacted prefix is already committed native history and must not be
|
|
246
|
+
// retracted. Compacted slots may be sparse holes after a later re-render
|
|
247
|
+
// (render only fills from #compactedChildStart), so the loop below must
|
|
248
|
+
// skip undefined entries.
|
|
249
|
+
if (index >= 0 && index < this.#compactedChildStart) return false;
|
|
216
250
|
for (const segment of this.#segments) {
|
|
217
|
-
if (segment.component !== component) continue;
|
|
251
|
+
if (segment === undefined || segment.component !== component) continue;
|
|
218
252
|
return segment.rowCount === 0 || segment.startRow >= this.#committedRows;
|
|
219
253
|
}
|
|
220
254
|
return true;
|
|
@@ -268,7 +302,7 @@ export class TranscriptContainer
|
|
|
268
302
|
if (maxRows <= 0) return EMPTY_TAIL;
|
|
269
303
|
const collected: (readonly string[])[] = [];
|
|
270
304
|
let total = 0;
|
|
271
|
-
for (let i = this.children.length - 1; i >=
|
|
305
|
+
for (let i = this.children.length - 1; i >= this.#compactedChildStart && total < maxRows; i--) {
|
|
272
306
|
const contribution = stripPlainBlankEdges(this.children[i]!.render(width));
|
|
273
307
|
if (contribution.length === 0) continue;
|
|
274
308
|
// One blank separator sits between this block and the (already
|
|
@@ -292,6 +326,7 @@ export class TranscriptContainer
|
|
|
292
326
|
this.#nativeScrollbackLiveRegionStart = undefined;
|
|
293
327
|
|
|
294
328
|
const count = this.children.length;
|
|
329
|
+
if (this.#compactedChildStart > count) this.#compactedChildStart = count;
|
|
295
330
|
|
|
296
331
|
// Seal displaceable snapshots whose rows are already on the tape (per the
|
|
297
332
|
// previous frame's segments — the geometry the committed count was
|
|
@@ -301,8 +336,9 @@ export class TranscriptContainer
|
|
|
301
336
|
// frame, and every frame so a block that BECAME displaceable after its
|
|
302
337
|
// pending-preview rows committed (late result on a scrolled-off call) is
|
|
303
338
|
// caught too.
|
|
304
|
-
for (let i =
|
|
305
|
-
const previous = this.#segments[i]
|
|
339
|
+
for (let i = this.#compactedChildStart; i < count && i < this.#segments.length; i++) {
|
|
340
|
+
const previous = this.#segments[i];
|
|
341
|
+
if (previous === undefined) continue;
|
|
306
342
|
if (previous.startRow >= this.#committedRows) break;
|
|
307
343
|
if (previous.rowCount === 0 || previous.component !== this.children[i]) continue;
|
|
308
344
|
sealCommittedSnapshot(previous.component);
|
|
@@ -316,7 +352,7 @@ export class TranscriptContainer
|
|
|
316
352
|
// reaches.
|
|
317
353
|
let liveStartIndex = -1;
|
|
318
354
|
let hasLiveBlock = false;
|
|
319
|
-
for (let i =
|
|
355
|
+
for (let i = this.#compactedChildStart; i < count; i++) {
|
|
320
356
|
if (!isBlockFinalized(this.children[i]!)) {
|
|
321
357
|
liveStartIndex = i;
|
|
322
358
|
hasLiveBlock = true;
|
|
@@ -348,7 +384,7 @@ export class TranscriptContainer
|
|
|
348
384
|
// Frame row cursor: rows emitted (reused or pushed) so far.
|
|
349
385
|
let row = 0;
|
|
350
386
|
let stableRows = 0;
|
|
351
|
-
for (let i =
|
|
387
|
+
for (let i = this.#compactedChildStart; i < count; i++) {
|
|
352
388
|
const child = this.children[i]!;
|
|
353
389
|
|
|
354
390
|
// This child's contribution: its current render with plain-blank
|
|
@@ -389,6 +425,7 @@ export class TranscriptContainer
|
|
|
389
425
|
previous.width === width &&
|
|
390
426
|
previous.generation === this.#generation);
|
|
391
427
|
const contribution = reusable ? previous.contribution : stripPlainBlankEdges(raw);
|
|
428
|
+
const compactable = finalized && version === undefined && previous?.finalized !== false;
|
|
392
429
|
|
|
393
430
|
// Empty (or stripped-to-nothing) children contribute nothing and never
|
|
394
431
|
// affect spacing. An empty still-live child still gates the commit
|
|
@@ -413,6 +450,7 @@ export class TranscriptContainer
|
|
|
413
450
|
rowCount: 0,
|
|
414
451
|
sep: 0,
|
|
415
452
|
finalized,
|
|
453
|
+
compactable,
|
|
416
454
|
version,
|
|
417
455
|
};
|
|
418
456
|
continue;
|
|
@@ -464,6 +502,7 @@ export class TranscriptContainer
|
|
|
464
502
|
rowCount,
|
|
465
503
|
sep,
|
|
466
504
|
finalized,
|
|
505
|
+
compactable,
|
|
467
506
|
version,
|
|
468
507
|
};
|
|
469
508
|
row += rowCount;
|
|
@@ -473,8 +512,72 @@ export class TranscriptContainer
|
|
|
473
512
|
if (lines.length !== row) lines.length = row;
|
|
474
513
|
this.#segments = segments;
|
|
475
514
|
this.#stableRowsFloor = Math.min(stableFloorBefore, stableRows, row);
|
|
515
|
+
if (this.#replayPending) {
|
|
516
|
+
this.#replayPending = false;
|
|
517
|
+
} else {
|
|
518
|
+
this.#compactCommittedPrefix();
|
|
519
|
+
}
|
|
476
520
|
return lines;
|
|
477
521
|
}
|
|
522
|
+
|
|
523
|
+
#compactCommittedPrefix(): void {
|
|
524
|
+
if (this.#committedRows <= 0 || this.#compactedChildStart >= this.children.length) return;
|
|
525
|
+
const lines = this.#lines;
|
|
526
|
+
const segments = this.#segments;
|
|
527
|
+
let dropRows = 0;
|
|
528
|
+
let dropUntil = this.#compactedChildStart;
|
|
529
|
+
for (let i = this.#compactedChildStart; i < segments.length; i++) {
|
|
530
|
+
const segment = segments[i];
|
|
531
|
+
if (segment === undefined || !segment.compactable) break;
|
|
532
|
+
const segmentEnd = segment.startRow + segment.rowCount;
|
|
533
|
+
if (segmentEnd > this.#committedRows) break;
|
|
534
|
+
dropRows = segmentEnd;
|
|
535
|
+
dropUntil = i + 1;
|
|
536
|
+
}
|
|
537
|
+
const retained = segments[dropUntil];
|
|
538
|
+
if (retained !== undefined && retained.sep > 0) {
|
|
539
|
+
const committedSeparatorRows = this.#committedRows - retained.startRow;
|
|
540
|
+
if (committedSeparatorRows <= 0) {
|
|
541
|
+
dropRows = 0;
|
|
542
|
+
dropUntil = this.#compactedChildStart;
|
|
543
|
+
} else {
|
|
544
|
+
const trim = Math.min(retained.sep, committedSeparatorRows);
|
|
545
|
+
dropRows += trim;
|
|
546
|
+
retained.sep -= trim;
|
|
547
|
+
retained.rowCount -= trim;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (dropRows === 0) return;
|
|
551
|
+
|
|
552
|
+
lines.splice(0, dropRows);
|
|
553
|
+
for (let i = this.#compactedChildStart; i < dropUntil; i++) {
|
|
554
|
+
const segment = segments[i];
|
|
555
|
+
if (segment === undefined) continue;
|
|
556
|
+
segments[i] = {
|
|
557
|
+
component: segment.component,
|
|
558
|
+
rawRef: EMPTY_TAIL,
|
|
559
|
+
contribution: EMPTY_TAIL,
|
|
560
|
+
width: segment.width,
|
|
561
|
+
generation: segment.generation,
|
|
562
|
+
startRow: 0,
|
|
563
|
+
rowCount: 0,
|
|
564
|
+
sep: 0,
|
|
565
|
+
finalized: true,
|
|
566
|
+
compactable: false,
|
|
567
|
+
version: segment.version,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
for (let i = dropUntil; i < segments.length; i++) {
|
|
571
|
+
const segment = segments[i];
|
|
572
|
+
if (segment !== undefined) segment.startRow -= dropRows;
|
|
573
|
+
}
|
|
574
|
+
this.#compactedChildStart = dropUntil;
|
|
575
|
+
this.#committedRows = Math.max(0, this.#committedRows - dropRows);
|
|
576
|
+
this.#stableRowsFloor = 0;
|
|
577
|
+
if (this.#nativeScrollbackLiveRegionStart !== undefined) {
|
|
578
|
+
this.#nativeScrollbackLiveRegionStart = Math.max(0, this.#nativeScrollbackLiveRegionStart - dropRows);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
478
581
|
}
|
|
479
582
|
|
|
480
583
|
/**
|
|
@@ -1388,14 +1388,22 @@ function formatWindowSuffix(label: string, windowLabel: string, uiTheme: typeof
|
|
|
1388
1388
|
return uiTheme.fg("dim", `(${windowLabel})`);
|
|
1389
1389
|
}
|
|
1390
1390
|
|
|
1391
|
+
/** ` (org)` suffix when the report is org-attributed — two subscriptions can share one email. */
|
|
1392
|
+
function orgSuffix(report: UsageReport): string {
|
|
1393
|
+
const orgName = report.metadata?.orgName;
|
|
1394
|
+
const orgId = report.metadata?.orgId;
|
|
1395
|
+
const org = typeof orgName === "string" && orgName ? orgName : typeof orgId === "string" ? orgId : undefined;
|
|
1396
|
+
return org ? ` (${org})` : "";
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1391
1399
|
function formatAccountLabel(limit: UsageLimit, report: UsageReport, index: number): string {
|
|
1392
1400
|
const email = report.metadata?.email;
|
|
1393
|
-
if (typeof email === "string" && email) return email
|
|
1401
|
+
if (typeof email === "string" && email) return `${email}${orgSuffix(report)}`;
|
|
1394
1402
|
const accountId =
|
|
1395
1403
|
typeof report.metadata?.accountId === "string" && report.metadata.accountId
|
|
1396
1404
|
? report.metadata.accountId
|
|
1397
1405
|
: limit.scope.accountId || undefined;
|
|
1398
|
-
if (accountId) return accountId
|
|
1406
|
+
if (accountId) return `${accountId}${orgSuffix(report)}`;
|
|
1399
1407
|
const projectId =
|
|
1400
1408
|
typeof report.metadata?.projectId === "string" && report.metadata.projectId
|
|
1401
1409
|
? report.metadata.projectId
|
|
@@ -1406,9 +1414,9 @@ function formatAccountLabel(limit: UsageLimit, report: UsageReport, index: numbe
|
|
|
1406
1414
|
|
|
1407
1415
|
function formatUnlimitedReportLabel(report: UsageReport, index: number): string {
|
|
1408
1416
|
const email = report.metadata?.email;
|
|
1409
|
-
if (typeof email === "string" && email) return email
|
|
1417
|
+
if (typeof email === "string" && email) return `${email}${orgSuffix(report)}`;
|
|
1410
1418
|
const accountId = report.metadata?.accountId;
|
|
1411
|
-
if (typeof accountId === "string" && accountId) return accountId
|
|
1419
|
+
if (typeof accountId === "string" && accountId) return `${accountId}${orgSuffix(report)}`;
|
|
1412
1420
|
const projectId = report.metadata?.projectId;
|
|
1413
1421
|
if (typeof projectId === "string" && projectId) return projectId;
|
|
1414
1422
|
return `account ${index + 1}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
1
|
+
import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
3
3
|
import { getStreamingPartialJson } from "@oh-my-pi/pi-ai/utils/block-symbols";
|
|
4
4
|
import { type Component, Loader, TERMINAL } from "@oh-my-pi/pi-tui";
|
|
@@ -32,7 +32,11 @@ import { vocalizer } from "../../tts/vocalizer";
|
|
|
32
32
|
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
33
33
|
import { interruptHint } from "../shared";
|
|
34
34
|
import { createAssistantMessageComponent } from "../utils/interactive-context-helpers";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
assistantHasVisibleContent,
|
|
37
|
+
assistantUsageIsBilled,
|
|
38
|
+
splitAssistantMessageToolTimeline,
|
|
39
|
+
} from "../utils/transcript-render-helpers";
|
|
36
40
|
import { StreamingRevealController } from "./streaming-reveal";
|
|
37
41
|
import { streamingStringKeysForTool, ToolArgsRevealController } from "./tool-args-reveal";
|
|
38
42
|
|
|
@@ -79,6 +83,8 @@ export class EventController {
|
|
|
79
83
|
#backgroundTaskCallIds = new Set<string>();
|
|
80
84
|
#readToolCallArgs = new Map<string, Record<string, unknown>>();
|
|
81
85
|
#readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
86
|
+
#toolTimelineComponents = new Map<string, Component>();
|
|
87
|
+
#postToolAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
82
88
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
83
89
|
// Assistant component whose turn-ending error is currently mirrored in the
|
|
84
90
|
// pinned banner. Its inline `Error: …` line is suppressed while pinned and
|
|
@@ -254,6 +260,39 @@ export class EventController {
|
|
|
254
260
|
assistantComponent.setToolResultImages(toolCallId, images);
|
|
255
261
|
return true;
|
|
256
262
|
}
|
|
263
|
+
|
|
264
|
+
#insertAfterTranscriptComponent(anchor: Component | undefined, component: Component): boolean {
|
|
265
|
+
const children = this.ctx.chatContainer.children;
|
|
266
|
+
const anchorIndex = anchor ? children.indexOf(anchor) : -1;
|
|
267
|
+
if (anchorIndex < 0) return false;
|
|
268
|
+
if (children.slice(anchorIndex + 1).some(child => !this.ctx.chatContainer.isBlockUncommitted(child))) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
this.ctx.chatContainer.addChild(component);
|
|
272
|
+
children.splice(children.length - 1, 1);
|
|
273
|
+
children.splice(anchorIndex + 1, 0, component);
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#upsertPostToolAssistantSegment(
|
|
278
|
+
toolCallId: string,
|
|
279
|
+
segment: AssistantMessage | undefined,
|
|
280
|
+
): AssistantMessageComponent | undefined {
|
|
281
|
+
if (!segment || !assistantHasVisibleContent(segment)) return undefined;
|
|
282
|
+
const existing = this.#postToolAssistantComponents.get(toolCallId);
|
|
283
|
+
if (existing) {
|
|
284
|
+
existing.updateContent(segment);
|
|
285
|
+
return existing;
|
|
286
|
+
}
|
|
287
|
+
const component = createAssistantMessageComponent(this.ctx);
|
|
288
|
+
component.updateContent(segment);
|
|
289
|
+
this.#postToolAssistantComponents.set(toolCallId, component);
|
|
290
|
+
if (!this.#insertAfterTranscriptComponent(this.#toolTimelineComponents.get(toolCallId), component)) {
|
|
291
|
+
this.ctx.chatContainer.addChild(component);
|
|
292
|
+
}
|
|
293
|
+
return component;
|
|
294
|
+
}
|
|
295
|
+
|
|
257
296
|
#updateWorkingMessageFromIntent(intent: unknown): void {
|
|
258
297
|
if (this.ctx.session.isAborting) return;
|
|
259
298
|
// Streamed JSON can deliver non-string `i` (object, number, boolean) before
|
|
@@ -281,6 +320,8 @@ export class EventController {
|
|
|
281
320
|
this.#lastVisibleBlockCount = 0;
|
|
282
321
|
this.#renderedCustomMessages.clear();
|
|
283
322
|
this.#lastIntent = undefined;
|
|
323
|
+
this.#toolTimelineComponents.clear();
|
|
324
|
+
this.#postToolAssistantComponents.clear();
|
|
284
325
|
this.#backgroundTaskCallIds.clear();
|
|
285
326
|
this.#readToolCallArgs.clear();
|
|
286
327
|
this.#readToolCallAssistantComponents.clear();
|
|
@@ -365,6 +406,8 @@ export class EventController {
|
|
|
365
406
|
}
|
|
366
407
|
|
|
367
408
|
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
|
|
409
|
+
this.#toolTimelineComponents.clear();
|
|
410
|
+
this.#postToolAssistantComponents.clear();
|
|
368
411
|
this.#lastIntent = undefined;
|
|
369
412
|
this.#readToolCallArgs.clear();
|
|
370
413
|
this.#readToolCallAssistantComponents.clear();
|
|
@@ -463,7 +506,10 @@ export class EventController {
|
|
|
463
506
|
this.ctx.streamingComponent = createAssistantMessageComponent(this.ctx);
|
|
464
507
|
this.ctx.streamingMessage = event.message;
|
|
465
508
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
466
|
-
this.#streamingReveal.begin(
|
|
509
|
+
this.#streamingReveal.begin(
|
|
510
|
+
this.ctx.streamingComponent,
|
|
511
|
+
splitAssistantMessageToolTimeline(this.ctx.streamingMessage).beforeTools,
|
|
512
|
+
);
|
|
467
513
|
this.ctx.ui.requestRender();
|
|
468
514
|
}
|
|
469
515
|
}
|
|
@@ -648,7 +694,8 @@ export class EventController {
|
|
|
648
694
|
this.#streamingReveal.resyncVisibility();
|
|
649
695
|
}
|
|
650
696
|
this.ctx.streamingMessage = event.message;
|
|
651
|
-
|
|
697
|
+
const timeline = splitAssistantMessageToolTimeline(this.ctx.streamingMessage);
|
|
698
|
+
this.#streamingReveal.setTarget(timeline.beforeTools);
|
|
652
699
|
|
|
653
700
|
const visibleBlockCount = this.ctx.streamingMessage.content.filter(
|
|
654
701
|
content =>
|
|
@@ -691,6 +738,7 @@ export class EventController {
|
|
|
691
738
|
const group = this.#getReadGroup();
|
|
692
739
|
group.updateArgs(content.arguments, content.id);
|
|
693
740
|
this.ctx.pendingTools.set(content.id, group);
|
|
741
|
+
this.#toolTimelineComponents.set(content.id, group);
|
|
694
742
|
}
|
|
695
743
|
continue;
|
|
696
744
|
}
|
|
@@ -737,6 +785,7 @@ export class EventController {
|
|
|
737
785
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
738
786
|
this.ctx.chatContainer.addChild(component);
|
|
739
787
|
this.ctx.pendingTools.set(content.id, component);
|
|
788
|
+
this.#toolTimelineComponents.set(content.id, component);
|
|
740
789
|
this.#toolArgsReveal.bind(content.id, component);
|
|
741
790
|
} else {
|
|
742
791
|
const component = this.ctx.pendingTools.get(content.id);
|
|
@@ -746,6 +795,9 @@ export class EventController {
|
|
|
746
795
|
}
|
|
747
796
|
}
|
|
748
797
|
}
|
|
798
|
+
for (const [toolCallId, segment] of timeline.afterToolCalls) {
|
|
799
|
+
this.#upsertPostToolAssistantSegment(toolCallId, segment);
|
|
800
|
+
}
|
|
749
801
|
|
|
750
802
|
// Update working message with intent from streamed tool arguments
|
|
751
803
|
for (const content of this.ctx.streamingMessage.content) {
|
|
@@ -810,15 +862,18 @@ export class EventController {
|
|
|
810
862
|
errorMessage = resolveAbortLabel(this.ctx.streamingMessage, this.ctx.viewSession.retryAttempt);
|
|
811
863
|
this.ctx.streamingMessage.errorMessage = errorMessage;
|
|
812
864
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
865
|
+
const displayMessage: AssistantMessage =
|
|
866
|
+
silentlyAborted || ttsrSilenced
|
|
867
|
+
? {
|
|
868
|
+
// Silence the streaming render by downgrading stopReason to "stop" for
|
|
869
|
+
// display only — does NOT mutate the persisted message's stopReason
|
|
870
|
+
// (the marker on errorMessage drives replay-side suppression).
|
|
871
|
+
...this.ctx.streamingMessage,
|
|
872
|
+
stopReason: "stop",
|
|
873
|
+
}
|
|
874
|
+
: this.ctx.streamingMessage;
|
|
875
|
+
const displayTimeline = splitAssistantMessageToolTimeline(displayMessage);
|
|
876
|
+
this.ctx.streamingComponent.updateContent(displayTimeline.beforeTools);
|
|
822
877
|
|
|
823
878
|
if (this.ctx.streamingMessage.stopReason !== "aborted" && this.ctx.streamingMessage.stopReason !== "error") {
|
|
824
879
|
for (const [toolCallId, component] of this.ctx.pendingTools.entries()) {
|
|
@@ -848,8 +903,14 @@ export class EventController {
|
|
|
848
903
|
}
|
|
849
904
|
this.ctx.lastAssistantUsage = usage;
|
|
850
905
|
}
|
|
851
|
-
this
|
|
852
|
-
|
|
906
|
+
this.ctx.streamingComponent.markTranscriptBlockFinalized();
|
|
907
|
+
let lastPostToolAssistantComponent: AssistantMessageComponent | undefined;
|
|
908
|
+
for (const [toolCallId, segment] of displayTimeline.afterToolCalls) {
|
|
909
|
+
const component = this.#upsertPostToolAssistantSegment(toolCallId, segment);
|
|
910
|
+
component?.markTranscriptBlockFinalized();
|
|
911
|
+
if (component) lastPostToolAssistantComponent = component;
|
|
912
|
+
}
|
|
913
|
+
this.#lastAssistantComponent = lastPostToolAssistantComponent ?? this.ctx.streamingComponent;
|
|
853
914
|
if (settings.get("display.showTokenUsage") && assistantUsageIsBilled(event.message.usage)) {
|
|
854
915
|
this.ctx.chatContainer.addChild(
|
|
855
916
|
createUsageRowBlock(event.message.usage, event.message.duration, event.message.ttft),
|
|
@@ -886,6 +947,7 @@ export class EventController {
|
|
|
886
947
|
const group = this.#getReadGroup();
|
|
887
948
|
group.updateArgs(event.args, event.toolCallId);
|
|
888
949
|
this.ctx.pendingTools.set(event.toolCallId, group);
|
|
950
|
+
this.#toolTimelineComponents.set(event.toolCallId, group);
|
|
889
951
|
}
|
|
890
952
|
this.ctx.ui.requestRender();
|
|
891
953
|
return;
|
|
@@ -911,6 +973,7 @@ export class EventController {
|
|
|
911
973
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
912
974
|
this.ctx.chatContainer.addChild(component);
|
|
913
975
|
this.ctx.pendingTools.set(event.toolCallId, component);
|
|
976
|
+
this.#toolTimelineComponents.set(event.toolCallId, component);
|
|
914
977
|
this.ctx.ui.requestRender();
|
|
915
978
|
} else {
|
|
916
979
|
// The tool is about to run, so its arguments are final and validated.
|
|
@@ -1104,6 +1167,8 @@ export class EventController {
|
|
|
1104
1167
|
);
|
|
1105
1168
|
this.#readToolCallArgs.clear();
|
|
1106
1169
|
this.#readToolCallAssistantComponents.clear();
|
|
1170
|
+
this.#toolTimelineComponents.clear();
|
|
1171
|
+
this.#postToolAssistantComponents.clear();
|
|
1107
1172
|
this.#resetReadGroup();
|
|
1108
1173
|
// The turn is over: nothing else lands this turn, so the waiting poll is
|
|
1109
1174
|
// final history — seal it instead of letting its spinner tick while idle.
|
|
@@ -488,14 +488,16 @@ export class InputController {
|
|
|
488
488
|
// main session, or returns the focused subagent view to the main session.
|
|
489
489
|
// Focused ←← intentionally matches Esc. From the main session the gesture
|
|
490
490
|
// stays inert when there are no subagents (requireContent); the explicit
|
|
491
|
-
// hub key still opens the empty roster.
|
|
491
|
+
// hub key still opens the empty roster. `armCloseTap` hands this gesture's
|
|
492
|
+
// tap state to the hub so the same ←← that opened it also arms its close —
|
|
493
|
+
// otherwise the hub's fresh detector demands a second ←← (issue #4780).
|
|
492
494
|
this.ctx.editor.onLeftAtStart = () => {
|
|
493
495
|
if (this.ctx.focusedAgentId) {
|
|
494
496
|
this.#handleFocusedLeftTap();
|
|
495
497
|
return;
|
|
496
498
|
}
|
|
497
499
|
if (this.#detectLeftDoubleTap()) {
|
|
498
|
-
this.ctx.showAgentHub({ requireContent: true });
|
|
500
|
+
this.ctx.showAgentHub({ requireContent: true, armCloseTap: true });
|
|
499
501
|
}
|
|
500
502
|
};
|
|
501
503
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
fetchResourceMetadataScopes,
|
|
15
15
|
loadAllMCPConfigs,
|
|
16
16
|
MCPManager,
|
|
17
|
+
type OAuthEndpoints,
|
|
17
18
|
} from "../../mcp";
|
|
18
19
|
import { connectToServer, disconnectServer, listTools } from "../../mcp/client";
|
|
19
20
|
import {
|
|
@@ -603,6 +604,7 @@ export class MCPCommandController {
|
|
|
603
604
|
callbackPath: finalConfig.oauth?.callbackPath,
|
|
604
605
|
redirectUri: finalConfig.oauth?.redirectUri,
|
|
605
606
|
prompt: finalConfig.oauth?.prompt,
|
|
607
|
+
registrationUrl: oauth.registrationUrl,
|
|
606
608
|
serverUrl: finalConfig.url,
|
|
607
609
|
resource: oauthResource,
|
|
608
610
|
stripSameOriginResource: oauthResourceIsFallback,
|
|
@@ -684,6 +686,7 @@ export class MCPCommandController {
|
|
|
684
686
|
redirectUri?: string;
|
|
685
687
|
prompt?: string;
|
|
686
688
|
serverUrl?: string;
|
|
689
|
+
registrationUrl?: string;
|
|
687
690
|
resource?: string;
|
|
688
691
|
stripSameOriginResource?: boolean;
|
|
689
692
|
/**
|
|
@@ -747,6 +750,7 @@ export class MCPCommandController {
|
|
|
747
750
|
{
|
|
748
751
|
authorizationUrl: authUrl,
|
|
749
752
|
tokenUrl: tokenUrl,
|
|
753
|
+
registrationUrl: opts?.registrationUrl,
|
|
750
754
|
clientId: resolvedClientId,
|
|
751
755
|
clientSecret: resolvedClientSecret,
|
|
752
756
|
scopes: scopes || undefined,
|
|
@@ -1038,13 +1042,7 @@ export class MCPCommandController {
|
|
|
1038
1042
|
return next;
|
|
1039
1043
|
}
|
|
1040
1044
|
|
|
1041
|
-
async #resolveOAuthEndpointsFromServer(config: MCPServerConfig): Promise<{
|
|
1042
|
-
authorizationUrl: string;
|
|
1043
|
-
tokenUrl: string;
|
|
1044
|
-
clientId?: string;
|
|
1045
|
-
scopes?: string;
|
|
1046
|
-
resource?: string;
|
|
1047
|
-
}> {
|
|
1045
|
+
async #resolveOAuthEndpointsFromServer(config: MCPServerConfig): Promise<OAuthEndpoints> {
|
|
1048
1046
|
// Stdio servers manage credentials inside the child process; OMP's OAuth
|
|
1049
1047
|
// flow only applies to http/sse transports. Without this guard the
|
|
1050
1048
|
// unauthenticated preflight below spawns the child, which happily reuses
|
|
@@ -1739,6 +1737,7 @@ export class MCPCommandController {
|
|
|
1739
1737
|
callbackPath: found.config.oauth?.callbackPath,
|
|
1740
1738
|
redirectUri: found.config.oauth?.redirectUri,
|
|
1741
1739
|
prompt: found.config.oauth?.prompt,
|
|
1740
|
+
registrationUrl: oauth.registrationUrl,
|
|
1742
1741
|
serverUrl,
|
|
1743
1742
|
resource: oauthResource,
|
|
1744
1743
|
stripSameOriginResource: oauthResourceIsFallback,
|
|
@@ -634,7 +634,8 @@ export class SelectorController {
|
|
|
634
634
|
onPick: async (model, selector) => {
|
|
635
635
|
try {
|
|
636
636
|
// Session-only: update agent state but don't persist the model to settings.
|
|
637
|
-
|
|
637
|
+
const roleThinkingLevel = this.ctx.session.resolveTemporaryModelThinkingLevel(model);
|
|
638
|
+
await this.ctx.session.setModelTemporary(model, roleThinkingLevel);
|
|
638
639
|
this.ctx.statusLine.invalidate();
|
|
639
640
|
this.ctx.updateEditorBorderColor();
|
|
640
641
|
const roleSelectorHint = this.ctx.keybindings.getKeys("app.model.select")[0] ?? "Alt+M";
|
|
@@ -718,7 +719,7 @@ export class SelectorController {
|
|
|
718
719
|
if (role === "default") {
|
|
719
720
|
const { switched } = await this.ctx.session.setModel(model, role, {
|
|
720
721
|
selector,
|
|
721
|
-
thinkingLevel: concreteThinking,
|
|
722
|
+
thinkingLevel: isAuto ? ThinkingLevel.Inherit : concreteThinking,
|
|
722
723
|
persist: true,
|
|
723
724
|
currentContextTokens,
|
|
724
725
|
});
|
|
@@ -777,6 +778,7 @@ export class SelectorController {
|
|
|
777
778
|
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
778
779
|
}
|
|
779
780
|
},
|
|
781
|
+
|
|
780
782
|
onLoginRequest: providerId => {
|
|
781
783
|
done();
|
|
782
784
|
void this.#loginThenReopenModelHub(providerId);
|
|
@@ -1291,7 +1293,7 @@ export class SelectorController {
|
|
|
1291
1293
|
this.ctx.ui.setFocus(dialog);
|
|
1292
1294
|
this.ctx.ui.requestRender();
|
|
1293
1295
|
try {
|
|
1294
|
-
await this.ctx.session.modelRegistry.authStorage.login(providerId as OAuthProvider, {
|
|
1296
|
+
const identity = await this.ctx.session.modelRegistry.authStorage.login(providerId as OAuthProvider, {
|
|
1295
1297
|
signal: dialog.signal,
|
|
1296
1298
|
onAuth: (info: { url: string; launchUrl?: string; instructions?: string }) => {
|
|
1297
1299
|
// The dialog renders the full URL (SSH-safe copy target) and
|
|
@@ -1310,8 +1312,18 @@ export class SelectorController {
|
|
|
1310
1312
|
});
|
|
1311
1313
|
this.ctx.session.modelRegistry.refreshInBackground();
|
|
1312
1314
|
const block = new TranscriptBlock();
|
|
1315
|
+
// Name the account (and Anthropic organization) that was stored so a
|
|
1316
|
+
// login that lands on an unintended account/subscription is visible
|
|
1317
|
+
// immediately instead of silently replacing an existing registration.
|
|
1318
|
+
const whoBase = identity?.type === "oauth" ? (identity.email ?? identity.accountId) : undefined;
|
|
1319
|
+
const whoOrg = identity?.type === "oauth" ? (identity.orgName ?? identity.orgId) : undefined;
|
|
1320
|
+
const who = whoBase ? ` as ${whoBase}${whoOrg ? ` (${whoOrg})` : ""}` : whoOrg ? ` as ${whoOrg}` : "";
|
|
1313
1321
|
block.addChild(
|
|
1314
|
-
new Text(
|
|
1322
|
+
new Text(
|
|
1323
|
+
theme.fg("success", `${theme.status.success} Successfully logged in to ${providerId}${who}`),
|
|
1324
|
+
1,
|
|
1325
|
+
0,
|
|
1326
|
+
),
|
|
1315
1327
|
);
|
|
1316
1328
|
block.addChild(new Text(theme.fg("dim", `Credentials saved to ${getAgentDbPath()}`), 1, 0));
|
|
1317
1329
|
this.ctx.present(block);
|
|
@@ -1532,7 +1544,10 @@ export class SelectorController {
|
|
|
1532
1544
|
});
|
|
1533
1545
|
}
|
|
1534
1546
|
|
|
1535
|
-
showAgentHub(
|
|
1547
|
+
showAgentHub(
|
|
1548
|
+
observers: SessionObserverRegistry,
|
|
1549
|
+
options?: { requireContent?: boolean; armCloseTap?: boolean },
|
|
1550
|
+
): void {
|
|
1536
1551
|
const hubKeys = [
|
|
1537
1552
|
...this.ctx.keybindings.getKeys("app.agents.hub"),
|
|
1538
1553
|
...this.ctx.keybindings.getKeys("app.session.observe"),
|
|
@@ -1587,6 +1602,10 @@ export class SelectorController {
|
|
|
1587
1602
|
this.ctx.editorContainer.clear();
|
|
1588
1603
|
this.ctx.editorContainer.addChild(hub);
|
|
1589
1604
|
this.ctx.ui.setFocus(hub);
|
|
1605
|
+
// When the hub was raised by the editor's double-← gesture, prime its own
|
|
1606
|
+
// close detector so the *next* single ← dismisses it — the two taps that
|
|
1607
|
+
// opened it were consumed by the editor's detector (issue #4780).
|
|
1608
|
+
if (options?.armCloseTap) hub.armCloseTap();
|
|
1590
1609
|
this.ctx.ui.requestRender();
|
|
1591
1610
|
};
|
|
1592
1611
|
|