lsd-pi 1.1.4 → 1.1.6
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/README.md +2 -1
- package/dist/headless-ui.js +2 -0
- package/dist/onboarding.js +11 -8
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +14 -0
- package/dist/resources/extensions/async-jobs/await-tool.js +14 -0
- package/dist/resources/extensions/async-jobs/cancel-job-tool.js +7 -0
- package/dist/resources/extensions/cache-timer/index.js +5 -0
- package/dist/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
- package/dist/resources/extensions/codex-rotate/README.md +9 -3
- package/dist/resources/extensions/codex-rotate/commands.js +15 -8
- package/dist/resources/extensions/codex-rotate/index.js +17 -8
- package/dist/resources/extensions/memory/auto-extract.js +196 -80
- package/dist/resources/extensions/memory/dream.js +86 -19
- package/dist/resources/extensions/shared/rtk.js +89 -87
- package/dist/resources/extensions/subagent/index.js +33 -7
- package/dist/startup-model-validation.js +12 -2
- package/dist/update-check.js +2 -2
- package/dist/update-cmd.js +3 -3
- package/dist/welcome-screen.js +43 -14
- package/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +43 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/pty-executor.d.ts +48 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.js +173 -0
- package/packages/pi-coding-agent/dist/core/pty-executor.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +16 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +18 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-approval.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-approval.js +2 -2
- package/packages/pi-coding-agent/dist/core/tool-approval.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +23 -2
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +50 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.js +289 -0
- package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +36 -22
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +3 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +23 -62
- package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts +39 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +182 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +106 -77
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +2 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +4 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +11 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +49 -13
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +27 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +251 -39
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts +10 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.js +67 -0
- package/packages/pi-coding-agent/dist/utils/terminal-screen.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts +7 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.js +67 -0
- package/packages/pi-coding-agent/dist/utils/terminal-serializer.js.map +1 -0
- package/packages/pi-coding-agent/package.json +9 -4
- package/packages/pi-coding-agent/src/core/agent-session.clear-queue.test.ts +50 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +50 -4
- package/packages/pi-coding-agent/src/core/extensions/types.ts +1 -1
- package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
- package/packages/pi-coding-agent/src/core/pty-executor.ts +229 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +16 -3
- package/packages/pi-coding-agent/src/core/settings-manager.ts +27 -0
- package/packages/pi-coding-agent/src/core/tool-approval.ts +2 -2
- package/packages/pi-coding-agent/src/core/tools/index.ts +35 -2
- package/packages/pi-coding-agent/src/core/tools/pty.ts +354 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +37 -24
- package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +22 -70
- package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +224 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +45 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +104 -81
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +5 -19
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +55 -13
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +296 -48
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +2 -2
- package/packages/pi-coding-agent/src/utils/terminal-screen.ts +77 -0
- package/packages/pi-coding-agent/src/utils/terminal-serializer.ts +72 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts +2 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js +105 -0
- package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js.map +1 -0
- package/packages/pi-tui/dist/components/editor.d.ts +4 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +57 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/components/loader.d.ts +26 -6
- package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/loader.js +178 -18
- package/packages/pi-tui/dist/components/loader.js.map +1 -1
- package/packages/pi-tui/src/components/editor.ts +65 -3
- package/packages/pi-tui/src/components/loader.ts +196 -19
- package/pkg/dist/modes/interactive/theme/themes.js +2 -2
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +13 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +13 -0
- package/src/resources/extensions/async-jobs/cancel-job-tool.ts +8 -0
- package/src/resources/extensions/cache-timer/index.ts +102 -96
- package/src/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
- package/src/resources/extensions/codex-rotate/README.md +9 -3
- package/src/resources/extensions/codex-rotate/commands.ts +335 -329
- package/src/resources/extensions/codex-rotate/index.ts +85 -75
- package/src/resources/extensions/memory/auto-extract.ts +330 -204
- package/src/resources/extensions/memory/dream.ts +88 -21
- package/src/resources/extensions/memory/tests/auto-extract.test.ts +200 -144
- package/src/resources/extensions/shared/rtk.js +112 -0
- package/src/resources/extensions/subagent/index.ts +35 -6
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { AgentTool } from "@gsd/pi-agent-core";
|
|
3
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
4
|
+
import { executeBashInPty, type PtyExecutionSession } from "../pty-executor.js";
|
|
5
|
+
import {
|
|
6
|
+
createHeadlessTerminal,
|
|
7
|
+
snapshotTerminalBufferText,
|
|
8
|
+
snapshotTerminalViewportText,
|
|
9
|
+
type HeadlessTerminal,
|
|
10
|
+
} from "../../utils/terminal-screen.js";
|
|
11
|
+
|
|
12
|
+
const ptyStartSchema = Type.Object({
|
|
13
|
+
command: Type.String({ description: "Shell command to start inside the PTY" }),
|
|
14
|
+
cols: Type.Optional(Type.Number({ description: "Terminal width in columns (default 80)" })),
|
|
15
|
+
rows: Type.Optional(Type.Number({ description: "Terminal height in rows (default 24)" })),
|
|
16
|
+
loginShell: Type.Optional(Type.Boolean({ description: "Whether to run the command in a login shell" })),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const ptySendSchema = Type.Object({
|
|
20
|
+
sessionId: Type.String({ description: "PTY session id from pty_start" }),
|
|
21
|
+
input: Type.String({ description: "Text or control sequence to send, e.g. \"y\\r\" or \"\\u001b[A\"" }),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const ptyReadSchema = Type.Object({
|
|
25
|
+
sessionId: Type.String({ description: "PTY session id from pty_start" }),
|
|
26
|
+
view: Type.Optional(Type.Union([
|
|
27
|
+
Type.Literal("viewport"),
|
|
28
|
+
Type.Literal("buffer"),
|
|
29
|
+
], { description: "Read the visible screen (viewport) or the full logical buffer" })),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const ptyWaitSchema = Type.Object({
|
|
33
|
+
sessionId: Type.String({ description: "PTY session id from pty_start" }),
|
|
34
|
+
text: Type.Optional(Type.String({ description: "Wait until the viewport or buffer contains this text" })),
|
|
35
|
+
view: Type.Optional(Type.Union([
|
|
36
|
+
Type.Literal("viewport"),
|
|
37
|
+
Type.Literal("buffer"),
|
|
38
|
+
], { description: "Where to search for the text (default viewport)" })),
|
|
39
|
+
timeoutMs: Type.Optional(Type.Number({ description: "How long to wait before timing out (default 30000)" })),
|
|
40
|
+
stableMs: Type.Optional(Type.Number({ description: "If text is omitted, wait until the screen stops changing for this long (default 800)" })),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const ptyResizeSchema = Type.Object({
|
|
44
|
+
sessionId: Type.String({ description: "PTY session id from pty_start" }),
|
|
45
|
+
cols: Type.Number({ description: "Terminal width in columns" }),
|
|
46
|
+
rows: Type.Number({ description: "Terminal height in rows" }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const ptyKillSchema = Type.Object({
|
|
50
|
+
sessionId: Type.String({ description: "PTY session id from pty_start" }),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export type PtyStartInput = Static<typeof ptyStartSchema>;
|
|
54
|
+
export type PtySendInput = Static<typeof ptySendSchema>;
|
|
55
|
+
export type PtyReadInput = Static<typeof ptyReadSchema>;
|
|
56
|
+
export type PtyWaitInput = Static<typeof ptyWaitSchema>;
|
|
57
|
+
export type PtyResizeInput = Static<typeof ptyResizeSchema>;
|
|
58
|
+
export type PtyKillInput = Static<typeof ptyKillSchema>;
|
|
59
|
+
|
|
60
|
+
interface ManagedPtySession {
|
|
61
|
+
id: string;
|
|
62
|
+
command: string;
|
|
63
|
+
pty: PtyExecutionSession;
|
|
64
|
+
terminal: HeadlessTerminal;
|
|
65
|
+
writeChain: Promise<void>;
|
|
66
|
+
completed: boolean;
|
|
67
|
+
cancelled: boolean;
|
|
68
|
+
exitCode?: number;
|
|
69
|
+
createdAt: number;
|
|
70
|
+
lastUpdateAt: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface PtyToolDetails {
|
|
74
|
+
sessionId: string;
|
|
75
|
+
pid?: number;
|
|
76
|
+
completed?: boolean;
|
|
77
|
+
cancelled?: boolean;
|
|
78
|
+
exitCode?: number;
|
|
79
|
+
view?: "viewport" | "buffer";
|
|
80
|
+
screenText?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class PtySessionManager {
|
|
84
|
+
private sessions = new Map<string, ManagedPtySession>();
|
|
85
|
+
|
|
86
|
+
constructor(private cwd: string) {}
|
|
87
|
+
|
|
88
|
+
async start(command: string, options?: { cols?: number; rows?: number; loginShell?: boolean }): Promise<ManagedPtySession> {
|
|
89
|
+
const id = `pty_${randomUUID().slice(0, 8)}`;
|
|
90
|
+
const terminal = createHeadlessTerminal(options?.cols ?? 80, options?.rows ?? 24, 10000);
|
|
91
|
+
const session: ManagedPtySession = {
|
|
92
|
+
id,
|
|
93
|
+
command,
|
|
94
|
+
pty: undefined as unknown as PtyExecutionSession,
|
|
95
|
+
terminal,
|
|
96
|
+
writeChain: Promise.resolve(),
|
|
97
|
+
completed: false,
|
|
98
|
+
cancelled: false,
|
|
99
|
+
createdAt: Date.now(),
|
|
100
|
+
lastUpdateAt: Date.now(),
|
|
101
|
+
};
|
|
102
|
+
this.sessions.set(id, session);
|
|
103
|
+
|
|
104
|
+
let pty: PtyExecutionSession;
|
|
105
|
+
try {
|
|
106
|
+
pty = await executeBashInPty(command, {
|
|
107
|
+
cols: options?.cols,
|
|
108
|
+
rows: options?.rows,
|
|
109
|
+
cwd: this.cwd,
|
|
110
|
+
loginShell: options?.loginShell,
|
|
111
|
+
onChunk: (chunk) => {
|
|
112
|
+
const current = this.sessions.get(id);
|
|
113
|
+
if (!current) return;
|
|
114
|
+
current.lastUpdateAt = Date.now();
|
|
115
|
+
current.writeChain = current.writeChain.then(
|
|
116
|
+
() =>
|
|
117
|
+
new Promise<void>((resolve) => {
|
|
118
|
+
current.terminal.write(chunk, () => resolve());
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
} catch (error) {
|
|
124
|
+
this.sessions.delete(id);
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
session.pty = pty;
|
|
128
|
+
|
|
129
|
+
pty.result
|
|
130
|
+
.then((result) => {
|
|
131
|
+
const current = this.sessions.get(id);
|
|
132
|
+
if (!current) return;
|
|
133
|
+
current.completed = true;
|
|
134
|
+
current.cancelled = result.cancelled;
|
|
135
|
+
current.exitCode = result.exitCode;
|
|
136
|
+
current.lastUpdateAt = Date.now();
|
|
137
|
+
})
|
|
138
|
+
.catch(() => {
|
|
139
|
+
const current = this.sessions.get(id);
|
|
140
|
+
if (!current) return;
|
|
141
|
+
current.completed = true;
|
|
142
|
+
current.lastUpdateAt = Date.now();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return session;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get(sessionId: string): ManagedPtySession {
|
|
149
|
+
const session = this.sessions.get(sessionId);
|
|
150
|
+
if (!session) {
|
|
151
|
+
throw new Error(`PTY session not found: ${sessionId}`);
|
|
152
|
+
}
|
|
153
|
+
return session;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async send(sessionId: string, input: string): Promise<ManagedPtySession> {
|
|
157
|
+
const session = this.get(sessionId);
|
|
158
|
+
if (session.completed || !session.pty.handle.isActive()) {
|
|
159
|
+
throw new Error(`PTY session is no longer active: ${sessionId}`);
|
|
160
|
+
}
|
|
161
|
+
session.pty.handle.write(input);
|
|
162
|
+
session.lastUpdateAt = Date.now();
|
|
163
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
164
|
+
return session;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async resize(sessionId: string, cols: number, rows: number): Promise<ManagedPtySession> {
|
|
168
|
+
const session = this.get(sessionId);
|
|
169
|
+
const safeCols = Math.max(20, cols);
|
|
170
|
+
const safeRows = Math.max(5, rows);
|
|
171
|
+
session.pty.handle.resize(safeCols, safeRows);
|
|
172
|
+
session.terminal.resize(safeCols, safeRows);
|
|
173
|
+
session.lastUpdateAt = Date.now();
|
|
174
|
+
return session;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async kill(sessionId: string): Promise<ManagedPtySession> {
|
|
178
|
+
const session = this.get(sessionId);
|
|
179
|
+
if (session.pty.handle.isActive()) {
|
|
180
|
+
session.pty.handle.kill();
|
|
181
|
+
}
|
|
182
|
+
session.cancelled = true;
|
|
183
|
+
session.completed = true;
|
|
184
|
+
session.lastUpdateAt = Date.now();
|
|
185
|
+
return session;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async read(sessionId: string, view: "viewport" | "buffer" = "viewport"): Promise<{ session: ManagedPtySession; text: string }> {
|
|
189
|
+
const session = this.get(sessionId);
|
|
190
|
+
await session.writeChain;
|
|
191
|
+
const text = view === "buffer"
|
|
192
|
+
? snapshotTerminalBufferText(session.terminal)
|
|
193
|
+
: snapshotTerminalViewportText(session.terminal);
|
|
194
|
+
return { session, text };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async wait(
|
|
198
|
+
sessionId: string,
|
|
199
|
+
options?: { text?: string; view?: "viewport" | "buffer"; timeoutMs?: number; stableMs?: number },
|
|
200
|
+
): Promise<{ session: ManagedPtySession; text: string }> {
|
|
201
|
+
const timeoutMs = Math.max(50, options?.timeoutMs ?? 30000);
|
|
202
|
+
const stableMs = Math.max(50, options?.stableMs ?? 800);
|
|
203
|
+
const view = options?.view ?? "viewport";
|
|
204
|
+
const start = Date.now();
|
|
205
|
+
let lastSnapshot = "";
|
|
206
|
+
let stableSince = Date.now();
|
|
207
|
+
|
|
208
|
+
while (Date.now() - start < timeoutMs) {
|
|
209
|
+
const { text } = await this.read(sessionId, view);
|
|
210
|
+
if (options?.text) {
|
|
211
|
+
if (text.includes(options.text)) {
|
|
212
|
+
return { session: this.get(sessionId), text };
|
|
213
|
+
}
|
|
214
|
+
if (this.get(sessionId).completed) {
|
|
215
|
+
return { session: this.get(sessionId), text };
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
if (text !== lastSnapshot) {
|
|
219
|
+
lastSnapshot = text;
|
|
220
|
+
stableSince = Date.now();
|
|
221
|
+
} else if (Date.now() - stableSince >= stableMs || this.get(sessionId).completed) {
|
|
222
|
+
return { session: this.get(sessionId), text };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
await this.read(sessionId, view);
|
|
229
|
+
throw new Error(
|
|
230
|
+
options?.text
|
|
231
|
+
? `Timed out waiting for text in PTY session ${sessionId}: ${options.text}`
|
|
232
|
+
: `Timed out waiting for PTY session ${sessionId} to stabilize`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function formatSessionSummary(session: ManagedPtySession): string {
|
|
238
|
+
const bits = [`session ${session.id}`, `pid ${session.pty.handle.pid}`];
|
|
239
|
+
if (session.completed) {
|
|
240
|
+
bits.push(session.cancelled ? "cancelled" : `completed${session.exitCode !== undefined ? ` exit ${session.exitCode}` : ""}`);
|
|
241
|
+
} else {
|
|
242
|
+
bits.push("running");
|
|
243
|
+
}
|
|
244
|
+
return bits.join(" · ");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function buildReadText(session: ManagedPtySession, view: "viewport" | "buffer", text: string): string {
|
|
248
|
+
const header = `[${formatSessionSummary(session)} · ${view}]`;
|
|
249
|
+
return text ? `${header}\n${text}` : `${header}\n(no visible text)`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function createPtyTools(cwd: string): Record<string, AgentTool<any, PtyToolDetails>> {
|
|
253
|
+
const manager = new PtySessionManager(cwd);
|
|
254
|
+
|
|
255
|
+
const ptyStartTool: AgentTool<typeof ptyStartSchema, PtyToolDetails> = {
|
|
256
|
+
name: "pty_start",
|
|
257
|
+
label: "pty_start",
|
|
258
|
+
description: "Start an agent-controlled interactive PTY session. Use this when a command requires terminal interaction, prompts, or a full-screen TUI. Returns a sessionId for follow-up pty_send/pty_read/pty_wait/pty_kill calls.",
|
|
259
|
+
parameters: ptyStartSchema,
|
|
260
|
+
execute: async (_toolCallId, { command, cols, rows, loginShell }) => {
|
|
261
|
+
const session = await manager.start(command, { cols, rows, loginShell });
|
|
262
|
+
const { text } = await manager.read(session.id, "viewport");
|
|
263
|
+
return {
|
|
264
|
+
content: [{ type: "text", text: `Started PTY session ${session.id} for: ${command}\n${buildReadText(session, "viewport", text)}` }],
|
|
265
|
+
details: { sessionId: session.id, pid: session.pty.handle.pid, completed: session.completed, cancelled: session.cancelled, exitCode: session.exitCode, view: "viewport", screenText: text },
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const ptySendTool: AgentTool<typeof ptySendSchema, PtyToolDetails> = {
|
|
271
|
+
name: "pty_send",
|
|
272
|
+
label: "pty_send",
|
|
273
|
+
description: "Send text or control sequences to an existing PTY session, e.g. \"y\\r\" or \"\\u001b[A\".",
|
|
274
|
+
parameters: ptySendSchema,
|
|
275
|
+
execute: async (_toolCallId, { sessionId, input }) => {
|
|
276
|
+
const session = await manager.send(sessionId, input);
|
|
277
|
+
const { text } = await manager.read(sessionId, "viewport");
|
|
278
|
+
return {
|
|
279
|
+
content: [{ type: "text", text: `Sent input to ${sessionId}: ${JSON.stringify(input)}\n${buildReadText(session, "viewport", text)}` }],
|
|
280
|
+
details: { sessionId, pid: session.pty.handle.pid, completed: session.completed, cancelled: session.cancelled, exitCode: session.exitCode, view: "viewport", screenText: text },
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const ptyReadTool: AgentTool<typeof ptyReadSchema, PtyToolDetails> = {
|
|
286
|
+
name: "pty_read",
|
|
287
|
+
label: "pty_read",
|
|
288
|
+
description: "Read the current PTY screen state. Use view='viewport' for the visible screen or view='buffer' for the full logical buffer.",
|
|
289
|
+
parameters: ptyReadSchema,
|
|
290
|
+
execute: async (_toolCallId, { sessionId, view }) => {
|
|
291
|
+
const effectiveView = view ?? "viewport";
|
|
292
|
+
const { session, text } = await manager.read(sessionId, effectiveView);
|
|
293
|
+
return {
|
|
294
|
+
content: [{ type: "text", text: buildReadText(session, effectiveView, text) }],
|
|
295
|
+
details: { sessionId, pid: session.pty.handle.pid, completed: session.completed, cancelled: session.cancelled, exitCode: session.exitCode, view: effectiveView, screenText: text },
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const ptyWaitTool: AgentTool<typeof ptyWaitSchema, PtyToolDetails> = {
|
|
301
|
+
name: "pty_wait",
|
|
302
|
+
label: "pty_wait",
|
|
303
|
+
description: "Wait until text appears in a PTY session or until the PTY screen stops changing. Useful between pty_send calls.",
|
|
304
|
+
parameters: ptyWaitSchema,
|
|
305
|
+
execute: async (_toolCallId, { sessionId, text, view, timeoutMs, stableMs }) => {
|
|
306
|
+
const effectiveView = view ?? "viewport";
|
|
307
|
+
const result = await manager.wait(sessionId, { text, view: effectiveView, timeoutMs, stableMs });
|
|
308
|
+
return {
|
|
309
|
+
content: [{ type: "text", text: buildReadText(result.session, effectiveView, result.text) }],
|
|
310
|
+
details: { sessionId, pid: result.session.pty.handle.pid, completed: result.session.completed, cancelled: result.session.cancelled, exitCode: result.session.exitCode, view: effectiveView, screenText: result.text },
|
|
311
|
+
};
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const ptyResizeTool: AgentTool<typeof ptyResizeSchema, PtyToolDetails> = {
|
|
316
|
+
name: "pty_resize",
|
|
317
|
+
label: "pty_resize",
|
|
318
|
+
description: "Resize an active PTY session to the given terminal dimensions.",
|
|
319
|
+
parameters: ptyResizeSchema,
|
|
320
|
+
execute: async (_toolCallId, { sessionId, cols, rows }) => {
|
|
321
|
+
const session = await manager.resize(sessionId, cols, rows);
|
|
322
|
+
const { text } = await manager.read(sessionId, "viewport");
|
|
323
|
+
return {
|
|
324
|
+
content: [{ type: "text", text: `Resized ${sessionId} to ${cols}x${rows}\n${buildReadText(session, "viewport", text)}` }],
|
|
325
|
+
details: { sessionId, pid: session.pty.handle.pid, completed: session.completed, cancelled: session.cancelled, exitCode: session.exitCode, view: "viewport", screenText: text },
|
|
326
|
+
};
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const ptyKillTool: AgentTool<typeof ptyKillSchema, PtyToolDetails> = {
|
|
331
|
+
name: "pty_kill",
|
|
332
|
+
label: "pty_kill",
|
|
333
|
+
description: "Terminate an agent-controlled PTY session.",
|
|
334
|
+
parameters: ptyKillSchema,
|
|
335
|
+
execute: async (_toolCallId, { sessionId }) => {
|
|
336
|
+
const session = await manager.kill(sessionId);
|
|
337
|
+
return {
|
|
338
|
+
content: [{ type: "text", text: `Terminated PTY session ${sessionId}. ${formatSessionSummary(session)}` }],
|
|
339
|
+
details: { sessionId, pid: session.pty.handle.pid, completed: session.completed, cancelled: session.cancelled, exitCode: session.exitCode },
|
|
340
|
+
};
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
pty_start: ptyStartTool,
|
|
346
|
+
pty_send: ptySendTool,
|
|
347
|
+
pty_read: ptyReadTool,
|
|
348
|
+
pty_wait: ptyWaitTool,
|
|
349
|
+
pty_resize: ptyResizeTool,
|
|
350
|
+
pty_kill: ptyKillTool,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export const ptyToolNames = ["pty_start", "pty_send", "pty_read", "pty_wait", "pty_resize", "pty_kill"] as const;
|
|
@@ -9,6 +9,7 @@ import { formatTimestamp, type TimestampFormat } from "./timestamp.js";
|
|
|
9
9
|
export class AssistantMessageComponent extends Container {
|
|
10
10
|
private contentContainer: Container;
|
|
11
11
|
private hideThinkingBlock: boolean;
|
|
12
|
+
private thinkingLevel: string;
|
|
12
13
|
private markdownTheme: MarkdownTheme;
|
|
13
14
|
private lastMessage?: AssistantMessage;
|
|
14
15
|
private timestampFormat: TimestampFormat;
|
|
@@ -18,10 +19,12 @@ export class AssistantMessageComponent extends Container {
|
|
|
18
19
|
hideThinkingBlock = false,
|
|
19
20
|
markdownTheme: MarkdownTheme = getMarkdownTheme(),
|
|
20
21
|
timestampFormat: TimestampFormat = "date-time-iso",
|
|
22
|
+
thinkingLevel = "off",
|
|
21
23
|
) {
|
|
22
24
|
super();
|
|
23
25
|
|
|
24
26
|
this.hideThinkingBlock = hideThinkingBlock;
|
|
27
|
+
this.thinkingLevel = thinkingLevel;
|
|
25
28
|
this.markdownTheme = markdownTheme;
|
|
26
29
|
this.timestampFormat = timestampFormat;
|
|
27
30
|
|
|
@@ -45,51 +48,61 @@ export class AssistantMessageComponent extends Container {
|
|
|
45
48
|
this.hideThinkingBlock = hide;
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
setThinkingLevel(level: string): void {
|
|
52
|
+
this.thinkingLevel = level;
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
updateContent(message: AssistantMessage): void {
|
|
49
56
|
this.lastMessage = message;
|
|
50
57
|
|
|
51
58
|
// Clear content container
|
|
52
59
|
this.contentContainer.clear();
|
|
53
60
|
|
|
54
|
-
const hasVisibleContent = message.content.some(
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
const hasVisibleContent = message.content.some((c) => {
|
|
62
|
+
if (c.type === "text") return Boolean(c.text.trim());
|
|
63
|
+
if (c.type === "thinking") return !this.hideThinkingBlock && Boolean(c.thinking.trim());
|
|
64
|
+
return false;
|
|
65
|
+
});
|
|
57
66
|
|
|
58
67
|
if (hasVisibleContent) {
|
|
59
68
|
this.contentContainer.addChild(new Spacer(1));
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
// Render content in order
|
|
72
|
+
let markerAdded = false;
|
|
73
|
+
const responseMarker = `${theme.fg("accent", "●")} `;
|
|
63
74
|
for (let i = 0; i < message.content.length; i++) {
|
|
64
75
|
const content = message.content[i];
|
|
65
76
|
if (content.type === "text" && content.text.trim()) {
|
|
66
77
|
// Assistant text messages with no background - trim the text
|
|
67
78
|
// Set paddingY=0 to avoid extra spacing before tool executions
|
|
68
|
-
|
|
79
|
+
const text = content.text.trim();
|
|
80
|
+
const withMarker = markerAdded ? text : `${responseMarker}${text}`;
|
|
81
|
+
this.contentContainer.addChild(new Markdown(withMarker, 1, 0, this.markdownTheme));
|
|
82
|
+
markerAdded = true;
|
|
69
83
|
} else if (content.type === "thinking" && content.thinking.trim()) {
|
|
84
|
+
if (this.hideThinkingBlock) {
|
|
85
|
+
// Hide thinking content entirely when hide-thinking is enabled.
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
// Add spacing only when another visible assistant content block follows.
|
|
71
90
|
// This avoids a superfluous blank line before separately-rendered tool execution blocks.
|
|
72
|
-
const hasVisibleContentAfter = message.content
|
|
73
|
-
.
|
|
74
|
-
|
|
91
|
+
const hasVisibleContentAfter = message.content.slice(i + 1).some((c) => {
|
|
92
|
+
if (c.type === "text") return Boolean(c.text.trim());
|
|
93
|
+
if (c.type === "thinking") return !this.hideThinkingBlock && Boolean(c.thinking.trim());
|
|
94
|
+
return false;
|
|
95
|
+
});
|
|
75
96
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.contentContainer.addChild(
|
|
85
|
-
new Markdown(content.thinking.trim(), 1, 0, this.markdownTheme, {
|
|
86
|
-
color: (text: string) => theme.fg("thinkingText", text),
|
|
87
|
-
italic: true,
|
|
88
|
-
}),
|
|
89
|
-
);
|
|
90
|
-
if (hasVisibleContentAfter) {
|
|
91
|
-
this.contentContainer.addChild(new Spacer(1));
|
|
92
|
-
}
|
|
97
|
+
// Thinking traces in thinkingText color, italic
|
|
98
|
+
this.contentContainer.addChild(
|
|
99
|
+
new Markdown(content.thinking.trim(), 1, 0, this.markdownTheme, {
|
|
100
|
+
color: (text: string) => theme.fg("thinkingText", text),
|
|
101
|
+
italic: true,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
if (hasVisibleContentAfter) {
|
|
105
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
}
|
|
@@ -3,21 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Container, Loader, Text, type TUI } from "@gsd/pi-tui";
|
|
6
|
-
import stripAnsi from "strip-ansi";
|
|
7
6
|
import {
|
|
8
7
|
DEFAULT_MAX_BYTES,
|
|
9
8
|
DEFAULT_MAX_LINES,
|
|
10
9
|
type TruncationResult,
|
|
11
10
|
truncateTail,
|
|
12
11
|
} from "../../../core/tools/truncate.js";
|
|
12
|
+
import { renderTerminalLines } from "../../../utils/terminal-serializer.js";
|
|
13
13
|
import { theme, type ThemeColor } from "../theme/theme.js";
|
|
14
|
-
import { DynamicBorder } from "./dynamic-border.js";
|
|
15
14
|
import { editorKey, keyHint } from "./keybinding-hints.js";
|
|
16
15
|
import { truncateToVisualLines } from "./visual-truncate.js";
|
|
17
16
|
|
|
18
|
-
// Flash interval for RTK badge animation (ms)
|
|
19
|
-
const RTK_FLASH_INTERVAL_MS = 400;
|
|
20
|
-
|
|
21
17
|
// Preview line limit when not expanded (matches tool execution behavior)
|
|
22
18
|
const PREVIEW_LINES = 20;
|
|
23
19
|
|
|
@@ -26,6 +22,7 @@ type ToolOutputMode = "minimal" | "normal";
|
|
|
26
22
|
export class BashExecutionComponent extends Container {
|
|
27
23
|
private command: string;
|
|
28
24
|
private outputLines: string[] = [];
|
|
25
|
+
private rawOutput = "";
|
|
29
26
|
private status: "running" | "complete" | "cancelled" | "error" = "running";
|
|
30
27
|
private exitCode: number | undefined = undefined;
|
|
31
28
|
private loader: Loader;
|
|
@@ -37,10 +34,7 @@ export class BashExecutionComponent extends Container {
|
|
|
37
34
|
private ui: TUI;
|
|
38
35
|
private colorKey: ThemeColor;
|
|
39
36
|
private sandboxed: boolean;
|
|
40
|
-
|
|
41
|
-
private rtkFlashOn = true;
|
|
42
|
-
private rtkFlashTimer: NodeJS.Timeout | null = null;
|
|
43
|
-
// Dedicated header node — updated in-place to avoid full container rebuild on flash tick
|
|
37
|
+
// Dedicated header node
|
|
44
38
|
private headerText: Text;
|
|
45
39
|
|
|
46
40
|
constructor(
|
|
@@ -48,28 +42,23 @@ export class BashExecutionComponent extends Container {
|
|
|
48
42
|
ui: TUI,
|
|
49
43
|
excludeFromContext = false,
|
|
50
44
|
renderMode: ToolOutputMode = "normal",
|
|
51
|
-
|
|
45
|
+
_rtkActive = false,
|
|
52
46
|
sandboxed = false,
|
|
53
47
|
) {
|
|
54
48
|
super();
|
|
55
49
|
this.command = command;
|
|
56
50
|
this.ui = ui;
|
|
57
51
|
this.renderMode = renderMode;
|
|
58
|
-
this.rtkActive = rtkActive;
|
|
59
52
|
this.sandboxed = sandboxed;
|
|
60
53
|
|
|
61
|
-
// Use dim
|
|
54
|
+
// Use dim tone for excluded-from-context commands (!! prefix)
|
|
62
55
|
this.colorKey = (excludeFromContext ? "dim" : "bashMode") as ThemeColor;
|
|
63
|
-
const borderColor = (str: string) => theme.fg(this.colorKey, str);
|
|
64
|
-
|
|
65
|
-
// Top border
|
|
66
|
-
this.addChild(new DynamicBorder(borderColor));
|
|
67
56
|
|
|
68
|
-
// Content container
|
|
57
|
+
// Content container
|
|
69
58
|
this.contentContainer = new Container();
|
|
70
59
|
this.addChild(this.contentContainer);
|
|
71
60
|
|
|
72
|
-
//
|
|
61
|
+
// Header Text node
|
|
73
62
|
this.headerText = new Text(this.buildHeaderText(), 1, 0);
|
|
74
63
|
this.contentContainer.addChild(this.headerText);
|
|
75
64
|
|
|
@@ -81,33 +70,14 @@ export class BashExecutionComponent extends Container {
|
|
|
81
70
|
`Running... (${editorKey("selectCancel")} to cancel)`, // Plain text for loader
|
|
82
71
|
);
|
|
83
72
|
this.contentContainer.addChild(this.loader);
|
|
84
|
-
|
|
85
|
-
// Bottom border
|
|
86
|
-
this.addChild(new DynamicBorder(borderColor));
|
|
87
|
-
|
|
88
|
-
// Start RTK flash animation if active
|
|
89
|
-
if (this.rtkActive) {
|
|
90
|
-
this.rtkFlashTimer = setInterval(() => {
|
|
91
|
-
this.rtkFlashOn = !this.rtkFlashOn;
|
|
92
|
-
// Only update the header node — no full container rebuild
|
|
93
|
-
this.headerText.setText(this.buildHeaderText());
|
|
94
|
-
this.ui.requestRender();
|
|
95
|
-
}, RTK_FLASH_INTERVAL_MS);
|
|
96
|
-
}
|
|
97
73
|
}
|
|
98
74
|
|
|
99
|
-
/** Build the header line text
|
|
75
|
+
/** Build the header line text. */
|
|
100
76
|
private buildHeaderText(): string {
|
|
101
|
-
let text = theme.fg(this.colorKey, theme.bold(`$ ${this.command}`))
|
|
77
|
+
let text = `${theme.fg("toolTitle", theme.bold("bash"))} ${theme.fg(this.colorKey, theme.bold(`$ ${this.command}`))}`;
|
|
102
78
|
if (this.sandboxed) {
|
|
103
79
|
text += ` ${theme.fg("success", "[sandboxed]")}`;
|
|
104
80
|
}
|
|
105
|
-
if (this.rtkActive) {
|
|
106
|
-
const badge = this.rtkFlashOn
|
|
107
|
-
? theme.fg("accent", "$ RTK")
|
|
108
|
-
: theme.fg("dim", "$ RTK");
|
|
109
|
-
text = `${text} ${badge}`;
|
|
110
|
-
}
|
|
111
81
|
return text;
|
|
112
82
|
}
|
|
113
83
|
|
|
@@ -116,10 +86,6 @@ export class BashExecutionComponent extends Container {
|
|
|
116
86
|
* from the tree before setComplete() has been called (e.g. on clear/cancel).
|
|
117
87
|
*/
|
|
118
88
|
dispose(): void {
|
|
119
|
-
if (this.rtkFlashTimer) {
|
|
120
|
-
clearInterval(this.rtkFlashTimer);
|
|
121
|
-
this.rtkFlashTimer = null;
|
|
122
|
-
}
|
|
123
89
|
this.loader.dispose();
|
|
124
90
|
}
|
|
125
91
|
|
|
@@ -144,20 +110,9 @@ export class BashExecutionComponent extends Container {
|
|
|
144
110
|
}
|
|
145
111
|
|
|
146
112
|
appendOutput(chunk: string): void {
|
|
147
|
-
// Strip ANSI codes and
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// Append to output lines
|
|
152
|
-
const newLines = clean.split("\n");
|
|
153
|
-
if (this.outputLines.length > 0 && newLines.length > 0) {
|
|
154
|
-
// Append first chunk to last line (incomplete line continuation)
|
|
155
|
-
this.outputLines[this.outputLines.length - 1] += newLines[0];
|
|
156
|
-
this.outputLines.push(...newLines.slice(1));
|
|
157
|
-
} else {
|
|
158
|
-
this.outputLines.push(...newLines);
|
|
159
|
-
}
|
|
160
|
-
|
|
113
|
+
// Strip ANSI codes and preserve carriage-return semantics for display.
|
|
114
|
+
this.rawOutput += chunk;
|
|
115
|
+
this.outputLines = renderTerminalLines(this.rawOutput);
|
|
161
116
|
this.updateDisplay();
|
|
162
117
|
}
|
|
163
118
|
|
|
@@ -183,15 +138,6 @@ export class BashExecutionComponent extends Container {
|
|
|
183
138
|
// Stop loader
|
|
184
139
|
this.loader.stop();
|
|
185
140
|
|
|
186
|
-
// Stop RTK flash — settle to steady dim state
|
|
187
|
-
if (this.rtkFlashTimer) {
|
|
188
|
-
clearInterval(this.rtkFlashTimer);
|
|
189
|
-
this.rtkFlashTimer = null;
|
|
190
|
-
this.rtkFlashOn = false;
|
|
191
|
-
// Final header update to ensure dim badge is shown
|
|
192
|
-
this.headerText.setText(this.buildHeaderText());
|
|
193
|
-
}
|
|
194
|
-
|
|
195
141
|
this.updateDisplay();
|
|
196
142
|
}
|
|
197
143
|
|
|
@@ -223,7 +169,7 @@ export class BashExecutionComponent extends Container {
|
|
|
223
169
|
const displayText = availableLines.map((line) => theme.fg("muted", line)).join("\n");
|
|
224
170
|
this.contentContainer.addChild(new Text(`\n${displayText}`, 1, 0));
|
|
225
171
|
} else if (this.renderMode === "minimal") {
|
|
226
|
-
|
|
172
|
+
// collapsed — no inline hint needed (shown in editor bottom border)
|
|
227
173
|
} else {
|
|
228
174
|
// Use shared visual truncation utility
|
|
229
175
|
const styledOutput = previewLogicalLines.map((line) => theme.fg("muted", line)).join("\n");
|
|
@@ -243,14 +189,20 @@ export class BashExecutionComponent extends Container {
|
|
|
243
189
|
} else {
|
|
244
190
|
const statusParts: string[] = [];
|
|
245
191
|
|
|
246
|
-
// Show
|
|
247
|
-
if (
|
|
192
|
+
// Show expand/collapse hint whenever there is output
|
|
193
|
+
if (availableLines.length > 0) {
|
|
248
194
|
if (this.expanded) {
|
|
249
195
|
statusParts.push(`(${keyHint("expandTools", "to collapse")})`);
|
|
250
|
-
} else {
|
|
196
|
+
} else if (this.renderMode === "minimal") {
|
|
197
|
+
statusParts.push(`(${keyHint("expandTools", "to expand")})`);
|
|
198
|
+
} else if (hiddenLineCount > 0) {
|
|
199
|
+
// Normal mode: show line count + hint
|
|
251
200
|
statusParts.push(
|
|
252
201
|
`${theme.fg("muted", `... ${hiddenLineCount} more lines`)} (${keyHint("expandTools", "to expand")})`,
|
|
253
202
|
);
|
|
203
|
+
} else {
|
|
204
|
+
// Normal mode: all preview lines visible, still offer expand
|
|
205
|
+
statusParts.push(`(${keyHint("expandTools", "to expand")})`);
|
|
254
206
|
}
|
|
255
207
|
}
|
|
256
208
|
|
|
@@ -46,9 +46,7 @@ export class BranchSummaryMessageComponent extends Box {
|
|
|
46
46
|
} else {
|
|
47
47
|
this.addChild(
|
|
48
48
|
new Text(
|
|
49
|
-
theme.fg("customMessageText", "Branch summary
|
|
50
|
-
theme.fg("dim", editorKey("expandTools")) +
|
|
51
|
-
theme.fg("customMessageText", " to expand)"),
|
|
49
|
+
theme.fg("customMessageText", "Branch summary"),
|
|
52
50
|
0,
|
|
53
51
|
0,
|
|
54
52
|
),
|
package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts
CHANGED
|
@@ -47,9 +47,7 @@ export class CompactionSummaryMessageComponent extends Box {
|
|
|
47
47
|
} else {
|
|
48
48
|
this.addChild(
|
|
49
49
|
new Text(
|
|
50
|
-
theme.fg("customMessageText", `Compacted from ${tokenStr} tokens
|
|
51
|
-
theme.fg("dim", editorKey("expandTools")) +
|
|
52
|
-
theme.fg("customMessageText", " to expand)"),
|
|
50
|
+
theme.fg("customMessageText", `Compacted from ${tokenStr} tokens`),
|
|
53
51
|
0,
|
|
54
52
|
0,
|
|
55
53
|
),
|