@oh-my-pi/pi-coding-agent 13.13.2 → 13.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/package.json +7 -7
- package/src/config/model-registry.ts +10 -3
- package/src/config/settings-schema.ts +3 -0
- package/src/discovery/helpers.ts +9 -2
- package/src/exec/bash-executor.ts +7 -5
- package/src/mcp/client.ts +29 -4
- package/src/mcp/manager.ts +256 -57
- package/src/mcp/tool-bridge.ts +189 -106
- package/src/mcp/transports/http.ts +8 -3
- package/src/modes/components/bash-execution.ts +40 -11
- package/src/modes/components/python-execution.ts +2 -3
- package/src/modes/components/tool-execution.ts +4 -5
- package/src/modes/controllers/command-controller.ts +0 -2
- package/src/modes/controllers/mcp-command-controller.ts +45 -0
- package/src/modes/interactive-mode.ts +9 -5
- package/src/patch/index.ts +15 -7
- package/src/prompts/agents/explore.md +4 -67
- package/src/session/agent-session.ts +7 -16
- package/src/session/streaming-output.ts +87 -37
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/tools/bash-interactive.ts +2 -6
- package/src/tools/python.ts +2 -2
|
@@ -6,7 +6,7 @@ import * as path from "node:path";
|
|
|
6
6
|
import { type Agent, type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { AssistantMessage, ImageContent, Message, Model, UsageReport } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { Component, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
9
|
-
import { Container, Loader, Markdown, ProcessTerminal, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
|
|
9
|
+
import { Container, Loader, Markdown, ProcessTerminal, Spacer, Text, TUI, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
10
10
|
import { APP_NAME, getProjectDir, hsvToRgb, isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import { KeybindingsManager } from "../config/keybindings";
|
|
@@ -1111,8 +1111,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1111
1111
|
this.#startMicAnimation();
|
|
1112
1112
|
} else if (state === "transcribing") {
|
|
1113
1113
|
this.#stopMicAnimation();
|
|
1114
|
-
this
|
|
1115
|
-
this.editor.cursorOverrideWidth = 1;
|
|
1114
|
+
this.#setMicCursor({ r: 200, g: 200, b: 200 });
|
|
1116
1115
|
} else {
|
|
1117
1116
|
this.#cleanupMicAnimation();
|
|
1118
1117
|
}
|
|
@@ -1122,10 +1121,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1122
1121
|
});
|
|
1123
1122
|
}
|
|
1124
1123
|
|
|
1124
|
+
#setMicCursor(color: { r: number; g: number; b: number }): void {
|
|
1125
|
+
this.editor.cursorOverride = `\x1b[38;2;${color.r};${color.g};${color.b}m${theme.icon.mic}\x1b[0m`;
|
|
1126
|
+
// Theme symbols can be wide (for example, 🎤), so measure the rendered override.
|
|
1127
|
+
this.editor.cursorOverrideWidth = visibleWidth(this.editor.cursorOverride);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1125
1130
|
#updateMicIcon(): void {
|
|
1126
1131
|
const { r, g, b } = hsvToRgb({ h: this.#voiceHue, s: 0.9, v: 1.0 });
|
|
1127
|
-
this
|
|
1128
|
-
this.editor.cursorOverrideWidth = 1;
|
|
1132
|
+
this.#setMicCursor({ r, g, b });
|
|
1129
1133
|
}
|
|
1130
1134
|
|
|
1131
1135
|
#startMicAnimation(): void {
|
package/src/patch/index.ts
CHANGED
|
@@ -97,8 +97,12 @@ const patchEditSchema = Type.Object({
|
|
|
97
97
|
export type ReplaceParams = Static<typeof replaceEditSchema>;
|
|
98
98
|
export type PatchParams = Static<typeof patchEditSchema>;
|
|
99
99
|
|
|
100
|
-
/**
|
|
101
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Pattern matching hashline display format prefixes: `LINE#ID:CONTENT`, `#ID:CONTENT`, and `+ID:CONTENT`.
|
|
102
|
+
* A plus-prefixed form appears in diff-like output and should be treated as hashline metadata too.
|
|
103
|
+
*/
|
|
104
|
+
const HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*(?:\+?\s*(?:\d+\s*#\s*|#\s*)|\+)\s*[ZPMQVRWSNKTXJBYH]{2}:/;
|
|
105
|
+
const HASHLINE_PREFIX_PLUS_RE = /^\s*(?:>>>|>>)?\s*\+\s*(?:\d+\s*#\s*|#\s*)?[ZPMQVRWSNKTXJBYH]{2}:/;
|
|
102
106
|
|
|
103
107
|
/** Pattern matching a unified-diff added-line `+` prefix (but not `++`). Does NOT match `-` to avoid corrupting Markdown list items. */
|
|
104
108
|
const DIFF_PLUS_RE = /^[+](?![+])/;
|
|
@@ -111,27 +115,31 @@ const DIFF_PLUS_RE = /^[+](?![+])/;
|
|
|
111
115
|
* output file. This strips them heuristically before application.
|
|
112
116
|
*/
|
|
113
117
|
export function stripNewLinePrefixes(lines: string[]): string[] {
|
|
114
|
-
// Hashline prefixes are highly specific to read output and
|
|
115
|
-
//
|
|
116
|
-
//
|
|
118
|
+
// Hashline prefixes are highly specific to read output and are usually stripped only when
|
|
119
|
+
// *every* non-empty line carries one. If a line is prefixed as `+ID:`, strip that
|
|
120
|
+
// prefix while leaving other `+` lines untouched to avoid corrupting mixed snippets.
|
|
117
121
|
let hashPrefixCount = 0;
|
|
122
|
+
let diffPlusHashPrefixCount = 0;
|
|
118
123
|
let diffPlusCount = 0;
|
|
119
124
|
let nonEmpty = 0;
|
|
120
125
|
for (const l of lines) {
|
|
121
126
|
if (l.length === 0) continue;
|
|
122
127
|
nonEmpty++;
|
|
123
128
|
if (HASHLINE_PREFIX_RE.test(l)) hashPrefixCount++;
|
|
129
|
+
if (HASHLINE_PREFIX_PLUS_RE.test(l)) diffPlusHashPrefixCount++;
|
|
124
130
|
if (DIFF_PLUS_RE.test(l)) diffPlusCount++;
|
|
125
131
|
}
|
|
126
132
|
if (nonEmpty === 0) return lines;
|
|
127
133
|
|
|
128
134
|
const stripHash = hashPrefixCount > 0 && hashPrefixCount === nonEmpty;
|
|
129
|
-
const stripPlus =
|
|
130
|
-
|
|
135
|
+
const stripPlus =
|
|
136
|
+
!stripHash && diffPlusHashPrefixCount === 0 && diffPlusCount > 0 && diffPlusCount >= nonEmpty * 0.5;
|
|
137
|
+
if (!stripHash && !stripPlus && diffPlusHashPrefixCount === 0) return lines;
|
|
131
138
|
|
|
132
139
|
return lines.map(l => {
|
|
133
140
|
if (stripHash) return l.replace(HASHLINE_PREFIX_RE, "");
|
|
134
141
|
if (stripPlus) return l.replace(DIFF_PLUS_RE, "");
|
|
142
|
+
if (diffPlusHashPrefixCount > 0 && HASHLINE_PREFIX_PLUS_RE.test(l)) return l.replace(HASHLINE_PREFIX_RE, "");
|
|
135
143
|
return l;
|
|
136
144
|
});
|
|
137
145
|
}
|
|
@@ -3,7 +3,7 @@ name: explore
|
|
|
3
3
|
description: Fast read-only codebase scout returning compressed context for handoff
|
|
4
4
|
tools: read, grep, find, fetch, web_search
|
|
5
5
|
model: pi/smol
|
|
6
|
-
thinking-level:
|
|
6
|
+
thinking-level: med
|
|
7
7
|
output:
|
|
8
8
|
properties:
|
|
9
9
|
summary:
|
|
@@ -12,84 +12,21 @@ output:
|
|
|
12
12
|
type: string
|
|
13
13
|
files:
|
|
14
14
|
metadata:
|
|
15
|
-
description: Files examined with
|
|
15
|
+
description: Files examined with relevant code references
|
|
16
16
|
elements:
|
|
17
17
|
properties:
|
|
18
|
-
|
|
18
|
+
ref:
|
|
19
19
|
metadata:
|
|
20
|
-
description:
|
|
20
|
+
description: Project-relative path or paths to the most relevant code reference(s), optionally suffixed with line ranges like `:12-34` when relevant
|
|
21
21
|
type: string
|
|
22
|
-
line_start:
|
|
23
|
-
metadata:
|
|
24
|
-
description: First line read (1-indexed)
|
|
25
|
-
type: number
|
|
26
|
-
line_end:
|
|
27
|
-
metadata:
|
|
28
|
-
description: Last line read (1-indexed)
|
|
29
|
-
type: number
|
|
30
22
|
description:
|
|
31
23
|
metadata:
|
|
32
24
|
description: Section contents
|
|
33
25
|
type: string
|
|
34
|
-
code:
|
|
35
|
-
metadata:
|
|
36
|
-
description: Critical types/interfaces/functions extracted verbatim
|
|
37
|
-
elements:
|
|
38
|
-
properties:
|
|
39
|
-
path:
|
|
40
|
-
metadata:
|
|
41
|
-
description: Absolute path to source file
|
|
42
|
-
type: string
|
|
43
|
-
line_start:
|
|
44
|
-
metadata:
|
|
45
|
-
description: Excerpt first line (1-indexed)
|
|
46
|
-
type: number
|
|
47
|
-
line_end:
|
|
48
|
-
metadata:
|
|
49
|
-
description: Excerpt last line (1-indexed)
|
|
50
|
-
type: number
|
|
51
|
-
language:
|
|
52
|
-
metadata:
|
|
53
|
-
description: Language id for syntax highlighting
|
|
54
|
-
type: string
|
|
55
|
-
content:
|
|
56
|
-
metadata:
|
|
57
|
-
description: Verbatim code excerpt
|
|
58
|
-
type: string
|
|
59
26
|
architecture:
|
|
60
27
|
metadata:
|
|
61
28
|
description: Brief explanation of how pieces connect
|
|
62
29
|
type: string
|
|
63
|
-
dependencies:
|
|
64
|
-
metadata:
|
|
65
|
-
description: Key internal and external dependencies relevant to the task
|
|
66
|
-
elements:
|
|
67
|
-
properties:
|
|
68
|
-
name:
|
|
69
|
-
metadata:
|
|
70
|
-
description: Package or module name
|
|
71
|
-
type: string
|
|
72
|
-
role:
|
|
73
|
-
metadata:
|
|
74
|
-
description: What it provides in context of the task
|
|
75
|
-
type: string
|
|
76
|
-
risks:
|
|
77
|
-
metadata:
|
|
78
|
-
description: Gotchas, edge cases, or constraints the receiving agent should know
|
|
79
|
-
elements:
|
|
80
|
-
type: string
|
|
81
|
-
start_here:
|
|
82
|
-
metadata:
|
|
83
|
-
description: Recommended entry point for receiving agent
|
|
84
|
-
properties:
|
|
85
|
-
path:
|
|
86
|
-
metadata:
|
|
87
|
-
description: Absolute path to start reading
|
|
88
|
-
type: string
|
|
89
|
-
reason:
|
|
90
|
-
metadata:
|
|
91
|
-
description: Why this file best starting point
|
|
92
|
-
type: string
|
|
93
30
|
---
|
|
94
31
|
|
|
95
32
|
You are a file search specialist and a codebase scout.
|
|
@@ -3791,33 +3791,24 @@ export class AgentSession {
|
|
|
3791
3791
|
if (calledRequiredTool) {
|
|
3792
3792
|
return;
|
|
3793
3793
|
}
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
const exitPlanModeTool = this.#toolRegistry.get("exit_plan_mode");
|
|
3797
|
-
if (!askTool || !exitPlanModeTool) {
|
|
3794
|
+
const hasRequiredTools = this.#toolRegistry.has("ask") && this.#toolRegistry.has("exit_plan_mode");
|
|
3795
|
+
if (!hasRequiredTools) {
|
|
3798
3796
|
logger.warn("Plan mode enforcement skipped because ask/exit tools are unavailable", {
|
|
3799
3797
|
activeToolNames: this.agent.state.tools.map(tool => tool.name),
|
|
3800
3798
|
});
|
|
3801
3799
|
return;
|
|
3802
3800
|
}
|
|
3803
|
-
const forcedTools = [askTool, exitPlanModeTool];
|
|
3804
3801
|
|
|
3805
3802
|
const reminder = renderPromptTemplate(planModeToolDecisionReminderPrompt, {
|
|
3806
3803
|
askToolName: "ask",
|
|
3807
3804
|
exitToolName: "exit_plan_mode",
|
|
3808
3805
|
});
|
|
3809
3806
|
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
expandPromptTemplates: false,
|
|
3816
|
-
toolChoice: "required",
|
|
3817
|
-
});
|
|
3818
|
-
} finally {
|
|
3819
|
-
this.agent.setTools(previousTools);
|
|
3820
|
-
}
|
|
3807
|
+
await this.prompt(reminder, {
|
|
3808
|
+
synthetic: true,
|
|
3809
|
+
expandPromptTemplates: false,
|
|
3810
|
+
toolChoice: "required",
|
|
3811
|
+
});
|
|
3821
3812
|
}
|
|
3822
3813
|
|
|
3823
3814
|
#createEagerTodoPrelude(): { message: AgentMessage; toolChoice: ToolChoice } | undefined {
|
|
@@ -32,6 +32,8 @@ export interface OutputSinkOptions {
|
|
|
32
32
|
artifactId?: string;
|
|
33
33
|
spillThreshold?: number;
|
|
34
34
|
onChunk?: (chunk: string) => void;
|
|
35
|
+
/** Minimum ms between onChunk calls. 0 = every chunk (default). */
|
|
36
|
+
chunkThrottleMs?: number;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export interface TruncationResult {
|
|
@@ -521,6 +523,7 @@ export class OutputSink {
|
|
|
521
523
|
#totalBytes = 0;
|
|
522
524
|
#sawData = false;
|
|
523
525
|
#truncated = false;
|
|
526
|
+
#lastChunkTime = 0;
|
|
524
527
|
|
|
525
528
|
#file?: {
|
|
526
529
|
path: string;
|
|
@@ -528,22 +531,46 @@ export class OutputSink {
|
|
|
528
531
|
sink: Bun.FileSink;
|
|
529
532
|
};
|
|
530
533
|
|
|
534
|
+
// Queue of chunks waiting for the file sink to be created.
|
|
535
|
+
#pendingFileWrites?: string[];
|
|
536
|
+
#fileReady = false;
|
|
537
|
+
|
|
531
538
|
readonly #artifactPath?: string;
|
|
532
539
|
readonly #artifactId?: string;
|
|
533
540
|
readonly #spillThreshold: number;
|
|
534
541
|
readonly #onChunk?: (chunk: string) => void;
|
|
542
|
+
readonly #chunkThrottleMs: number;
|
|
535
543
|
|
|
536
544
|
constructor(options?: OutputSinkOptions) {
|
|
537
|
-
const {
|
|
545
|
+
const {
|
|
546
|
+
artifactPath,
|
|
547
|
+
artifactId,
|
|
548
|
+
spillThreshold = DEFAULT_MAX_BYTES,
|
|
549
|
+
onChunk,
|
|
550
|
+
chunkThrottleMs = 0,
|
|
551
|
+
} = options ?? {};
|
|
538
552
|
this.#artifactPath = artifactPath;
|
|
539
553
|
this.#artifactId = artifactId;
|
|
540
554
|
this.#spillThreshold = spillThreshold;
|
|
541
555
|
this.#onChunk = onChunk;
|
|
556
|
+
this.#chunkThrottleMs = chunkThrottleMs;
|
|
542
557
|
}
|
|
543
558
|
|
|
544
|
-
|
|
559
|
+
/**
|
|
560
|
+
* Push a chunk of output. The buffer management and onChunk callback run
|
|
561
|
+
* synchronously. File sink writes are deferred and serialized internally.
|
|
562
|
+
*/
|
|
563
|
+
push(chunk: string): void {
|
|
545
564
|
chunk = sanitizeWithOptionalSixelPassthrough(chunk, sanitizeText);
|
|
546
|
-
|
|
565
|
+
|
|
566
|
+
// Throttled onChunk: only call the callback when enough time has passed.
|
|
567
|
+
if (this.#onChunk) {
|
|
568
|
+
const now = Date.now();
|
|
569
|
+
if (now - this.#lastChunkTime >= this.#chunkThrottleMs) {
|
|
570
|
+
this.#lastChunkTime = now;
|
|
571
|
+
this.#onChunk(chunk);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
547
574
|
|
|
548
575
|
const dataBytes = Buffer.byteLength(chunk, "utf-8");
|
|
549
576
|
this.#totalBytes += dataBytes;
|
|
@@ -556,10 +583,9 @@ export class OutputSink {
|
|
|
556
583
|
const threshold = this.#spillThreshold;
|
|
557
584
|
const willOverflow = this.#bufferBytes + dataBytes > threshold;
|
|
558
585
|
|
|
559
|
-
// Write to file if
|
|
560
|
-
if (this.#file != null || willOverflow) {
|
|
561
|
-
|
|
562
|
-
await sink?.write(chunk);
|
|
586
|
+
// Write to artifact file if configured and past the threshold
|
|
587
|
+
if (this.#artifactPath && (this.#file != null || willOverflow)) {
|
|
588
|
+
this.#writeToFile(chunk);
|
|
563
589
|
}
|
|
564
590
|
|
|
565
591
|
if (!willOverflow) {
|
|
@@ -589,14 +615,64 @@ export class OutputSink {
|
|
|
589
615
|
if (this.#file) this.#truncated = true;
|
|
590
616
|
}
|
|
591
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Write a chunk to the artifact file. Handles the async file sink creation
|
|
620
|
+
* by queuing writes until the sink is ready, then draining synchronously.
|
|
621
|
+
*/
|
|
622
|
+
#writeToFile(chunk: string): void {
|
|
623
|
+
if (this.#fileReady && this.#file) {
|
|
624
|
+
// Fast path: file sink exists, write synchronously
|
|
625
|
+
this.#file.sink.write(chunk);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
// File sink not yet created — queue this chunk and kick off creation
|
|
629
|
+
if (!this.#pendingFileWrites) {
|
|
630
|
+
this.#pendingFileWrites = [chunk];
|
|
631
|
+
void this.#createFileSink();
|
|
632
|
+
} else {
|
|
633
|
+
this.#pendingFileWrites.push(chunk);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
async #createFileSink(): Promise<void> {
|
|
638
|
+
if (!this.#artifactPath || this.#fileReady) return;
|
|
639
|
+
try {
|
|
640
|
+
const sink = Bun.file(this.#artifactPath).writer();
|
|
641
|
+
this.#file = { path: this.#artifactPath, artifactId: this.#artifactId, sink };
|
|
642
|
+
|
|
643
|
+
// Flush existing buffer to file BEFORE it gets trimmed further.
|
|
644
|
+
if (this.#buffer.length > 0) {
|
|
645
|
+
sink.write(this.#buffer);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Drain any chunks that arrived while the sink was being created
|
|
649
|
+
if (this.#pendingFileWrites) {
|
|
650
|
+
for (const pending of this.#pendingFileWrites) {
|
|
651
|
+
sink.write(pending);
|
|
652
|
+
}
|
|
653
|
+
this.#pendingFileWrites = undefined;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
this.#fileReady = true;
|
|
657
|
+
} catch {
|
|
658
|
+
try {
|
|
659
|
+
await this.#file?.sink?.end();
|
|
660
|
+
} catch {
|
|
661
|
+
/* ignore */
|
|
662
|
+
}
|
|
663
|
+
this.#file = undefined;
|
|
664
|
+
this.#pendingFileWrites = undefined;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
592
668
|
createInput(): WritableStream<Uint8Array | string> {
|
|
593
669
|
const dec = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
594
|
-
const finalize =
|
|
595
|
-
|
|
670
|
+
const finalize = () => {
|
|
671
|
+
this.push(dec.decode());
|
|
596
672
|
};
|
|
597
673
|
return new WritableStream({
|
|
598
|
-
write:
|
|
599
|
-
|
|
674
|
+
write: chunk => {
|
|
675
|
+
this.push(typeof chunk === "string" ? chunk : dec.decode(chunk, { stream: true }));
|
|
600
676
|
},
|
|
601
677
|
close: finalize,
|
|
602
678
|
abort: finalize,
|
|
@@ -620,32 +696,6 @@ export class OutputSink {
|
|
|
620
696
|
artifactId: this.#file?.artifactId,
|
|
621
697
|
};
|
|
622
698
|
}
|
|
623
|
-
|
|
624
|
-
// -- private ---------------------------------------------------------------
|
|
625
|
-
|
|
626
|
-
async #ensureFileSink(): Promise<Bun.FileSink | null> {
|
|
627
|
-
if (!this.#artifactPath) return null;
|
|
628
|
-
if (this.#file) return this.#file.sink;
|
|
629
|
-
|
|
630
|
-
try {
|
|
631
|
-
const sink = Bun.file(this.#artifactPath).writer();
|
|
632
|
-
this.#file = { path: this.#artifactPath, artifactId: this.#artifactId, sink };
|
|
633
|
-
|
|
634
|
-
// Flush existing buffer to file BEFORE it gets trimmed further.
|
|
635
|
-
if (this.#buffer.length > 0) {
|
|
636
|
-
await sink.write(this.#buffer);
|
|
637
|
-
}
|
|
638
|
-
return sink;
|
|
639
|
-
} catch {
|
|
640
|
-
try {
|
|
641
|
-
await this.#file?.sink?.end();
|
|
642
|
-
} catch {
|
|
643
|
-
/* ignore */
|
|
644
|
-
}
|
|
645
|
-
this.#file = undefined;
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
699
|
}
|
|
650
700
|
|
|
651
701
|
// =============================================================================
|
|
@@ -406,6 +406,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
406
406
|
},
|
|
407
407
|
{ name: "smithery-login", description: "Login to Smithery and cache API key" },
|
|
408
408
|
{ name: "smithery-logout", description: "Remove cached Smithery API key" },
|
|
409
|
+
{ name: "reconnect", description: "Reconnect to a specific MCP server", usage: "<name>" },
|
|
409
410
|
{ name: "reload", description: "Force reload MCP runtime tools" },
|
|
410
411
|
{ name: "resources", description: "List available resources from connected servers" },
|
|
411
412
|
{ name: "prompts", description: "List available prompts from connected servers" },
|
|
@@ -295,7 +295,6 @@ export async function runInteractiveBashPty(
|
|
|
295
295
|
},
|
|
296
296
|
): Promise<BashInteractiveResult> {
|
|
297
297
|
const sink = new OutputSink({ artifactPath: options.artifactPath, artifactId: options.artifactId });
|
|
298
|
-
let pendingChunks = Promise.resolve();
|
|
299
298
|
const result = await ui.custom<BashInteractiveResult>(
|
|
300
299
|
(tui, uiTheme, _keybindings, done) => {
|
|
301
300
|
const session = new PtySession();
|
|
@@ -309,7 +308,6 @@ export async function runInteractiveBashPty(
|
|
|
309
308
|
tui.requestRender();
|
|
310
309
|
void (async () => {
|
|
311
310
|
await component.flushOutput();
|
|
312
|
-
await pendingChunks;
|
|
313
311
|
const summary = await sink.dump();
|
|
314
312
|
done({
|
|
315
313
|
exitCode: run.exitCode,
|
|
@@ -362,15 +360,13 @@ export async function runInteractiveBashPty(
|
|
|
362
360
|
if (finished || err || !chunk) return;
|
|
363
361
|
component.appendOutput(chunk);
|
|
364
362
|
const normalizedChunk = normalizeCaptureChunk(chunk);
|
|
365
|
-
|
|
363
|
+
sink.push(normalizedChunk);
|
|
366
364
|
tui.requestRender();
|
|
367
365
|
},
|
|
368
366
|
)
|
|
369
367
|
.then(finalize)
|
|
370
368
|
.catch(error => {
|
|
371
|
-
|
|
372
|
-
.then(() => sink.push(`PTY error: ${error instanceof Error ? error.message : String(error)}\n`))
|
|
373
|
-
.catch(() => {});
|
|
369
|
+
sink.push(`PTY error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
374
370
|
finalize({ exitCode: undefined, cancelled: false, timedOut: false });
|
|
375
371
|
});
|
|
376
372
|
return component;
|
package/src/tools/python.ts
CHANGED
|
@@ -289,8 +289,8 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
|
|
|
289
289
|
const executorOptions: PythonExecutorOptions = {
|
|
290
290
|
...baseExecutorOptions,
|
|
291
291
|
reset: isFirstCell ? reset : false,
|
|
292
|
-
onChunk:
|
|
293
|
-
|
|
292
|
+
onChunk: chunk => {
|
|
293
|
+
outputSink!.push(chunk);
|
|
294
294
|
},
|
|
295
295
|
};
|
|
296
296
|
|