@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
|
@@ -36,6 +36,16 @@ export class AssistantMessageComponent extends Container {
|
|
|
36
36
|
* transcript keeps the error in history.
|
|
37
37
|
*/
|
|
38
38
|
#errorPinned = false;
|
|
39
|
+
/**
|
|
40
|
+
* Monotonic content version reported to the transcript container via
|
|
41
|
+
* {@link getTranscriptBlockVersion}. Bumped by {@link updateContent} — the
|
|
42
|
+
* choke point every mutator funnels through, including the post-finalize
|
|
43
|
+
* ones: `setErrorPinned(false)` restoring the inline error at the next
|
|
44
|
+
* turn's `agent_start`, late tool-result images, async Kitty conversions,
|
|
45
|
+
* and `setUsageInfo`. Without it, the container's committed-scrollback
|
|
46
|
+
* bypass would replay this block's pre-mutation bytes forever.
|
|
47
|
+
*/
|
|
48
|
+
#blockVersion = 0;
|
|
39
49
|
/** Whether the last updateContent carried an in-flight streaming partial; such
|
|
40
50
|
* renders bypass the markdown module LRU (see Markdown.transientRenderCache). */
|
|
41
51
|
#lastUpdateTransient = false;
|
|
@@ -86,6 +96,10 @@ export class AssistantMessageComponent extends Container {
|
|
|
86
96
|
return this.#transcriptBlockFinalized;
|
|
87
97
|
}
|
|
88
98
|
|
|
99
|
+
getTranscriptBlockVersion(): number {
|
|
100
|
+
return this.#blockVersion;
|
|
101
|
+
}
|
|
102
|
+
|
|
89
103
|
markTranscriptBlockFinalized(): void {
|
|
90
104
|
this.#transcriptBlockFinalized = true;
|
|
91
105
|
}
|
|
@@ -215,6 +229,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
215
229
|
}
|
|
216
230
|
|
|
217
231
|
updateContent(message: AssistantMessage, opts?: { transient?: boolean }): void {
|
|
232
|
+
this.#blockVersion++;
|
|
218
233
|
this.#lastMessage = message;
|
|
219
234
|
this.#lastUpdateTransient = opts?.transient === true;
|
|
220
235
|
|
|
@@ -73,7 +73,11 @@ export class BtwPanelComponent extends Container {
|
|
|
73
73
|
this.addChild(new Text(this.#footerLine(), 1, 0));
|
|
74
74
|
this.addChild(new Spacer(1));
|
|
75
75
|
this.addChild(new DynamicBorder(str => theme.fg("dim", str)));
|
|
76
|
-
this
|
|
76
|
+
// Component-scoped: a rebuild replaces only this panel's own children
|
|
77
|
+
// (streaming deltas arrive per token, and a full compose would re-walk
|
|
78
|
+
// the whole transcript each time). Before the panel is mounted the TUI
|
|
79
|
+
// cannot resolve it and falls back to a full compose on its own.
|
|
80
|
+
this.#tui.requestComponentRender(this);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
#footerLine(): string {
|
|
@@ -1,51 +1,87 @@
|
|
|
1
|
-
import { Box,
|
|
1
|
+
import { Box, type Component, Markdown } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
3
3
|
import type { CompactionSummaryMessage } from "../../session/messages";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Compaction point in the transcript, rendered as a slim horizontal divider:
|
|
7
|
+
*
|
|
8
|
+
* ──────── 📷 compacted · ctrl+o ────────
|
|
9
|
+
*
|
|
10
|
+
* The conversation above the divider stays visible (display transcript keeps
|
|
11
|
+
* full history); only the LLM context was reset. Expanding (ctrl+o) reveals
|
|
12
|
+
* the compaction summary below the divider.
|
|
8
13
|
*/
|
|
9
|
-
export class CompactionSummaryMessageComponent
|
|
14
|
+
export class CompactionSummaryMessageComponent implements Component {
|
|
10
15
|
#expanded = false;
|
|
16
|
+
#cache?: { width: number; lines: string[] };
|
|
17
|
+
#detail?: Box;
|
|
11
18
|
|
|
12
|
-
constructor(private readonly message: CompactionSummaryMessage) {
|
|
13
|
-
super(1, 1, t => theme.bg("customMessageBg", t));
|
|
14
|
-
this.#updateDisplay();
|
|
15
|
-
}
|
|
19
|
+
constructor(private readonly message: CompactionSummaryMessage) {}
|
|
16
20
|
|
|
17
21
|
setExpanded(expanded: boolean): void {
|
|
22
|
+
if (this.#expanded === expanded) return;
|
|
18
23
|
this.#expanded = expanded;
|
|
19
|
-
this.#
|
|
24
|
+
this.#cache = undefined;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
invalidate(): void {
|
|
28
|
+
this.#cache = undefined;
|
|
29
|
+
// Theme may have changed — rebuild the detail box lazily on next render.
|
|
30
|
+
this.#detail = undefined;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
render(width: number): readonly string[] {
|
|
34
|
+
width = Math.max(1, width);
|
|
35
|
+
if (this.#cache?.width === width) {
|
|
36
|
+
return this.#cache.lines;
|
|
37
|
+
}
|
|
38
|
+
const lines = this.#expanded
|
|
39
|
+
? ["", this.#divider(width), "", ...this.#detailBox().render(width)]
|
|
40
|
+
: ["", this.#divider(width), ""];
|
|
41
|
+
this.#cache = { width, lines };
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
29
44
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
#divider(width: number): string {
|
|
46
|
+
const rule = theme.tree.horizontal;
|
|
47
|
+
const label = `${theme.icon.camera} compacted`;
|
|
48
|
+
// sep.dot ships pre-padded (" · "); trim so the hint joins with single spaces.
|
|
49
|
+
const hint = `${theme.sep.dot.trim()} ctrl+o`;
|
|
50
|
+
const plainWidth = Bun.stringWidth(`${label} ${hint}`, { countAnsiEscapeCodes: false });
|
|
51
|
+
// ` label hint ` framed by rules on both sides.
|
|
52
|
+
const remaining = width - plainWidth - 2;
|
|
53
|
+
if (remaining < 4) {
|
|
54
|
+
// Too narrow for a framed rule — emit the bare label.
|
|
55
|
+
return theme.fg("muted", label);
|
|
56
|
+
}
|
|
57
|
+
const left = Math.floor(remaining / 2);
|
|
58
|
+
const right = remaining - left;
|
|
59
|
+
return (
|
|
60
|
+
theme.fg("dim", rule.repeat(left)) +
|
|
61
|
+
` ${theme.fg("muted", label)} ${theme.fg("dim", hint)} ` +
|
|
62
|
+
theme.fg("dim", rule.repeat(right))
|
|
63
|
+
);
|
|
64
|
+
}
|
|
34
65
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
66
|
+
#detailBox(): Box {
|
|
67
|
+
if (this.#detail) return this.#detail;
|
|
68
|
+
const box = new Box(1, 1, t => theme.bg("customMessageBg", t));
|
|
69
|
+
const tokenStr = this.message.tokensBefore.toLocaleString();
|
|
70
|
+
const frameCount = this.message.images?.length ?? 0;
|
|
71
|
+
const frameNote =
|
|
72
|
+
frameCount > 0 ? `\n\n_${frameCount} snapcompact frame${frameCount === 1 ? "" : "s"} attached_` : "";
|
|
73
|
+
box.addChild(
|
|
74
|
+
new Markdown(
|
|
75
|
+
`**Compacted from ${tokenStr} tokens**\n\n${this.message.summary}${frameNote}`,
|
|
76
|
+
0,
|
|
77
|
+
0,
|
|
78
|
+
getMarkdownTheme(),
|
|
79
|
+
{
|
|
39
80
|
color: (text: string) => theme.fg("customMessageText", text),
|
|
40
|
-
}
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
46
|
-
if (this.message.shortSummary) {
|
|
47
|
-
this.addChild(new Text(theme.fg("customMessageText", this.message.shortSummary), 0, 1));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
81
|
+
},
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
this.#detail = box;
|
|
85
|
+
return box;
|
|
50
86
|
}
|
|
51
87
|
}
|
|
@@ -175,6 +175,8 @@ export class CustomEditor extends Editor {
|
|
|
175
175
|
onDequeue?: () => void;
|
|
176
176
|
/** Called when Caps Lock is pressed. */
|
|
177
177
|
onCapsLock?: () => void;
|
|
178
|
+
/** Called when left-arrow is pressed while the editor is empty (cursor necessarily at start). */
|
|
179
|
+
onLeftAtStart?: () => void;
|
|
178
180
|
|
|
179
181
|
/** Custom key handlers from extensions and non-built-in app actions. */
|
|
180
182
|
#customKeyHandlers = new Map<KeyId, () => void>();
|
|
@@ -257,6 +259,14 @@ export class CustomEditor extends Editor {
|
|
|
257
259
|
const parsedKey = parseKey(data);
|
|
258
260
|
const canonical = parsedKey !== undefined ? canonicalKeyId(parsedKey) : undefined;
|
|
259
261
|
|
|
262
|
+
// Left-arrow on an empty editor: surface for the agent-hub double-tap
|
|
263
|
+
// gesture. Plain "left" only — modified arrows and any in-text cursor
|
|
264
|
+
// movement fall through to normal handling.
|
|
265
|
+
if (canonical === "left" && this.onLeftAtStart && this.getText().trim() === "") {
|
|
266
|
+
this.onLeftAtStart();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
260
270
|
if (canonical !== undefined) {
|
|
261
271
|
// Intercept configured image paste (async - fires and handles result)
|
|
262
272
|
if (this.#matchesAction(canonical, "app.clipboard.pasteImage") && this.onPasteImage) {
|
|
@@ -57,6 +57,7 @@ export interface MCPAddWizardOAuthResult {
|
|
|
57
57
|
credentialId: string;
|
|
58
58
|
clientId?: string;
|
|
59
59
|
clientSecret?: string;
|
|
60
|
+
resource?: string;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
interface WizardState {
|
|
@@ -71,6 +72,7 @@ interface WizardState {
|
|
|
71
72
|
oauthClientId: string;
|
|
72
73
|
oauthClientSecret: string;
|
|
73
74
|
oauthScopes: string;
|
|
75
|
+
oauthResource: string;
|
|
74
76
|
oauthCredentialId: string | null;
|
|
75
77
|
apiKey: string;
|
|
76
78
|
authLocation: AuthLocation | null;
|
|
@@ -101,6 +103,7 @@ export class MCPAddWizard extends Container {
|
|
|
101
103
|
oauthClientId: "",
|
|
102
104
|
oauthClientSecret: "",
|
|
103
105
|
oauthScopes: "",
|
|
106
|
+
oauthResource: "",
|
|
104
107
|
oauthCredentialId: null,
|
|
105
108
|
apiKey: "",
|
|
106
109
|
authLocation: null,
|
|
@@ -122,6 +125,7 @@ export class MCPAddWizard extends Container {
|
|
|
122
125
|
clientId: string,
|
|
123
126
|
clientSecret: string,
|
|
124
127
|
scopes: string,
|
|
128
|
+
resource?: string,
|
|
125
129
|
) => Promise<MCPAddWizardOAuthResult>)
|
|
126
130
|
| null = null;
|
|
127
131
|
#onTestConnectionCallback: ((config: MCPServerConfig) => Promise<void>) | null = null;
|
|
@@ -136,6 +140,7 @@ export class MCPAddWizard extends Container {
|
|
|
136
140
|
clientId: string,
|
|
137
141
|
clientSecret: string,
|
|
138
142
|
scopes: string,
|
|
143
|
+
resource?: string,
|
|
139
144
|
) => Promise<MCPAddWizardOAuthResult>,
|
|
140
145
|
onTestConnection?: (config: MCPServerConfig) => Promise<void>,
|
|
141
146
|
onRender?: () => void,
|
|
@@ -987,6 +992,7 @@ export class MCPAddWizard extends Container {
|
|
|
987
992
|
this.#state.oauthTokenUrl = oauth.tokenUrl;
|
|
988
993
|
this.#state.oauthClientId = oauth.clientId || "";
|
|
989
994
|
this.#state.oauthScopes = oauth.scopes || "";
|
|
995
|
+
this.#state.oauthResource = oauth.resource || (this.#state.transport === "stdio" ? "" : this.#state.url);
|
|
990
996
|
this.#state.authMethod = "oauth";
|
|
991
997
|
|
|
992
998
|
this.#contentContainer.clear();
|
|
@@ -1054,6 +1060,7 @@ export class MCPAddWizard extends Container {
|
|
|
1054
1060
|
type: "oauth",
|
|
1055
1061
|
credentialId: this.#state.oauthCredentialId,
|
|
1056
1062
|
tokenUrl: this.#state.oauthTokenUrl || undefined,
|
|
1063
|
+
resource: this.#state.oauthResource || undefined,
|
|
1057
1064
|
clientId: this.#state.oauthClientId || undefined,
|
|
1058
1065
|
clientSecret: this.#state.oauthClientSecret || undefined,
|
|
1059
1066
|
};
|
|
@@ -1081,6 +1088,7 @@ export class MCPAddWizard extends Container {
|
|
|
1081
1088
|
type: "oauth",
|
|
1082
1089
|
credentialId: this.#state.oauthCredentialId,
|
|
1083
1090
|
tokenUrl: this.#state.oauthTokenUrl || undefined,
|
|
1091
|
+
resource: this.#state.oauthResource || undefined,
|
|
1084
1092
|
clientId: this.#state.oauthClientId || undefined,
|
|
1085
1093
|
clientSecret: this.#state.oauthClientSecret || undefined,
|
|
1086
1094
|
};
|
|
@@ -1142,12 +1150,14 @@ export class MCPAddWizard extends Container {
|
|
|
1142
1150
|
|
|
1143
1151
|
try {
|
|
1144
1152
|
// Call OAuth handler
|
|
1153
|
+
const oauthResource = this.#state.oauthResource || (this.#state.transport === "stdio" ? "" : this.#state.url);
|
|
1145
1154
|
const oauthResult = await this.#onOAuthCallback(
|
|
1146
1155
|
this.#state.oauthAuthUrl,
|
|
1147
1156
|
this.#state.oauthTokenUrl,
|
|
1148
1157
|
this.#state.oauthClientId,
|
|
1149
1158
|
this.#state.oauthClientSecret,
|
|
1150
1159
|
this.#state.oauthScopes,
|
|
1160
|
+
oauthResource || undefined,
|
|
1151
1161
|
);
|
|
1152
1162
|
|
|
1153
1163
|
// Store credential ID + any dynamically-registered client credentials,
|
|
@@ -1155,6 +1165,7 @@ export class MCPAddWizard extends Container {
|
|
|
1155
1165
|
this.#state.oauthCredentialId = oauthResult.credentialId;
|
|
1156
1166
|
if (oauthResult.clientId) this.#state.oauthClientId = oauthResult.clientId;
|
|
1157
1167
|
if (oauthResult.clientSecret) this.#state.oauthClientSecret = oauthResult.clientSecret;
|
|
1168
|
+
this.#state.oauthResource = oauthResult.resource ?? oauthResource;
|
|
1158
1169
|
|
|
1159
1170
|
// Show success message
|
|
1160
1171
|
this.#contentContainer.clear();
|
|
@@ -1284,6 +1295,7 @@ export class MCPAddWizard extends Container {
|
|
|
1284
1295
|
type: "oauth",
|
|
1285
1296
|
credentialId: this.#state.oauthCredentialId,
|
|
1286
1297
|
tokenUrl: this.#state.oauthTokenUrl || undefined,
|
|
1298
|
+
resource: this.#state.oauthResource || undefined,
|
|
1287
1299
|
clientId: this.#state.oauthClientId || undefined,
|
|
1288
1300
|
clientSecret: this.#state.oauthClientSecret || undefined,
|
|
1289
1301
|
};
|
|
@@ -1312,6 +1324,7 @@ export class MCPAddWizard extends Container {
|
|
|
1312
1324
|
type: "oauth",
|
|
1313
1325
|
credentialId: this.#state.oauthCredentialId,
|
|
1314
1326
|
tokenUrl: this.#state.oauthTokenUrl || undefined,
|
|
1327
|
+
resource: this.#state.oauthResource || undefined,
|
|
1315
1328
|
clientId: this.#state.oauthClientId || undefined,
|
|
1316
1329
|
clientSecret: this.#state.oauthClientSecret || undefined,
|
|
1317
1330
|
};
|
|
@@ -196,6 +196,7 @@ export interface StatusLinePreviewSettings {
|
|
|
196
196
|
rightSegments?: StatusLineSegmentId[];
|
|
197
197
|
separator?: StatusLineSeparatorStyle;
|
|
198
198
|
sessionAccent?: boolean;
|
|
199
|
+
transparent?: boolean;
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
export interface SettingsCallbacks {
|
|
@@ -590,6 +591,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
590
591
|
rightSegments: settings.get("statusLine.rightSegments"),
|
|
591
592
|
separator: settings.get("statusLine.separator"),
|
|
592
593
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
594
|
+
transparent: settings.get("statusLine.transparent"),
|
|
593
595
|
};
|
|
594
596
|
this.callbacks.onStatusLinePreview?.(statusLineSettings);
|
|
595
597
|
this.#updateStatusPreview();
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
4
4
|
import { estimateTokens } from "@oh-my-pi/pi-agent-core/compaction";
|
|
5
5
|
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
6
|
-
import {
|
|
6
|
+
import { getProjectDir } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { $ } from "bun";
|
|
8
8
|
import { settings } from "../../../config/settings";
|
|
9
9
|
import type { AgentSession } from "../../../session/agent-session";
|
|
@@ -196,6 +196,7 @@ export class StatusLineComponent implements Component {
|
|
|
196
196
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
197
197
|
segmentOptions: settings.getGroup("statusLine").segmentOptions,
|
|
198
198
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
199
|
+
transparent: settings.get("statusLine.transparent"),
|
|
199
200
|
};
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -713,7 +714,15 @@ export class StatusLineComponent implements Component {
|
|
|
713
714
|
const ctx = this.#buildSegmentContext(width, effectiveSettings.segmentOptions, includeContext);
|
|
714
715
|
const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
|
|
715
716
|
|
|
716
|
-
|
|
717
|
+
// `transparent` reuses the empty-string sentinel (`\x1b[49m`) so the bar
|
|
718
|
+
// inherits the terminal's default background, matching custom themes that
|
|
719
|
+
// set `statusLineBg: ""`. Powerline end caps need a contrasting fill to
|
|
720
|
+
// bridge the bar into the surrounding terminal; without one they read as
|
|
721
|
+
// stray glyphs, so the cap renderer drops them when the fill is empty.
|
|
722
|
+
const TRANSPARENT_BG_ANSI = "\x1b[49m";
|
|
723
|
+
const themeBgAnsi = theme.getBgAnsi("statusLineBg");
|
|
724
|
+
const bgAnsi = effectiveSettings.transparent ? TRANSPARENT_BG_ANSI : themeBgAnsi;
|
|
725
|
+
const transparentBg = bgAnsi === TRANSPARENT_BG_ANSI;
|
|
717
726
|
const fgAnsi = theme.getFgAnsi("text");
|
|
718
727
|
const sepAnsi = theme.getFgAnsi("statusLineSep");
|
|
719
728
|
|
|
@@ -738,9 +747,7 @@ export class StatusLineComponent implements Component {
|
|
|
738
747
|
|
|
739
748
|
const runningBackgroundJobs = this.session.getAsyncJobSnapshot()?.running.length ?? 0;
|
|
740
749
|
if (runningBackgroundJobs > 0) {
|
|
741
|
-
|
|
742
|
-
const label = `${formatCount("job", runningBackgroundJobs)} running`;
|
|
743
|
-
rightParts.push(theme.fg("statusLineSubagents", `${icon}${label}`));
|
|
750
|
+
rightParts.unshift(theme.fg("statusLineSubagents", `${theme.icon.job} ${runningBackgroundJobs}`));
|
|
744
751
|
}
|
|
745
752
|
const topFillWidth = Math.max(0, width);
|
|
746
753
|
const left = [...leftParts];
|
|
@@ -748,8 +755,10 @@ export class StatusLineComponent implements Component {
|
|
|
748
755
|
|
|
749
756
|
const leftSepWidth = visibleWidth(separatorDef.left);
|
|
750
757
|
const rightSepWidth = visibleWidth(separatorDef.right);
|
|
751
|
-
|
|
752
|
-
|
|
758
|
+
// Transparent mode drops powerline caps (they need a bg fill to bridge),
|
|
759
|
+
// so the width budget excludes them too.
|
|
760
|
+
const leftCapWidth = separatorDef.endCaps && !transparentBg ? visibleWidth(separatorDef.endCaps.right) : 0;
|
|
761
|
+
const rightCapWidth = separatorDef.endCaps && !transparentBg ? visibleWidth(separatorDef.endCaps.left) : 0;
|
|
753
762
|
|
|
754
763
|
const groupWidth = (parts: string[], capWidth: number, sepWidth: number): number => {
|
|
755
764
|
if (parts.length === 0) return 0;
|
|
@@ -810,11 +819,12 @@ export class StatusLineComponent implements Component {
|
|
|
810
819
|
const renderGroup = (parts: string[], direction: "left" | "right"): string => {
|
|
811
820
|
if (parts.length === 0) return "";
|
|
812
821
|
const sep = direction === "left" ? separatorDef.left : separatorDef.right;
|
|
813
|
-
const cap =
|
|
814
|
-
|
|
815
|
-
?
|
|
816
|
-
|
|
817
|
-
|
|
822
|
+
const cap =
|
|
823
|
+
separatorDef.endCaps && !transparentBg
|
|
824
|
+
? direction === "left"
|
|
825
|
+
? separatorDef.endCaps.right
|
|
826
|
+
: separatorDef.endCaps.left
|
|
827
|
+
: "";
|
|
818
828
|
const capPrefix = separatorDef.endCaps?.useBgAsFg ? bgAnsi.replace("\x1b[48;", "\x1b[38;") : bgAnsi + sepAnsi;
|
|
819
829
|
const capText = cap ? `${capPrefix}${cap}\x1b[0m` : "";
|
|
820
830
|
|
|
@@ -18,6 +18,9 @@ export interface StatusLineSettings {
|
|
|
18
18
|
segmentOptions?: StatusLineSegmentOptions;
|
|
19
19
|
showHookStatus?: boolean;
|
|
20
20
|
sessionAccent?: boolean;
|
|
21
|
+
/** Drop the theme's `statusLineBg` fill and powerline caps so the bar
|
|
22
|
+
* inherits the terminal's default background. */
|
|
23
|
+
transparent?: boolean;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export type EffectiveStatusLineSettings = Required<
|
|
@@ -19,6 +19,7 @@ import type { Theme } from "../../modes/theme/theme";
|
|
|
19
19
|
import { theme } from "../../modes/theme/theme";
|
|
20
20
|
import { BASH_DEFAULT_PREVIEW_LINES } from "../../tools/bash";
|
|
21
21
|
import { EVAL_DEFAULT_PREVIEW_LINES } from "../../tools/eval";
|
|
22
|
+
import { isWaitingPollDetails } from "../../tools/job";
|
|
22
23
|
import {
|
|
23
24
|
formatArgsInline,
|
|
24
25
|
JSON_TREE_MAX_DEPTH_COLLAPSED,
|
|
@@ -194,6 +195,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
194
195
|
// sealed the block stays in the transcript's repaintable live region so a
|
|
195
196
|
// late result still repaints instead of stranding the streaming preview.
|
|
196
197
|
#sealed = false;
|
|
198
|
+
// A `job` poll result whose watched jobs are all still running. Such a
|
|
199
|
+
// block never finalizes (stays in the transcript live region) so a
|
|
200
|
+
// follow-up `job` call can displace it instead of stacking another
|
|
201
|
+
// "waiting on N jobs" frame. Cleared by `seal()`.
|
|
202
|
+
#displaceable = false;
|
|
197
203
|
#renderState: {
|
|
198
204
|
spinnerFrame?: number;
|
|
199
205
|
expanded: boolean;
|
|
@@ -359,6 +365,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
359
365
|
): void {
|
|
360
366
|
this.#result = result;
|
|
361
367
|
this.#isPartial = isPartial;
|
|
368
|
+
// A `job` poll that found every watched job still running is transient
|
|
369
|
+
// "still waiting" chrome; keep the block displaceable so the next `job`
|
|
370
|
+
// call replaces it instead of stacking another waiting frame (see the
|
|
371
|
+
// event controller's displaceable-poll bookkeeping).
|
|
372
|
+
this.#displaceable = this.#toolName === "job" && result.isError !== true && isWaitingPollDetails(result.details);
|
|
362
373
|
// When tool is complete, ensure args are marked complete so spinner stops
|
|
363
374
|
if (!isPartial) {
|
|
364
375
|
this.#argsComplete = true;
|
|
@@ -425,7 +436,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
425
436
|
(this.#result?.details as { async?: { state?: string } } | undefined)?.async?.state === "running";
|
|
426
437
|
const isBackgroundAsyncTask = this.#toolName === "task" && isBackgroundAsyncRunning;
|
|
427
438
|
const isPartialTask = this.#isPartial && this.#toolName === "task" && !isBackgroundAsyncTask;
|
|
428
|
-
|
|
439
|
+
// A displaceable waiting poll keeps its spinner ticking: it reads as one
|
|
440
|
+
// persistent live poll, and the changing leading glyph keeps the
|
|
441
|
+
// transcript's stable-prefix ratchet from committing rows of a block
|
|
442
|
+
// that a follow-up `job` call may remove.
|
|
443
|
+
const needsSpinner = isStreamingArgs || isPartialTask || this.isDisplaceableBlock();
|
|
429
444
|
if (needsSpinner && !this.#spinnerInterval) {
|
|
430
445
|
const now = performance.now();
|
|
431
446
|
const frameCount = theme.spinnerFrames.length;
|
|
@@ -513,6 +528,9 @@ export class ToolExecutionComponent extends Container {
|
|
|
513
528
|
isTranscriptBlockFinalized(): boolean {
|
|
514
529
|
if (this.#sealed) return true;
|
|
515
530
|
if (this.#result === undefined) return false;
|
|
531
|
+
// A displaceable waiting poll stays live: its rows are kept out of
|
|
532
|
+
// native scrollback so a follow-up `job` call can remove the block.
|
|
533
|
+
if (this.#displaceable) return false;
|
|
516
534
|
if (!this.#isPartial) return true;
|
|
517
535
|
// Partial result: a background async tool is accepted to freeze (the agent
|
|
518
536
|
// continues while it runs and would otherwise pin an unbounded live region);
|
|
@@ -528,11 +546,23 @@ export class ToolExecutionComponent extends Container {
|
|
|
528
546
|
seal(): void {
|
|
529
547
|
if (this.#sealed) return;
|
|
530
548
|
this.#sealed = true;
|
|
549
|
+
this.#displaceable = false;
|
|
531
550
|
this.stopAnimation();
|
|
532
551
|
this.#updateDisplay();
|
|
533
552
|
this.#ui.requestRender();
|
|
534
553
|
}
|
|
535
554
|
|
|
555
|
+
/**
|
|
556
|
+
* Whether this block is a waiting `job` poll (every watched job still
|
|
557
|
+
* running) that has not been sealed. Such a block never finalized, so none
|
|
558
|
+
* of its rows entered native scrollback (the ticking spinner keeps the
|
|
559
|
+
* stable-prefix ratchet at zero) and the whole block can be removed when a
|
|
560
|
+
* follow-up `job` call supersedes it.
|
|
561
|
+
*/
|
|
562
|
+
isDisplaceableBlock(): boolean {
|
|
563
|
+
return this.#displaceable && !this.#sealed;
|
|
564
|
+
}
|
|
565
|
+
|
|
536
566
|
/**
|
|
537
567
|
* Stop spinner animation and cleanup resources.
|
|
538
568
|
*/
|