@oh-my-pi/pi-coding-agent 6.7.670 → 6.8.0
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 +28 -0
- package/package.json +6 -7
- package/src/cli/session-picker.ts +27 -28
- package/src/cli/setup-cli.ts +7 -16
- package/src/cli/update-cli.ts +1 -1
- package/src/config.ts +1 -1
- package/src/core/agent-session.ts +202 -37
- package/src/core/agent-storage.ts +1 -1
- package/src/core/auth-storage.ts +15 -25
- package/src/core/bash-executor.ts +63 -105
- package/src/core/custom-commands/loader.ts +1 -1
- package/src/core/custom-tools/loader.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -2
- package/src/core/exec.ts +16 -100
- package/src/core/extensions/index.ts +1 -7
- package/src/core/extensions/loader.ts +1 -1
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +2 -2
- package/src/core/extensions/wrapper.ts +15 -20
- package/src/core/frontmatter.ts +1 -1
- package/src/core/history-storage.ts +3 -6
- package/src/core/hooks/index.ts +2 -2
- package/src/core/hooks/loader.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +14 -26
- package/src/core/hooks/types.ts +1 -2
- package/src/core/keybindings.ts +1 -1
- package/src/core/mcp/client.ts +13 -13
- package/src/core/mcp/json-rpc.ts +1 -1
- package/src/core/mcp/loader.ts +1 -1
- package/src/core/mcp/manager.ts +2 -2
- package/src/core/mcp/tool-cache.ts +1 -1
- package/src/core/mcp/transports/http.ts +32 -70
- package/src/core/model-registry.ts +1 -1
- package/src/core/plugins/installer.ts +13 -11
- package/src/core/prompt-templates.ts +4 -9
- package/src/core/python-executor.ts +23 -18
- package/src/core/python-gateway-coordinator.ts +29 -28
- package/src/core/python-kernel.ts +230 -211
- package/src/core/sdk.ts +10 -13
- package/src/core/session-manager.ts +1 -1
- package/src/core/settings-manager.ts +22 -9
- package/src/core/skills.ts +1 -1
- package/src/core/ssh/connection-manager.ts +19 -33
- package/src/core/ssh/ssh-executor.ts +39 -35
- package/src/core/ssh/sshfs-mount.ts +14 -33
- package/src/core/storage-migration.ts +1 -1
- package/src/core/streaming-output.ts +183 -127
- package/src/core/system-prompt.ts +119 -79
- package/src/core/title-generator.ts +1 -1
- package/src/core/tools/ask.ts +2 -2
- package/src/core/tools/bash.ts +3 -3
- package/src/core/tools/calculator.ts +1 -1
- package/src/core/tools/exa/mcp-client.ts +1 -1
- package/src/core/tools/exa/render.ts +1 -1
- package/src/core/tools/find.ts +39 -71
- package/src/core/tools/gemini-image.ts +1 -1
- package/src/core/tools/grep.ts +88 -100
- package/src/core/tools/index.ts +1 -1
- package/src/core/tools/ls.ts +1 -1
- package/src/core/tools/lsp/client.ts +50 -50
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +1 -1
- package/src/core/tools/lsp/config.ts +1 -1
- package/src/core/tools/lsp/index.ts +2 -4
- package/src/core/tools/lsp/lspmux.ts +1 -1
- package/src/core/tools/lsp/rust-analyzer.ts +2 -2
- package/src/core/tools/lsp/utils.ts +0 -14
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/patch/shared.ts +3 -4
- package/src/core/tools/python.ts +3 -3
- package/src/core/tools/read.ts +29 -68
- package/src/core/tools/render-utils.ts +0 -5
- package/src/core/tools/ssh.ts +3 -3
- package/src/core/tools/task/model-resolver.ts +7 -9
- package/src/core/tools/task/worker.ts +144 -139
- package/src/core/tools/todo-write.ts +1 -1
- package/src/core/tools/truncate.ts +2 -2
- package/src/core/tools/web-fetch.ts +13 -15
- package/src/core/tools/web-scrapers/types.ts +1 -3
- package/src/core/tools/web-scrapers/utils.ts +14 -13
- package/src/core/tools/web-scrapers/youtube.ts +39 -12
- package/src/core/tools/web-search/auth.ts +1 -1
- package/src/core/tools/write.ts +1 -1
- package/src/core/ttsr.ts +1 -1
- package/src/core/utils.ts +1 -187
- package/src/core/voice-controller.ts +1 -1
- package/src/core/voice-supervisor.ts +11 -38
- package/src/core/voice.ts +1 -8
- package/src/discovery/codex.ts +1 -1
- package/src/index.ts +4 -4
- package/src/main.ts +5 -10
- package/src/migrations.ts +1 -1
- package/src/modes/index.ts +7 -40
- package/src/modes/interactive/components/extensions/state-manager.ts +1 -1
- package/src/modes/interactive/components/hook-editor.ts +12 -9
- package/src/modes/interactive/components/login-dialog.ts +24 -11
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line.ts +36 -35
- package/src/modes/interactive/components/todo-display.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +1 -1
- package/src/modes/interactive/controllers/command-controller.ts +50 -84
- package/src/modes/interactive/controllers/extension-ui-controller.ts +76 -76
- package/src/modes/interactive/controllers/input-controller.ts +12 -11
- package/src/modes/interactive/interactive-mode.ts +10 -11
- package/src/modes/interactive/theme/theme.ts +1 -1
- package/src/modes/interactive/types.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +91 -121
- package/src/modes/rpc/rpc-mode.ts +71 -79
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/utils/clipboard.ts +57 -141
- package/src/utils/shell-snapshot.ts +12 -60
- package/src/utils/shell.ts +35 -56
- package/src/utils/tools-manager.ts +42 -71
- package/src/core/logger.ts +0 -111
- package/src/modes/cleanup.ts +0 -23
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Component, TUI } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
3
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import type {
|
|
4
5
|
ExtensionActions,
|
|
5
6
|
ExtensionCommandContextActions,
|
|
@@ -8,7 +9,6 @@ import type {
|
|
|
8
9
|
ExtensionUIContext,
|
|
9
10
|
} from "../../../core/extensions/index";
|
|
10
11
|
import { KeybindingsManager } from "../../../core/keybindings";
|
|
11
|
-
import { logger } from "../../../core/logger";
|
|
12
12
|
import { setTerminalTitle } from "../../../core/title-generator";
|
|
13
13
|
import { HookEditorComponent } from "../components/hook-editor";
|
|
14
14
|
import { HookInputComponent } from "../components/hook-input";
|
|
@@ -483,26 +483,26 @@ export class ExtensionUiController {
|
|
|
483
483
|
* Show a selector for hooks.
|
|
484
484
|
*/
|
|
485
485
|
showHookSelector(title: string, options: string[], initialIndex?: number): Promise<string | undefined> {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
486
|
+
const { promise, resolve } = Promise.withResolvers<string | undefined>();
|
|
487
|
+
this.ctx.hookSelector = new HookSelectorComponent(
|
|
488
|
+
title,
|
|
489
|
+
options,
|
|
490
|
+
(option) => {
|
|
491
|
+
this.hideHookSelector();
|
|
492
|
+
resolve(option);
|
|
493
|
+
},
|
|
494
|
+
() => {
|
|
495
|
+
this.hideHookSelector();
|
|
496
|
+
resolve(undefined);
|
|
497
|
+
},
|
|
498
|
+
{ initialIndex },
|
|
499
|
+
);
|
|
500
500
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
501
|
+
this.ctx.editorContainer.clear();
|
|
502
|
+
this.ctx.editorContainer.addChild(this.ctx.hookSelector);
|
|
503
|
+
this.ctx.ui.setFocus(this.ctx.hookSelector);
|
|
504
|
+
this.ctx.ui.requestRender();
|
|
505
|
+
return promise;
|
|
506
506
|
}
|
|
507
507
|
|
|
508
508
|
/**
|
|
@@ -528,25 +528,25 @@ export class ExtensionUiController {
|
|
|
528
528
|
* Show a text input for hooks.
|
|
529
529
|
*/
|
|
530
530
|
showHookInput(title: string, placeholder?: string): Promise<string | undefined> {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
531
|
+
const { promise, resolve } = Promise.withResolvers<string | undefined>();
|
|
532
|
+
this.ctx.hookInput = new HookInputComponent(
|
|
533
|
+
title,
|
|
534
|
+
placeholder,
|
|
535
|
+
(value) => {
|
|
536
|
+
this.hideHookInput();
|
|
537
|
+
resolve(value);
|
|
538
|
+
},
|
|
539
|
+
() => {
|
|
540
|
+
this.hideHookInput();
|
|
541
|
+
resolve(undefined);
|
|
542
|
+
},
|
|
543
|
+
);
|
|
544
544
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
545
|
+
this.ctx.editorContainer.clear();
|
|
546
|
+
this.ctx.editorContainer.addChild(this.ctx.hookInput);
|
|
547
|
+
this.ctx.ui.setFocus(this.ctx.hookInput);
|
|
548
|
+
this.ctx.ui.requestRender();
|
|
549
|
+
return promise;
|
|
550
550
|
}
|
|
551
551
|
|
|
552
552
|
/**
|
|
@@ -564,26 +564,26 @@ export class ExtensionUiController {
|
|
|
564
564
|
* Show a multi-line editor for hooks (with Ctrl+G support).
|
|
565
565
|
*/
|
|
566
566
|
showHookEditor(title: string, prefill?: string): Promise<string | undefined> {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
567
|
+
const { promise, resolve } = Promise.withResolvers<string | undefined>();
|
|
568
|
+
this.ctx.hookEditor = new HookEditorComponent(
|
|
569
|
+
this.ctx.ui,
|
|
570
|
+
title,
|
|
571
|
+
prefill,
|
|
572
|
+
(value) => {
|
|
573
|
+
this.hideHookEditor();
|
|
574
|
+
resolve(value);
|
|
575
|
+
},
|
|
576
|
+
() => {
|
|
577
|
+
this.hideHookEditor();
|
|
578
|
+
resolve(undefined);
|
|
579
|
+
},
|
|
580
|
+
);
|
|
581
581
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
582
|
+
this.ctx.editorContainer.clear();
|
|
583
|
+
this.ctx.editorContainer.addChild(this.ctx.hookEditor);
|
|
584
|
+
this.ctx.ui.setFocus(this.ctx.hookEditor);
|
|
585
|
+
this.ctx.ui.requestRender();
|
|
586
|
+
return promise;
|
|
587
587
|
}
|
|
588
588
|
|
|
589
589
|
/**
|
|
@@ -624,27 +624,27 @@ export class ExtensionUiController {
|
|
|
624
624
|
const savedText = this.ctx.editor.getText();
|
|
625
625
|
const keybindings = KeybindingsManager.inMemory();
|
|
626
626
|
|
|
627
|
-
|
|
628
|
-
|
|
627
|
+
const { promise, resolve } = Promise.withResolvers<T>();
|
|
628
|
+
let component: Component & { dispose?(): void };
|
|
629
629
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
});
|
|
630
|
+
const close = (result: T) => {
|
|
631
|
+
component.dispose?.();
|
|
632
|
+
this.ctx.editorContainer.clear();
|
|
633
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
634
|
+
this.ctx.editor.setText(savedText);
|
|
635
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
636
|
+
this.ctx.ui.requestRender();
|
|
637
|
+
resolve(result);
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
Promise.try(() => factory(this.ctx.ui, theme, keybindings, close)).then((c) => {
|
|
641
|
+
component = c;
|
|
642
|
+
this.ctx.editorContainer.clear();
|
|
643
|
+
this.ctx.editorContainer.addChild(component);
|
|
644
|
+
this.ctx.ui.setFocus(component);
|
|
645
|
+
this.ctx.ui.requestRender();
|
|
647
646
|
});
|
|
647
|
+
return promise;
|
|
648
648
|
}
|
|
649
649
|
|
|
650
650
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
@@ -56,11 +56,11 @@ export class InputController {
|
|
|
56
56
|
this.ctx.editor.onAltP = () => this.ctx.showModelSelector({ temporaryOnly: true });
|
|
57
57
|
|
|
58
58
|
// Global debug handler on TUI (works regardless of focus)
|
|
59
|
-
this.ctx.ui.onDebug = () => this.ctx.handleDebugCommand();
|
|
59
|
+
this.ctx.ui.onDebug = () => void this.ctx.handleDebugCommand();
|
|
60
60
|
this.ctx.editor.onCtrlL = () => this.ctx.showModelSelector();
|
|
61
61
|
this.ctx.editor.onCtrlR = () => this.ctx.showHistorySearch();
|
|
62
62
|
this.ctx.editor.onCtrlT = () => this.ctx.toggleTodoExpansion();
|
|
63
|
-
this.ctx.editor.onCtrlG = () => this.openExternalEditor();
|
|
63
|
+
this.ctx.editor.onCtrlG = () => void this.openExternalEditor();
|
|
64
64
|
this.ctx.editor.onQuestionMark = () => this.ctx.handleHotkeysCommand();
|
|
65
65
|
this.ctx.editor.onCtrlV = () => this.handleImagePaste();
|
|
66
66
|
|
|
@@ -246,7 +246,7 @@ export class InputController {
|
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
248
|
if (text === "/debug") {
|
|
249
|
-
this.ctx.handleDebugCommand();
|
|
249
|
+
void this.ctx.handleDebugCommand();
|
|
250
250
|
this.ctx.editor.setText("");
|
|
251
251
|
return;
|
|
252
252
|
}
|
|
@@ -276,7 +276,7 @@ export class InputController {
|
|
|
276
276
|
this.ctx.editor.addToHistory(text);
|
|
277
277
|
this.ctx.editor.setText("");
|
|
278
278
|
try {
|
|
279
|
-
const content =
|
|
279
|
+
const content = await Bun.file(skillPath).text();
|
|
280
280
|
const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
|
|
281
281
|
const metaLines = [`Skill: ${skillPath}`];
|
|
282
282
|
if (args) {
|
|
@@ -587,7 +587,7 @@ export class InputController {
|
|
|
587
587
|
this.ctx.showStatus(`Thinking blocks: ${this.ctx.hideThinkingBlock ? "hidden" : "visible"}`);
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
-
openExternalEditor(): void {
|
|
590
|
+
async openExternalEditor(): Promise<void> {
|
|
591
591
|
// Determine editor (respect $VISUAL, then $EDITOR)
|
|
592
592
|
const editorCmd = process.env.VISUAL || process.env.EDITOR;
|
|
593
593
|
if (!editorCmd) {
|
|
@@ -600,7 +600,7 @@ export class InputController {
|
|
|
600
600
|
|
|
601
601
|
try {
|
|
602
602
|
// Write current content to temp file
|
|
603
|
-
|
|
603
|
+
await Bun.write(tmpFile, currentText);
|
|
604
604
|
|
|
605
605
|
// Stop TUI to release terminal
|
|
606
606
|
this.ctx.ui.stop();
|
|
@@ -609,22 +609,23 @@ export class InputController {
|
|
|
609
609
|
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
610
610
|
|
|
611
611
|
// Spawn editor synchronously with inherited stdio for interactive editing
|
|
612
|
-
const
|
|
612
|
+
const child = Bun.spawn([editor, ...editorArgs, tmpFile], {
|
|
613
613
|
stdin: "inherit",
|
|
614
614
|
stdout: "inherit",
|
|
615
615
|
stderr: "inherit",
|
|
616
616
|
});
|
|
617
|
+
const exitCode = await child.exited;
|
|
617
618
|
|
|
618
619
|
// On successful exit (exitCode 0), replace editor content
|
|
619
|
-
if (
|
|
620
|
-
const newContent =
|
|
620
|
+
if (exitCode === 0) {
|
|
621
|
+
const newContent = (await Bun.file(tmpFile).text()).replace(/\n$/, "");
|
|
621
622
|
this.ctx.editor.setText(newContent);
|
|
622
623
|
}
|
|
623
624
|
// On non-zero exit, keep original text (no action needed)
|
|
624
625
|
} finally {
|
|
625
626
|
// Clean up temp file
|
|
626
627
|
try {
|
|
627
|
-
|
|
628
|
+
await rm(tmpFile, { force: true });
|
|
628
629
|
} catch {
|
|
629
630
|
// Ignore cleanup errors
|
|
630
631
|
}
|
|
@@ -16,13 +16,13 @@ import {
|
|
|
16
16
|
Text,
|
|
17
17
|
TUI,
|
|
18
18
|
} from "@oh-my-pi/pi-tui";
|
|
19
|
+
import { logger, postmortem } from "@oh-my-pi/pi-utils";
|
|
19
20
|
import chalk from "chalk";
|
|
20
21
|
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session";
|
|
21
22
|
import type { ExtensionUIContext } from "../../core/extensions/index";
|
|
22
23
|
import type { CompactOptions } from "../../core/extensions/types";
|
|
23
24
|
import { HistoryStorage } from "../../core/history-storage";
|
|
24
25
|
import { KeybindingsManager } from "../../core/keybindings";
|
|
25
|
-
import { logger } from "../../core/logger";
|
|
26
26
|
import type { SessionContext, SessionManager } from "../../core/session-manager";
|
|
27
27
|
import { getRecentSessions } from "../../core/session-manager";
|
|
28
28
|
import type { SettingsManager } from "../../core/settings-manager";
|
|
@@ -30,7 +30,6 @@ import { loadSlashCommands } from "../../core/slash-commands";
|
|
|
30
30
|
import { setTerminalTitle } from "../../core/title-generator";
|
|
31
31
|
import { getArtifactsDir } from "../../core/tools/task/artifacts";
|
|
32
32
|
import { VoiceSupervisor } from "../../core/voice-supervisor";
|
|
33
|
-
import { registerAsyncCleanup } from "../cleanup";
|
|
34
33
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
35
34
|
import type { BashExecutionComponent } from "./components/bash-execution";
|
|
36
35
|
import { CustomEditor } from "./components/custom-editor";
|
|
@@ -268,7 +267,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
268
267
|
this.keybindings = await KeybindingsManager.create();
|
|
269
268
|
|
|
270
269
|
// Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
|
|
271
|
-
this.cleanupUnsubscribe =
|
|
270
|
+
this.cleanupUnsubscribe = postmortem.register("session-manager-flush", () => this.sessionManager.flush());
|
|
272
271
|
|
|
273
272
|
// Load and convert file commands to SlashCommand format (async)
|
|
274
273
|
const fileCommands = await loadSlashCommands({ cwd: process.cwd() });
|
|
@@ -394,12 +393,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
394
393
|
}
|
|
395
394
|
|
|
396
395
|
async getUserInput(): Promise<{ text: string; images?: ImageContent[] }> {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
396
|
+
const { promise, resolve } = Promise.withResolvers<{ text: string; images?: ImageContent[] }>();
|
|
397
|
+
this.onInputCallback = (input) => {
|
|
398
|
+
this.onInputCallback = undefined;
|
|
399
|
+
resolve(input);
|
|
400
|
+
};
|
|
401
|
+
return promise;
|
|
403
402
|
}
|
|
404
403
|
|
|
405
404
|
updateEditorBorderColor(): void {
|
|
@@ -664,8 +663,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
664
663
|
return this.commandController.handleClearCommand();
|
|
665
664
|
}
|
|
666
665
|
|
|
667
|
-
handleDebugCommand(): void {
|
|
668
|
-
this.commandController.handleDebugCommand();
|
|
666
|
+
handleDebugCommand(): Promise<void> {
|
|
667
|
+
return this.commandController.handleDebugCommand();
|
|
669
668
|
}
|
|
670
669
|
|
|
671
670
|
handleArminSaysHi(): void {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { EditorTheme, MarkdownTheme, SelectListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { type Static, Type } from "@sinclair/typebox";
|
|
5
6
|
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
6
7
|
import chalk from "chalk";
|
|
7
8
|
import { highlight, supportsLanguage } from "cli-highlight";
|
|
8
9
|
import { getCustomThemesDir } from "../../../config";
|
|
9
|
-
import { logger } from "../../../core/logger";
|
|
10
10
|
// Embed theme JSON files at build time
|
|
11
11
|
import darkThemeJson from "./dark.json" with { type: "json" };
|
|
12
12
|
import { defaultThemes } from "./defaults";
|
|
@@ -141,7 +141,7 @@ export interface InteractiveModeContext {
|
|
|
141
141
|
handleHotkeysCommand(): void;
|
|
142
142
|
handleDumpCommand(): Promise<void>;
|
|
143
143
|
handleClearCommand(): Promise<void>;
|
|
144
|
-
handleDebugCommand(): void
|
|
144
|
+
handleDebugCommand(): Promise<void>;
|
|
145
145
|
handleArminSaysHi(): void;
|
|
146
146
|
handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
|
|
147
147
|
handleCompactCommand(customInstructions?: string): Promise<void>;
|
|
@@ -6,16 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
9
|
-
import
|
|
9
|
+
import { createSanitizerStream, createSplitterStream, createTextDecoderStream, ptree } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import type { SessionStats } from "../../core/agent-session";
|
|
11
11
|
import type { BashResult } from "../../core/bash-executor";
|
|
12
12
|
import type { CompactionResult } from "../../core/compaction/index";
|
|
13
13
|
import type { RpcCommand, RpcResponse, RpcSessionState } from "./rpc-types";
|
|
14
14
|
|
|
15
|
-
// ============================================================================
|
|
16
|
-
// Types
|
|
17
|
-
// ============================================================================
|
|
18
|
-
|
|
19
15
|
/** Distributive Omit that works with union types */
|
|
20
16
|
type DistributiveOmit<T, K extends keyof T> = T extends unknown ? Omit<T, K> : never;
|
|
21
17
|
|
|
@@ -51,13 +47,12 @@ export type RpcEventListener = (event: AgentEvent) => void;
|
|
|
51
47
|
// ============================================================================
|
|
52
48
|
|
|
53
49
|
export class RpcClient {
|
|
54
|
-
private process:
|
|
55
|
-
private lineReader:
|
|
50
|
+
private process: ptree.ChildProcess | null = null;
|
|
51
|
+
private lineReader: ReadableStream<string> | null = null;
|
|
56
52
|
private eventListeners: RpcEventListener[] = [];
|
|
57
53
|
private pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =
|
|
58
54
|
new Map();
|
|
59
55
|
private requestId = 0;
|
|
60
|
-
private stderr = "";
|
|
61
56
|
|
|
62
57
|
constructor(private options: RpcClientOptions = {}) {}
|
|
63
58
|
|
|
@@ -82,65 +77,39 @@ export class RpcClient {
|
|
|
82
77
|
args.push(...this.options.args);
|
|
83
78
|
}
|
|
84
79
|
|
|
85
|
-
this.process =
|
|
80
|
+
this.process = ptree.cspawn(["bun", cliPath, ...args], {
|
|
86
81
|
cwd: this.options.cwd,
|
|
87
82
|
env: { ...process.env, ...this.options.env },
|
|
88
83
|
stdin: "pipe",
|
|
89
|
-
stdout: "pipe",
|
|
90
|
-
stderr: "pipe",
|
|
91
84
|
});
|
|
92
85
|
|
|
93
|
-
// Collect stderr for debugging
|
|
94
|
-
(async () => {
|
|
95
|
-
const reader = (this.process!.stderr as ReadableStream<Uint8Array>).getReader();
|
|
96
|
-
const decoder = new TextDecoder();
|
|
97
|
-
while (true) {
|
|
98
|
-
const { done, value } = await reader.read();
|
|
99
|
-
if (done) break;
|
|
100
|
-
this.stderr += decoder.decode(value);
|
|
101
|
-
}
|
|
102
|
-
})();
|
|
103
|
-
|
|
104
|
-
// Set up line reader for stdout
|
|
105
|
-
const textStream = (this.process.stdout as ReadableStream<Uint8Array>).pipeThrough(new TextDecoderStream());
|
|
106
|
-
this.lineReader = textStream
|
|
107
|
-
.pipeThrough(
|
|
108
|
-
new TransformStream<string, string>({
|
|
109
|
-
transform(chunk, controller) {
|
|
110
|
-
const lines = chunk.split("\n");
|
|
111
|
-
for (const line of lines) {
|
|
112
|
-
if (line.trim()) {
|
|
113
|
-
controller.enqueue(line);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
}),
|
|
118
|
-
)
|
|
119
|
-
.getReader() as ReadableStreamDefaultReader<string>;
|
|
120
|
-
|
|
121
86
|
// Process lines in background
|
|
122
|
-
|
|
87
|
+
const lines = this.process.stdout
|
|
88
|
+
.pipeThrough(createTextDecoderStream())
|
|
89
|
+
.pipeThrough(createSanitizerStream())
|
|
90
|
+
.pipeThrough(createSplitterStream("\n"));
|
|
91
|
+
this.lineReader = lines;
|
|
92
|
+
void (async () => {
|
|
123
93
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (done) break;
|
|
127
|
-
this.handleLine(value);
|
|
94
|
+
for await (const line of lines) {
|
|
95
|
+
this.handleLine(line);
|
|
128
96
|
}
|
|
129
97
|
} catch {
|
|
130
98
|
// Stream closed
|
|
99
|
+
} finally {
|
|
100
|
+
lines.cancel();
|
|
131
101
|
}
|
|
132
102
|
})();
|
|
133
103
|
|
|
134
104
|
// Wait a moment for process to initialize
|
|
135
|
-
await
|
|
105
|
+
await Bun.sleep(100);
|
|
136
106
|
|
|
137
107
|
try {
|
|
138
|
-
const exitCode = await Promise.race([
|
|
139
|
-
this.process.exited,
|
|
140
|
-
new Promise<null>((resolve) => setTimeout(() => resolve(null), 50)),
|
|
141
|
-
]);
|
|
108
|
+
const exitCode = await Promise.race([this.process.exited, Bun.sleep(50).then(() => null)]);
|
|
142
109
|
if (exitCode !== null) {
|
|
143
|
-
throw new Error(
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Agent process exited immediately with code ${exitCode}. Stderr: ${this.process.peekStderr()}`,
|
|
112
|
+
);
|
|
144
113
|
}
|
|
145
114
|
} catch {
|
|
146
115
|
// Process still running, which is what we want
|
|
@@ -154,22 +123,7 @@ export class RpcClient {
|
|
|
154
123
|
if (!this.process) return;
|
|
155
124
|
|
|
156
125
|
this.lineReader?.cancel();
|
|
157
|
-
this.process.
|
|
158
|
-
|
|
159
|
-
// Wait for process to exit
|
|
160
|
-
await Promise.race([
|
|
161
|
-
this.process.exited,
|
|
162
|
-
new Promise<void>((resolve) => {
|
|
163
|
-
setTimeout(() => {
|
|
164
|
-
try {
|
|
165
|
-
this.process?.kill(9);
|
|
166
|
-
} catch {
|
|
167
|
-
// Already dead
|
|
168
|
-
}
|
|
169
|
-
resolve();
|
|
170
|
-
}, 1000);
|
|
171
|
-
}),
|
|
172
|
-
]);
|
|
126
|
+
await this.process.killAndWait();
|
|
173
127
|
|
|
174
128
|
this.process = null;
|
|
175
129
|
this.lineReader = null;
|
|
@@ -193,7 +147,7 @@ export class RpcClient {
|
|
|
193
147
|
* Get collected stderr output (useful for debugging).
|
|
194
148
|
*/
|
|
195
149
|
getStderr(): string {
|
|
196
|
-
return this.
|
|
150
|
+
return this.process?.peekStderr() ?? "";
|
|
197
151
|
}
|
|
198
152
|
|
|
199
153
|
// =========================================================================
|
|
@@ -416,42 +370,50 @@ export class RpcClient {
|
|
|
416
370
|
* Resolves when agent_end event is received.
|
|
417
371
|
*/
|
|
418
372
|
waitForIdle(timeout = 60000): Promise<void> {
|
|
419
|
-
|
|
420
|
-
|
|
373
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
374
|
+
let settled = false;
|
|
375
|
+
const unsubscribe = this.onEvent((event) => {
|
|
376
|
+
if (event.type === "agent_end") {
|
|
377
|
+
settled = true;
|
|
421
378
|
unsubscribe();
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const unsubscribe = this.onEvent((event) => {
|
|
426
|
-
if (event.type === "agent_end") {
|
|
427
|
-
clearTimeout(timer);
|
|
428
|
-
unsubscribe();
|
|
429
|
-
resolve();
|
|
430
|
-
}
|
|
431
|
-
});
|
|
379
|
+
resolve();
|
|
380
|
+
}
|
|
432
381
|
});
|
|
382
|
+
|
|
383
|
+
void (async () => {
|
|
384
|
+
await Bun.sleep(timeout);
|
|
385
|
+
if (settled) return;
|
|
386
|
+
settled = true;
|
|
387
|
+
unsubscribe();
|
|
388
|
+
reject(new Error(`Timeout waiting for agent to become idle. Stderr: ${this.process?.peekStderr() ?? ""}`));
|
|
389
|
+
})();
|
|
390
|
+
return promise;
|
|
433
391
|
}
|
|
434
392
|
|
|
435
393
|
/**
|
|
436
394
|
* Collect events until agent becomes idle.
|
|
437
395
|
*/
|
|
438
396
|
collectEvents(timeout = 60000): Promise<AgentEvent[]> {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
397
|
+
const { promise, resolve, reject } = Promise.withResolvers<AgentEvent[]>();
|
|
398
|
+
const events: AgentEvent[] = [];
|
|
399
|
+
let settled = false;
|
|
400
|
+
const unsubscribe = this.onEvent((event) => {
|
|
401
|
+
events.push(event);
|
|
402
|
+
if (event.type === "agent_end") {
|
|
403
|
+
settled = true;
|
|
442
404
|
unsubscribe();
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const unsubscribe = this.onEvent((event) => {
|
|
447
|
-
events.push(event);
|
|
448
|
-
if (event.type === "agent_end") {
|
|
449
|
-
clearTimeout(timer);
|
|
450
|
-
unsubscribe();
|
|
451
|
-
resolve(events);
|
|
452
|
-
}
|
|
453
|
-
});
|
|
405
|
+
resolve(events);
|
|
406
|
+
}
|
|
454
407
|
});
|
|
408
|
+
|
|
409
|
+
void (async () => {
|
|
410
|
+
await Bun.sleep(timeout);
|
|
411
|
+
if (settled) return;
|
|
412
|
+
settled = true;
|
|
413
|
+
unsubscribe();
|
|
414
|
+
reject(new Error(`Timeout collecting events. Stderr: ${this.process?.peekStderr() ?? ""}`));
|
|
415
|
+
})();
|
|
416
|
+
return promise;
|
|
455
417
|
}
|
|
456
418
|
|
|
457
419
|
/**
|
|
@@ -495,37 +457,45 @@ export class RpcClient {
|
|
|
495
457
|
|
|
496
458
|
const id = `req_${++this.requestId}`;
|
|
497
459
|
const fullCommand = { ...command, id } as RpcCommand;
|
|
460
|
+
const { promise, resolve, reject } = Promise.withResolvers<RpcResponse>();
|
|
461
|
+
let settled = false;
|
|
462
|
+
void (async () => {
|
|
463
|
+
await Bun.sleep(30000);
|
|
464
|
+
if (settled) return;
|
|
465
|
+
this.pendingRequests.delete(id);
|
|
466
|
+
settled = true;
|
|
467
|
+
reject(
|
|
468
|
+
new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.process?.peekStderr() ?? ""}`),
|
|
469
|
+
);
|
|
470
|
+
})();
|
|
498
471
|
|
|
499
|
-
|
|
500
|
-
|
|
472
|
+
this.pendingRequests.set(id, {
|
|
473
|
+
resolve: (response) => {
|
|
474
|
+
if (settled) return;
|
|
475
|
+
settled = true;
|
|
476
|
+
resolve(response);
|
|
477
|
+
},
|
|
478
|
+
reject: (error) => {
|
|
479
|
+
if (settled) return;
|
|
480
|
+
settled = true;
|
|
481
|
+
reject(error);
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Write to stdin after registering the handler
|
|
486
|
+
const stdin = this.process!.stdin as import("bun").FileSink;
|
|
487
|
+
stdin.write(new TextEncoder().encode(`${JSON.stringify(fullCommand)}\n`));
|
|
488
|
+
// flush() returns number | Promise<number> - handle both cases
|
|
489
|
+
const flushResult = stdin.flush();
|
|
490
|
+
if (flushResult instanceof Promise) {
|
|
491
|
+
flushResult.catch((err: Error) => {
|
|
501
492
|
this.pendingRequests.delete(id);
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
this.pendingRequests.set(id, {
|
|
506
|
-
resolve: (response) => {
|
|
507
|
-
clearTimeout(timeout);
|
|
508
|
-
resolve(response);
|
|
509
|
-
},
|
|
510
|
-
reject: (error) => {
|
|
511
|
-
clearTimeout(timeout);
|
|
512
|
-
reject(error);
|
|
513
|
-
},
|
|
493
|
+
if (settled) return;
|
|
494
|
+
settled = true;
|
|
495
|
+
reject(err);
|
|
514
496
|
});
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const stdin = this.process!.stdin as import("bun").FileSink;
|
|
518
|
-
stdin.write(new TextEncoder().encode(`${JSON.stringify(fullCommand)}\n`));
|
|
519
|
-
// flush() returns number | Promise<number> - handle both cases
|
|
520
|
-
const flushResult = stdin.flush();
|
|
521
|
-
if (flushResult instanceof Promise) {
|
|
522
|
-
flushResult.catch((err: Error) => {
|
|
523
|
-
this.pendingRequests.delete(id);
|
|
524
|
-
clearTimeout(timeout);
|
|
525
|
-
reject(err);
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
});
|
|
497
|
+
}
|
|
498
|
+
return promise;
|
|
529
499
|
}
|
|
530
500
|
|
|
531
501
|
private getData<T>(response: RpcResponse): T {
|