@oh-my-pi/pi-coding-agent 15.10.12 → 15.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -4
- package/dist/cli.js +869 -825
- package/dist/types/async/index.d.ts +0 -1
- package/dist/types/capability/mcp.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +66 -34
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/history-protocol.d.ts +14 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/irc/bus.d.ts +66 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +6 -1
- package/dist/types/mcp/transports/stdio.d.ts +1 -0
- package/dist/types/mcp/types.d.ts +2 -0
- package/dist/types/modes/components/agent-hub.d.ts +30 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +3 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
- package/dist/types/modes/components/welcome.d.ts +3 -9
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/tool-args-reveal.d.ts +43 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -2
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +3 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +51 -0
- package/dist/types/registry/agent-registry.d.ts +16 -5
- package/dist/types/session/agent-session.d.ts +35 -30
- package/dist/types/session/messages.d.ts +2 -4
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +21 -3
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/index.d.ts +11 -4
- package/dist/types/task/output-manager.d.ts +0 -7
- package/dist/types/task/repair-args.d.ts +8 -7
- package/dist/types/task/types.d.ts +55 -51
- package/dist/types/tools/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/find.d.ts +0 -11
- package/dist/types/tools/grouped-file-output.d.ts +0 -49
- package/dist/types/tools/index.d.ts +1 -3
- package/dist/types/tools/irc.d.ts +76 -38
- package/dist/types/tools/job.d.ts +7 -1
- package/dist/types/tools/render-utils.d.ts +22 -0
- package/examples/extensions/with-deps/package.json +1 -0
- package/package.json +11 -10
- package/scripts/bundle-dist.ts +28 -19
- package/src/async/index.ts +0 -1
- package/src/capability/mcp.ts +1 -0
- package/src/cli/gallery-cli.ts +6 -5
- package/src/cli/gallery-fixtures/agentic.ts +230 -115
- package/src/cli/gallery-fixtures/types.ts +5 -0
- package/src/cli.ts +20 -6
- package/src/commit/agentic/tools/analyze-file.ts +38 -19
- package/src/config/keybindings.ts +6 -1
- package/src/config/mcp-schema.json +4 -0
- package/src/config/settings-schema.ts +68 -41
- package/src/config/settings.ts +7 -0
- package/src/edit/renderer.ts +96 -46
- package/src/eval/__tests__/agent-bridge.test.ts +5 -3
- package/src/eval/agent-bridge.ts +3 -16
- package/src/eval/js/shared/prelude.txt +1 -1
- package/src/eval/py/prelude.py +5 -6
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +44 -14
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +9 -9
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/router.ts +3 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/irc/bus.ts +292 -0
- package/src/main.ts +8 -60
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/oauth-discovery.ts +27 -2
- package/src/mcp/oauth-flow.ts +47 -1
- package/src/mcp/transports/stdio.ts +3 -0
- package/src/mcp/types.ts +2 -0
- package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
- package/src/modes/components/assistant-message.ts +15 -0
- package/src/modes/components/btw-panel.ts +5 -1
- package/src/modes/components/compaction-summary-message.ts +68 -32
- package/src/modes/components/custom-editor.ts +10 -0
- package/src/modes/components/mcp-add-wizard.ts +13 -0
- package/src/modes/components/settings-selector.ts +2 -0
- package/src/modes/components/status-line/component.ts +22 -12
- package/src/modes/components/status-line/types.ts +3 -0
- package/src/modes/components/tool-execution.ts +31 -1
- package/src/modes/components/transcript-container.ts +99 -18
- package/src/modes/components/tree-selector.ts +6 -1
- package/src/modes/components/ttsr-notification.ts +72 -30
- package/src/modes/components/welcome.ts +9 -33
- package/src/modes/controllers/event-controller.ts +93 -4
- package/src/modes/controllers/extension-ui-controller.ts +8 -8
- package/src/modes/controllers/input-controller.ts +18 -2
- package/src/modes/controllers/mcp-command-controller.ts +34 -2
- package/src/modes/controllers/selector-controller.ts +25 -17
- package/src/modes/controllers/tool-args-reveal.ts +174 -0
- package/src/modes/interactive-mode.ts +17 -15
- package/src/modes/theme/theme.ts +24 -5
- package/src/modes/types.ts +3 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +51 -49
- package/src/prompts/system/irc-incoming.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +0 -5
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/irc.md +29 -19
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/task-summary.md +5 -16
- package/src/prompts/tools/task.md +43 -29
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +16 -5
- package/src/sdk.ts +29 -9
- package/src/session/agent-session.ts +268 -241
- package/src/session/messages.ts +11 -78
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-manager.ts +59 -5
- package/src/session/streaming-output.ts +60 -0
- package/src/task/executor.ts +855 -466
- package/src/task/index.ts +723 -794
- package/src/task/output-manager.ts +0 -11
- package/src/task/render.ts +142 -66
- package/src/task/repair-args.ts +21 -9
- package/src/task/types.ts +73 -66
- package/src/tools/ask.ts +4 -2
- package/src/tools/bash.ts +15 -5
- package/src/tools/browser/tab-worker.ts +26 -7
- package/src/tools/browser.ts +28 -1
- package/src/tools/find.ts +2 -27
- package/src/tools/grouped-file-output.ts +1 -118
- package/src/tools/index.ts +4 -12
- package/src/tools/irc.ts +596 -171
- package/src/tools/job.ts +41 -7
- package/src/tools/read.ts +57 -1
- package/src/tools/render-utils.ts +56 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +4 -1
- package/src/tools/write.ts +65 -47
- package/src/web/search/providers/anthropic.ts +29 -4
- package/dist/types/async/support.d.ts +0 -2
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/task/simple-mode.d.ts +0 -8
- package/src/async/support.ts +0 -5
- package/src/task/simple-mode.ts +0 -27
|
@@ -21,6 +21,7 @@ import { isSilentAbort, readPendingDisplayTag, resolveAbortLabel } from "../../s
|
|
|
21
21
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
22
22
|
import { interruptHint } from "../shared";
|
|
23
23
|
import { StreamingRevealController } from "./streaming-reveal";
|
|
24
|
+
import { ToolArgsRevealController } from "./tool-args-reveal";
|
|
24
25
|
|
|
25
26
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
26
27
|
|
|
@@ -77,7 +78,16 @@ export class EventController {
|
|
|
77
78
|
// Insertion-ordered IRC cards not yet retired; values are the transcript
|
|
78
79
|
// components each card contributed (see #retireIrcCard for the guard).
|
|
79
80
|
#liveIrcCards = new Map<string, Component[]>();
|
|
81
|
+
// Most recent `job` tool block whose result still had every watched job
|
|
82
|
+
// running. Kept un-finalized (live) so the next `job` call displaces it —
|
|
83
|
+
// one persistent poll instead of a stack of "waiting on N jobs" frames —
|
|
84
|
+
// and sealed in place the moment anything else lands below it.
|
|
85
|
+
#displaceablePollComponent: ToolExecutionComponent | undefined = undefined;
|
|
86
|
+
// Most recent TTSR notification block. A new ttsr_triggered event merges its
|
|
87
|
+
// rules into this block while it is still the (live-region) transcript tail.
|
|
88
|
+
#lastTtsrNotification: TtsrNotificationComponent | undefined = undefined;
|
|
80
89
|
#streamingReveal: StreamingRevealController;
|
|
90
|
+
#toolArgsReveal: ToolArgsRevealController;
|
|
81
91
|
#handlers: AgentSessionEventHandlers;
|
|
82
92
|
|
|
83
93
|
constructor(private ctx: InteractiveModeContext) {
|
|
@@ -86,6 +96,10 @@ export class EventController {
|
|
|
86
96
|
getHideThinkingBlock: () => this.ctx.hideThinkingBlock,
|
|
87
97
|
requestRender: () => this.ctx.ui.requestRender(),
|
|
88
98
|
});
|
|
99
|
+
this.#toolArgsReveal = new ToolArgsRevealController({
|
|
100
|
+
getSmoothStreaming: () => this.ctx.settings.get("display.smoothStreaming"),
|
|
101
|
+
requestRender: () => this.ctx.ui.requestRender(),
|
|
102
|
+
});
|
|
89
103
|
this.#handlers = {
|
|
90
104
|
agent_start: e => this.#handleAgentStart(e),
|
|
91
105
|
agent_end: e => this.#handleAgentEnd(e),
|
|
@@ -119,6 +133,7 @@ export class EventController {
|
|
|
119
133
|
|
|
120
134
|
dispose(): void {
|
|
121
135
|
this.#streamingReveal.stop();
|
|
136
|
+
this.#toolArgsReveal.stop();
|
|
122
137
|
this.#cancelIdleCompaction();
|
|
123
138
|
for (const timer of this.#ircExpiryTimers.values()) {
|
|
124
139
|
clearTimeout(timer);
|
|
@@ -282,6 +297,7 @@ export class EventController {
|
|
|
282
297
|
const signature = `${textContent}\u0000${imageCount}`;
|
|
283
298
|
|
|
284
299
|
this.#resetReadGroup();
|
|
300
|
+
this.#resolveDisplaceablePoll();
|
|
285
301
|
const wasOptimistic = this.ctx.optimisticUserMessageSignature === signature;
|
|
286
302
|
const wasLocallySubmitted = this.ctx.locallySubmittedUserSignatures.delete(signature) || wasOptimistic;
|
|
287
303
|
if (!wasOptimistic) {
|
|
@@ -389,6 +405,28 @@ export class EventController {
|
|
|
389
405
|
}
|
|
390
406
|
}
|
|
391
407
|
|
|
408
|
+
/**
|
|
409
|
+
* Resolve the pending displaceable poll block before the next block lands.
|
|
410
|
+
* A follow-up `job` call displaces it — the stale "waiting on N jobs" frame
|
|
411
|
+
* is removed so repeated polls read as one persistent poll — while anything
|
|
412
|
+
* else seals it in place as final history. Removal is safe only because a
|
|
413
|
+
* displaceable block never finalizes: commits stop at the first live block,
|
|
414
|
+
* so none of its rows have entered native scrollback (see
|
|
415
|
+
* ToolExecutionComponent.isDisplaceableBlock).
|
|
416
|
+
*/
|
|
417
|
+
#resolveDisplaceablePoll(nextToolName?: string): void {
|
|
418
|
+
const previous = this.#displaceablePollComponent;
|
|
419
|
+
if (!previous) return;
|
|
420
|
+
this.#displaceablePollComponent = undefined;
|
|
421
|
+
if (nextToolName === "job" && previous.isDisplaceableBlock()) {
|
|
422
|
+
this.ctx.chatContainer.removeChild(previous);
|
|
423
|
+
}
|
|
424
|
+
// Sealing stops the waiting-poll spinner and freezes the block (for a
|
|
425
|
+
// just-removed component it only clears the animation timer).
|
|
426
|
+
previous.seal();
|
|
427
|
+
this.ctx.ui.requestRender();
|
|
428
|
+
}
|
|
429
|
+
|
|
392
430
|
async #handleNotice(event: Extract<AgentSessionEvent, { type: "notice" }>): Promise<void> {
|
|
393
431
|
const message = event.source ? `${event.source}: ${event.message}` : event.message;
|
|
394
432
|
if (event.level === "error") {
|
|
@@ -444,6 +482,7 @@ export class EventController {
|
|
|
444
482
|
continue;
|
|
445
483
|
}
|
|
446
484
|
if (!readArgsTargetInternalUrl(content.arguments)) {
|
|
485
|
+
if (!this.ctx.pendingTools.has(content.id)) this.#resolveDisplaceablePoll(content.name);
|
|
447
486
|
this.#trackReadToolCall(content.id, content.arguments);
|
|
448
487
|
const component = this.ctx.pendingTools.get(content.id);
|
|
449
488
|
if (component) {
|
|
@@ -460,11 +499,25 @@ export class EventController {
|
|
|
460
499
|
|
|
461
500
|
// Preserve the raw partial JSON for renderers that need to surface fields before the JSON object closes.
|
|
462
501
|
// Bash uses this to show inline env assignments during streaming instead of popping them in at completion.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
502
|
+
// While the JSON is still open, ToolArgsRevealController paces the
|
|
503
|
+
// reveal (write/edit/bash previews grow smoothly when a slow provider
|
|
504
|
+
// delivers large batches); once it closes, the final args render
|
|
505
|
+
// as-is — mirroring how assistant text snaps at message_end.
|
|
506
|
+
const partialJson = "partialJson" in content ? content.partialJson : undefined;
|
|
507
|
+
let renderArgs: Record<string, unknown>;
|
|
508
|
+
if (typeof partialJson === "string") {
|
|
509
|
+
renderArgs = this.#toolArgsReveal.setTarget(
|
|
510
|
+
content.id,
|
|
511
|
+
partialJson,
|
|
512
|
+
content.customWireName !== undefined,
|
|
513
|
+
content.arguments,
|
|
514
|
+
);
|
|
515
|
+
} else {
|
|
516
|
+
this.#toolArgsReveal.finish(content.id);
|
|
517
|
+
renderArgs = content.arguments;
|
|
518
|
+
}
|
|
467
519
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
520
|
+
this.#resolveDisplaceablePoll(content.name);
|
|
468
521
|
this.#resetReadGroup();
|
|
469
522
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
470
523
|
const component = new ToolExecutionComponent(
|
|
@@ -484,10 +537,12 @@ export class EventController {
|
|
|
484
537
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
485
538
|
this.ctx.chatContainer.addChild(component);
|
|
486
539
|
this.ctx.pendingTools.set(content.id, component);
|
|
540
|
+
this.#toolArgsReveal.bind(content.id, component);
|
|
487
541
|
} else {
|
|
488
542
|
const component = this.ctx.pendingTools.get(content.id);
|
|
489
543
|
if (component) {
|
|
490
544
|
component.updateArgs(renderArgs, content.id);
|
|
545
|
+
this.#toolArgsReveal.bind(content.id, component);
|
|
491
546
|
}
|
|
492
547
|
}
|
|
493
548
|
}
|
|
@@ -522,6 +577,7 @@ export class EventController {
|
|
|
522
577
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
523
578
|
this.ctx.streamingMessage = event.message;
|
|
524
579
|
this.#streamingReveal.stop();
|
|
580
|
+
this.#toolArgsReveal.flushAll();
|
|
525
581
|
let errorMessage: string | undefined;
|
|
526
582
|
const aborted = this.ctx.streamingMessage.stopReason === "aborted";
|
|
527
583
|
const silentlyAborted = aborted && isSilentAbort(this.ctx.streamingMessage.errorMessage);
|
|
@@ -561,6 +617,9 @@ export class EventController {
|
|
|
561
617
|
component.seal();
|
|
562
618
|
}
|
|
563
619
|
}
|
|
620
|
+
// These calls will never produce a result either, so the tracked
|
|
621
|
+
// waiting poll cannot be displaced anymore — freeze it in place.
|
|
622
|
+
this.#resolveDisplaceablePoll();
|
|
564
623
|
}
|
|
565
624
|
this.#lastAssistantComponent = this.ctx.streamingComponent;
|
|
566
625
|
this.#lastAssistantComponent.setUsageInfo(event.message.usage);
|
|
@@ -589,6 +648,7 @@ export class EventController {
|
|
|
589
648
|
async #handleToolExecutionStart(event: Extract<AgentSessionEvent, { type: "tool_execution_start" }>): Promise<void> {
|
|
590
649
|
this.#updateWorkingMessageFromIntent(event.intent);
|
|
591
650
|
if (!this.ctx.pendingTools.has(event.toolCallId)) {
|
|
651
|
+
this.#resolveDisplaceablePoll(event.toolName);
|
|
592
652
|
if (event.toolName === "read" && readArgsHaveTarget(event.args) && !readArgsTargetInternalUrl(event.args)) {
|
|
593
653
|
this.#trackReadToolCall(event.toolCallId, event.args);
|
|
594
654
|
const component = this.ctx.pendingTools.get(event.toolCallId);
|
|
@@ -697,6 +757,14 @@ export class EventController {
|
|
|
697
757
|
this.ctx.pendingTools.delete(event.toolCallId);
|
|
698
758
|
this.#backgroundToolCallIds.delete(event.toolCallId);
|
|
699
759
|
}
|
|
760
|
+
if (
|
|
761
|
+
event.toolName === "job" &&
|
|
762
|
+
component instanceof ToolExecutionComponent &&
|
|
763
|
+
component.isDisplaceableBlock()
|
|
764
|
+
) {
|
|
765
|
+
// Remember the waiting poll so the next `job` call can displace it.
|
|
766
|
+
this.#displaceablePollComponent = component;
|
|
767
|
+
}
|
|
700
768
|
this.ctx.ui.requestRender();
|
|
701
769
|
}
|
|
702
770
|
}
|
|
@@ -727,6 +795,7 @@ export class EventController {
|
|
|
727
795
|
async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
728
796
|
this.#agentTurnActive = false;
|
|
729
797
|
this.#streamingReveal.stop();
|
|
798
|
+
this.#toolArgsReveal.flushAll();
|
|
730
799
|
if (this.ctx.loadingAnimation) {
|
|
731
800
|
this.ctx.loadingAnimation.stop();
|
|
732
801
|
this.ctx.loadingAnimation = undefined;
|
|
@@ -759,6 +828,9 @@ export class EventController {
|
|
|
759
828
|
this.#readToolCallArgs.clear();
|
|
760
829
|
this.#readToolCallAssistantComponents.clear();
|
|
761
830
|
this.#resetReadGroup();
|
|
831
|
+
// The turn is over: nothing else lands this turn, so the waiting poll is
|
|
832
|
+
// final history — seal it instead of letting its spinner tick while idle.
|
|
833
|
+
this.#resolveDisplaceablePoll();
|
|
762
834
|
this.#lastAssistantComponent = undefined;
|
|
763
835
|
this.ctx.ui.requestRender();
|
|
764
836
|
this.#scheduleIdleCompaction();
|
|
@@ -908,9 +980,26 @@ export class EventController {
|
|
|
908
980
|
}
|
|
909
981
|
|
|
910
982
|
async #handleTtsrTriggered(event: Extract<AgentSessionEvent, { type: "ttsr_triggered" }>): Promise<void> {
|
|
983
|
+
// Consecutive notifications (e.g. per-tool matches from one assistant
|
|
984
|
+
// message) merge into the previous block instead of stacking. Mutating an
|
|
985
|
+
// existing block is only safe while it sits inside the live region — a
|
|
986
|
+
// still-mutating block above it means none of its rows have been committed
|
|
987
|
+
// to native scrollback yet (commits are prefix-only and stop at the first
|
|
988
|
+
// live block), so the grown block still repaints.
|
|
989
|
+
const previous = this.#lastTtsrNotification;
|
|
990
|
+
if (
|
|
991
|
+
previous &&
|
|
992
|
+
this.ctx.chatContainer.children.at(-1) === previous &&
|
|
993
|
+
this.ctx.chatContainer.isWithinLiveRegion(previous)
|
|
994
|
+
) {
|
|
995
|
+
previous.addRules(event.rules);
|
|
996
|
+
this.ctx.ui.requestRender();
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
911
999
|
const component = new TtsrNotificationComponent(event.rules);
|
|
912
1000
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
913
1001
|
this.ctx.present(component);
|
|
1002
|
+
this.#lastTtsrNotification = component;
|
|
914
1003
|
}
|
|
915
1004
|
|
|
916
1005
|
async #handleTodoReminder(event: Extract<AgentSessionEvent, { type: "todo_reminder" }>): Promise<void> {
|
|
@@ -140,7 +140,7 @@ export class ExtensionUiController {
|
|
|
140
140
|
reload: async () => {
|
|
141
141
|
await this.ctx.session.reload();
|
|
142
142
|
this.ctx.chatContainer.clear();
|
|
143
|
-
this.ctx.renderInitialMessages(
|
|
143
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
144
144
|
await this.ctx.reloadTodos();
|
|
145
145
|
this.ctx.showStatus("Reloaded session");
|
|
146
146
|
},
|
|
@@ -197,7 +197,7 @@ export class ExtensionUiController {
|
|
|
197
197
|
|
|
198
198
|
// Update UI
|
|
199
199
|
this.ctx.chatContainer.clear();
|
|
200
|
-
this.ctx.renderInitialMessages(
|
|
200
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
201
201
|
await this.ctx.reloadTodos();
|
|
202
202
|
this.ctx.editor.setText(result.selectedText);
|
|
203
203
|
this.ctx.showStatus("Branched to new session");
|
|
@@ -212,7 +212,7 @@ export class ExtensionUiController {
|
|
|
212
212
|
|
|
213
213
|
// Update UI
|
|
214
214
|
this.ctx.chatContainer.clear();
|
|
215
|
-
this.ctx.renderInitialMessages(
|
|
215
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
216
216
|
await this.ctx.reloadTodos();
|
|
217
217
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
218
218
|
this.ctx.editor.setText(result.editorText);
|
|
@@ -230,7 +230,7 @@ export class ExtensionUiController {
|
|
|
230
230
|
}
|
|
231
231
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
232
232
|
this.ctx.chatContainer.clear();
|
|
233
|
-
this.ctx.renderInitialMessages(
|
|
233
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
234
234
|
await this.ctx.reloadTodos();
|
|
235
235
|
return { cancelled: false };
|
|
236
236
|
},
|
|
@@ -376,7 +376,7 @@ export class ExtensionUiController {
|
|
|
376
376
|
reload: async () => {
|
|
377
377
|
await this.ctx.session.reload();
|
|
378
378
|
this.ctx.chatContainer.clear();
|
|
379
|
-
this.ctx.renderInitialMessages(
|
|
379
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
380
380
|
await this.ctx.reloadTodos();
|
|
381
381
|
this.ctx.showStatus("Reloaded session");
|
|
382
382
|
},
|
|
@@ -426,7 +426,7 @@ export class ExtensionUiController {
|
|
|
426
426
|
|
|
427
427
|
// Update UI
|
|
428
428
|
this.ctx.chatContainer.clear();
|
|
429
|
-
this.ctx.renderInitialMessages(
|
|
429
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
430
430
|
await this.ctx.reloadTodos();
|
|
431
431
|
this.ctx.editor.setText(result.selectedText);
|
|
432
432
|
this.ctx.showStatus("Branched to new session");
|
|
@@ -441,7 +441,7 @@ export class ExtensionUiController {
|
|
|
441
441
|
|
|
442
442
|
// Update UI
|
|
443
443
|
this.ctx.chatContainer.clear();
|
|
444
|
-
this.ctx.renderInitialMessages(
|
|
444
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
445
445
|
await this.ctx.reloadTodos();
|
|
446
446
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
447
447
|
this.ctx.editor.setText(result.editorText);
|
|
@@ -458,7 +458,7 @@ export class ExtensionUiController {
|
|
|
458
458
|
return { cancelled: true };
|
|
459
459
|
}
|
|
460
460
|
this.ctx.chatContainer.clear();
|
|
461
|
-
this.ctx.renderInitialMessages(
|
|
461
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
462
462
|
await this.ctx.reloadTodos();
|
|
463
463
|
return { cancelled: false };
|
|
464
464
|
},
|
|
@@ -235,10 +235,26 @@ export class InputController {
|
|
|
235
235
|
for (const key of this.ctx.keybindings.getKeys("app.clipboard.copyLine")) {
|
|
236
236
|
this.ctx.editor.setCustomKeyHandler(key, () => this.handleCopyCurrentLine());
|
|
237
237
|
}
|
|
238
|
-
|
|
239
|
-
this.ctx.
|
|
238
|
+
const hubKeys = new Set([
|
|
239
|
+
...this.ctx.keybindings.getKeys("app.agents.hub"),
|
|
240
|
+
...this.ctx.keybindings.getKeys("app.session.observe"),
|
|
241
|
+
]);
|
|
242
|
+
for (const key of hubKeys) {
|
|
243
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showAgentHub());
|
|
240
244
|
}
|
|
241
245
|
|
|
246
|
+
// Double-tap left arrow on an empty editor opens the agent hub — same
|
|
247
|
+
// 500ms window as the double-escape state machine above.
|
|
248
|
+
this.ctx.editor.onLeftAtStart = () => {
|
|
249
|
+
const now = Date.now();
|
|
250
|
+
if (now - this.ctx.lastLeftTapTime < 500) {
|
|
251
|
+
this.ctx.lastLeftTapTime = 0;
|
|
252
|
+
this.ctx.showAgentHub();
|
|
253
|
+
} else {
|
|
254
|
+
this.ctx.lastLeftTapTime = now;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
242
258
|
this.#setupEnhancedPaste();
|
|
243
259
|
|
|
244
260
|
this.ctx.editor.onChange = (text: string) => {
|
|
@@ -127,6 +127,7 @@ interface OAuthFlowResult {
|
|
|
127
127
|
credentialId: string;
|
|
128
128
|
clientId?: string;
|
|
129
129
|
clientSecret?: string;
|
|
130
|
+
resource?: string;
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
type MCPAddScope = "user" | "project";
|
|
@@ -490,6 +491,7 @@ export class MCPCommandController {
|
|
|
490
491
|
|
|
491
492
|
try {
|
|
492
493
|
const oauthClientSecret = finalConfig.oauth?.clientSecret ?? "";
|
|
494
|
+
const oauthResource = oauth.resource ?? finalConfig.url;
|
|
493
495
|
const oauthResult = await this.#handleOAuthFlow(
|
|
494
496
|
oauth.authorizationUrl,
|
|
495
497
|
oauth.tokenUrl,
|
|
@@ -499,15 +501,18 @@ export class MCPCommandController {
|
|
|
499
501
|
finalConfig.oauth?.callbackPort,
|
|
500
502
|
finalConfig.oauth?.callbackPath,
|
|
501
503
|
finalConfig.oauth?.redirectUri,
|
|
504
|
+
oauthResource,
|
|
502
505
|
);
|
|
503
506
|
const persistedClientId = oauthResult.clientId ?? oauth.clientId ?? finalConfig.oauth?.clientId;
|
|
504
507
|
const persistedClientSecret = oauthResult.clientSecret ?? finalConfig.oauth?.clientSecret;
|
|
508
|
+
const persistedResource = oauthResult.resource ?? oauthResource;
|
|
505
509
|
finalConfig = {
|
|
506
510
|
...finalConfig,
|
|
507
511
|
auth: {
|
|
508
512
|
type: "oauth",
|
|
509
513
|
credentialId: oauthResult.credentialId,
|
|
510
514
|
tokenUrl: oauth.tokenUrl,
|
|
515
|
+
resource: persistedResource,
|
|
511
516
|
clientId: persistedClientId,
|
|
512
517
|
clientSecret: persistedClientSecret,
|
|
513
518
|
},
|
|
@@ -548,8 +553,25 @@ export class MCPCommandController {
|
|
|
548
553
|
done();
|
|
549
554
|
this.#handleWizardCancel();
|
|
550
555
|
},
|
|
551
|
-
async (
|
|
552
|
-
|
|
556
|
+
async (
|
|
557
|
+
authUrl: string,
|
|
558
|
+
tokenUrl: string,
|
|
559
|
+
clientId: string,
|
|
560
|
+
clientSecret: string,
|
|
561
|
+
scopes: string,
|
|
562
|
+
resource?: string,
|
|
563
|
+
) => {
|
|
564
|
+
return await this.#handleOAuthFlow(
|
|
565
|
+
authUrl,
|
|
566
|
+
tokenUrl,
|
|
567
|
+
clientId,
|
|
568
|
+
clientSecret,
|
|
569
|
+
scopes,
|
|
570
|
+
undefined,
|
|
571
|
+
undefined,
|
|
572
|
+
undefined,
|
|
573
|
+
resource,
|
|
574
|
+
);
|
|
553
575
|
},
|
|
554
576
|
async (config: MCPServerConfig) => {
|
|
555
577
|
return await this.#handleTestConnection(config);
|
|
@@ -579,6 +601,7 @@ export class MCPCommandController {
|
|
|
579
601
|
callbackPort?: number,
|
|
580
602
|
callbackPath?: string,
|
|
581
603
|
redirectUri?: string,
|
|
604
|
+
resource?: string,
|
|
582
605
|
): Promise<OAuthFlowResult> {
|
|
583
606
|
const authStorage = this.ctx.session.modelRegistry.authStorage;
|
|
584
607
|
let parsedAuthUrl: URL;
|
|
@@ -617,6 +640,7 @@ export class MCPCommandController {
|
|
|
617
640
|
redirectUri,
|
|
618
641
|
callbackPort,
|
|
619
642
|
callbackPath,
|
|
643
|
+
resource,
|
|
620
644
|
},
|
|
621
645
|
{
|
|
622
646
|
onAuth: (info: { url: string; instructions?: string }) => {
|
|
@@ -704,6 +728,7 @@ export class MCPCommandController {
|
|
|
704
728
|
credentialId,
|
|
705
729
|
clientId: flow.resolvedClientId,
|
|
706
730
|
clientSecret: flow.registeredClientSecret,
|
|
731
|
+
resource: flow.resource,
|
|
707
732
|
};
|
|
708
733
|
} catch (error) {
|
|
709
734
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -804,6 +829,7 @@ export class MCPCommandController {
|
|
|
804
829
|
tokenUrl: string;
|
|
805
830
|
clientId?: string;
|
|
806
831
|
scopes?: string;
|
|
832
|
+
resource?: string;
|
|
807
833
|
}> {
|
|
808
834
|
// First test if server actually needs auth by connecting without OAuth
|
|
809
835
|
let connectionSucceeded = false;
|
|
@@ -1415,6 +1441,9 @@ export class MCPCommandController {
|
|
|
1415
1441
|
|
|
1416
1442
|
this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
|
|
1417
1443
|
|
|
1444
|
+
const oauthResource =
|
|
1445
|
+
oauth.resource ?? currentAuth?.resource ?? ("url" in baseConfig ? baseConfig.url : undefined);
|
|
1446
|
+
|
|
1418
1447
|
const oauthResult = await this.#handleOAuthFlow(
|
|
1419
1448
|
oauth.authorizationUrl,
|
|
1420
1449
|
oauth.tokenUrl,
|
|
@@ -1424,10 +1453,12 @@ export class MCPCommandController {
|
|
|
1424
1453
|
found.config.oauth?.callbackPort,
|
|
1425
1454
|
found.config.oauth?.callbackPath,
|
|
1426
1455
|
found.config.oauth?.redirectUri,
|
|
1456
|
+
oauthResource,
|
|
1427
1457
|
);
|
|
1428
1458
|
|
|
1429
1459
|
const persistedClientId = oauthResult.clientId ?? oauth.clientId ?? found.config.oauth?.clientId;
|
|
1430
1460
|
const persistedClientSecret = oauthResult.clientSecret ?? (oauthClientSecret || undefined);
|
|
1461
|
+
const persistedResource = oauthResult.resource ?? oauthResource;
|
|
1431
1462
|
|
|
1432
1463
|
const updated: MCPServerConfig = {
|
|
1433
1464
|
...baseConfig,
|
|
@@ -1435,6 +1466,7 @@ export class MCPCommandController {
|
|
|
1435
1466
|
type: "oauth",
|
|
1436
1467
|
credentialId: oauthResult.credentialId,
|
|
1437
1468
|
tokenUrl: oauth.tokenUrl,
|
|
1469
|
+
resource: persistedResource,
|
|
1438
1470
|
clientId: persistedClientId,
|
|
1439
1471
|
clientSecret: persistedClientSecret,
|
|
1440
1472
|
},
|
|
@@ -40,6 +40,7 @@ import { shortenPath } from "../../tools/render-utils";
|
|
|
40
40
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
41
41
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
42
42
|
import { AgentDashboard } from "../components/agent-dashboard";
|
|
43
|
+
import { AgentHubOverlayComponent } from "../components/agent-hub";
|
|
43
44
|
import { AssistantMessageComponent } from "../components/assistant-message";
|
|
44
45
|
import { CopySelectorComponent } from "../components/copy-selector";
|
|
45
46
|
import { ExtensionDashboard } from "../components/extensions";
|
|
@@ -47,7 +48,6 @@ import { HistorySearchComponent } from "../components/history-search";
|
|
|
47
48
|
import { ModelSelectorComponent } from "../components/model-selector";
|
|
48
49
|
import { OAuthSelectorComponent } from "../components/oauth-selector";
|
|
49
50
|
import { PluginSelectorComponent } from "../components/plugin-selector";
|
|
50
|
-
import { SessionObserverOverlayComponent } from "../components/session-observer-overlay";
|
|
51
51
|
import { SessionSelectorComponent } from "../components/session-selector";
|
|
52
52
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
53
53
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
@@ -120,6 +120,7 @@ export class SelectorController {
|
|
|
120
120
|
separator: settings.get("statusLine.separator"),
|
|
121
121
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
122
122
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
123
|
+
transparent: settings.get("statusLine.transparent"),
|
|
123
124
|
...previewSettings,
|
|
124
125
|
});
|
|
125
126
|
this.ctx.updateEditorTopBorder();
|
|
@@ -147,6 +148,7 @@ export class SelectorController {
|
|
|
147
148
|
separator: settings.get("statusLine.separator"),
|
|
148
149
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
149
150
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
151
|
+
transparent: settings.get("statusLine.transparent"),
|
|
150
152
|
});
|
|
151
153
|
this.ctx.updateEditorTopBorder();
|
|
152
154
|
this.ctx.ui.requestRender();
|
|
@@ -351,6 +353,7 @@ export class SelectorController {
|
|
|
351
353
|
case "statusLineShowHooks":
|
|
352
354
|
case "statusLine.showHookStatus":
|
|
353
355
|
case "statusLine.sessionAccent":
|
|
356
|
+
case "statusLine.transparent":
|
|
354
357
|
case "statusLineSegments":
|
|
355
358
|
case "statusLineModelThinking":
|
|
356
359
|
case "statusLinePathAbbreviate":
|
|
@@ -369,6 +372,7 @@ export class SelectorController {
|
|
|
369
372
|
separator: settings.get("statusLine.separator"),
|
|
370
373
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
371
374
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
375
|
+
transparent: settings.get("statusLine.transparent"),
|
|
372
376
|
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
373
377
|
};
|
|
374
378
|
this.ctx.statusLine.updateSettings(statusLineSettings);
|
|
@@ -578,7 +582,7 @@ export class SelectorController {
|
|
|
578
582
|
}
|
|
579
583
|
|
|
580
584
|
this.ctx.chatContainer.clear();
|
|
581
|
-
this.ctx.renderInitialMessages(
|
|
585
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
582
586
|
this.ctx.editor.setText(result.selectedText);
|
|
583
587
|
done();
|
|
584
588
|
this.ctx.showStatus("Branched to new session");
|
|
@@ -719,9 +723,10 @@ export class SelectorController {
|
|
|
719
723
|
return;
|
|
720
724
|
}
|
|
721
725
|
|
|
722
|
-
// Update UI —
|
|
726
|
+
// Update UI — rebuild the display transcript for the new leaf (the
|
|
727
|
+
// context from navigateTree is the LLM context, not the transcript).
|
|
723
728
|
this.ctx.chatContainer.clear();
|
|
724
|
-
this.ctx.renderInitialMessages(
|
|
729
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
725
730
|
await this.ctx.reloadTodos();
|
|
726
731
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
727
732
|
this.ctx.editor.setText(result.editorText);
|
|
@@ -846,7 +851,7 @@ export class SelectorController {
|
|
|
846
851
|
this.ctx.statusLine.setSessionStartTime(Date.now());
|
|
847
852
|
this.ctx.updateEditorTopBorder();
|
|
848
853
|
this.ctx.updateEditorBorderColor();
|
|
849
|
-
this.ctx.renderInitialMessages(
|
|
854
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
850
855
|
await this.ctx.reloadTodos();
|
|
851
856
|
this.ctx.ui.requestRender(true, { clearScrollback: true });
|
|
852
857
|
return true;
|
|
@@ -871,7 +876,7 @@ export class SelectorController {
|
|
|
871
876
|
|
|
872
877
|
// Clear and re-render the chat
|
|
873
878
|
this.ctx.chatContainer.clear();
|
|
874
|
-
this.ctx.renderInitialMessages(
|
|
879
|
+
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
875
880
|
await this.ctx.reloadTodos();
|
|
876
881
|
this.ctx.showStatus(movedProject ? `Resumed session in ${shortenPath(newCwd)}` : "Resumed session");
|
|
877
882
|
}
|
|
@@ -1074,31 +1079,34 @@ export class SelectorController {
|
|
|
1074
1079
|
});
|
|
1075
1080
|
}
|
|
1076
1081
|
|
|
1077
|
-
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1082
|
+
showAgentHub(observers: SessionObserverRegistry): void {
|
|
1083
|
+
const hubKeys = [
|
|
1084
|
+
...this.ctx.keybindings.getKeys("app.agents.hub"),
|
|
1085
|
+
...this.ctx.keybindings.getKeys("app.session.observe"),
|
|
1086
|
+
];
|
|
1087
|
+
let hub: AgentHubOverlayComponent | undefined;
|
|
1080
1088
|
let overlayHandle: OverlayHandle | undefined;
|
|
1081
1089
|
|
|
1082
1090
|
const done = () => {
|
|
1083
|
-
|
|
1091
|
+
hub?.dispose();
|
|
1084
1092
|
overlayHandle?.hide();
|
|
1085
1093
|
this.ctx.ui.requestRender();
|
|
1086
1094
|
};
|
|
1087
1095
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
this.ctx.ui.requestRender()
|
|
1096
|
+
hub = new AgentHubOverlayComponent({
|
|
1097
|
+
observers,
|
|
1098
|
+
hubKeys,
|
|
1099
|
+
onDone: done,
|
|
1100
|
+
requestRender: () => this.ctx.ui.requestRender(),
|
|
1093
1101
|
});
|
|
1094
1102
|
|
|
1095
|
-
overlayHandle = this.ctx.ui.showOverlay(
|
|
1103
|
+
overlayHandle = this.ctx.ui.showOverlay(hub, {
|
|
1096
1104
|
anchor: "bottom-center",
|
|
1097
1105
|
width: "100%",
|
|
1098
1106
|
maxHeight: "100%",
|
|
1099
1107
|
margin: 0,
|
|
1100
1108
|
});
|
|
1101
|
-
this.ctx.ui.setFocus(
|
|
1109
|
+
this.ctx.ui.setFocus(hub);
|
|
1102
1110
|
this.ctx.ui.requestRender();
|
|
1103
1111
|
}
|
|
1104
1112
|
}
|