agent-sh 0.12.26 → 0.13.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/README.md +13 -2
- package/dist/agent/agent-loop.d.ts +3 -5
- package/dist/agent/agent-loop.js +44 -100
- package/dist/agent/conversation-state.d.ts +9 -0
- package/dist/agent/conversation-state.js +38 -1
- package/dist/agent/history-file.d.ts +6 -0
- package/dist/agent/history-file.js +1 -1
- package/dist/agent/host-types.d.ts +125 -0
- package/dist/agent/index.d.ts +12 -4
- package/dist/agent/index.js +357 -6
- package/dist/agent/nuclear-form.d.ts +7 -0
- package/dist/{extensions → agent}/providers/deepseek.d.ts +2 -2
- package/dist/{extensions → agent}/providers/deepseek.js +5 -4
- package/dist/{extensions → agent}/providers/openai-compatible.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openai.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openai.js +3 -2
- package/dist/{extensions → agent}/providers/openrouter.d.ts +2 -2
- package/dist/{extensions → agent}/providers/openrouter.js +4 -3
- package/dist/agent/skills.js +51 -7
- package/dist/agent/subagent.d.ts +1 -1
- package/dist/agent/system-prompt.js +14 -17
- package/dist/agent/tool-protocol.d.ts +1 -1
- package/dist/agent/tool-protocol.js +5 -3
- package/dist/agent/tool-registry.d.ts +9 -4
- package/dist/agent/tool-registry.js +27 -4
- package/dist/agent/tools/bash.d.ts +1 -1
- package/dist/agent/tools/bash.js +3 -2
- package/dist/agent/tools/edit-file.js +0 -1
- package/dist/agent/tools/glob.js +1 -1
- package/dist/agent/tools/grep.js +1 -1
- package/dist/agent/tools/pwsh.d.ts +1 -1
- package/dist/agent/tools/pwsh.js +1 -2
- package/dist/agent/tools/read-file.js +7 -4
- package/dist/agent/tools/write-file.js +0 -1
- package/dist/agent/types.d.ts +17 -2
- package/dist/cli/auth/cli.d.ts +1 -0
- package/dist/cli/auth/cli.js +216 -0
- package/dist/cli/auth/keys.d.ts +31 -0
- package/dist/cli/auth/keys.js +102 -0
- package/dist/{index.js → cli/index.js} +29 -32
- package/dist/{init.js → cli/init.js} +1 -1
- package/dist/{install.js → cli/install.js} +114 -5
- package/dist/cli/subcommands.d.ts +1 -0
- package/dist/cli/subcommands.js +17 -0
- package/dist/{event-bus.d.ts → core/event-bus.d.ts} +7 -13
- package/dist/{extension-loader.d.ts → core/extension-loader.d.ts} +1 -1
- package/dist/{extension-loader.js → core/extension-loader.js} +62 -70
- package/dist/{core.d.ts → core/index.d.ts} +18 -15
- package/dist/{core.js → core/index.js} +18 -92
- package/dist/{settings.d.ts → core/settings.d.ts} +7 -0
- package/dist/{settings.js → core/settings.js} +1 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/types.js +1 -0
- package/dist/extensions/file-autocomplete.d.ts +1 -1
- package/dist/extensions/index.d.ts +7 -14
- package/dist/extensions/index.js +2 -19
- package/dist/extensions/slash-commands.d.ts +1 -1
- package/dist/extensions/slash-commands.js +7 -2
- package/dist/shell/host-types.d.ts +114 -0
- package/dist/shell/host-types.js +1 -0
- package/dist/shell/index.d.ts +8 -7
- package/dist/shell/index.js +58 -9
- package/dist/shell/input-handler.d.ts +7 -1
- package/dist/shell/input-handler.js +5 -2
- package/dist/shell/output-parser.d.ts +1 -1
- package/dist/{extensions → shell}/shell-context.d.ts +1 -1
- package/dist/{extensions → shell}/shell-context.js +18 -12
- package/dist/shell/shell.d.ts +6 -4
- package/dist/shell/shell.js +33 -109
- package/dist/shell/strategies/bash.d.ts +2 -0
- package/dist/shell/strategies/bash.js +68 -0
- package/dist/shell/strategies/fish.d.ts +2 -0
- package/dist/shell/strategies/fish.js +65 -0
- package/dist/shell/strategies/index.d.ts +13 -0
- package/dist/shell/strategies/index.js +17 -0
- package/dist/shell/strategies/types.d.ts +50 -0
- package/dist/shell/strategies/types.js +9 -0
- package/dist/shell/strategies/zsh.d.ts +2 -0
- package/dist/shell/strategies/zsh.js +72 -0
- package/dist/shell/tui-input-view.js +14 -3
- package/dist/{extensions → shell}/tui-renderer.d.ts +1 -1
- package/dist/{extensions → shell}/tui-renderer.js +27 -55
- package/dist/utils/box-frame.d.ts +4 -0
- package/dist/utils/box-frame.js +17 -6
- package/dist/utils/compositor.d.ts +1 -1
- package/dist/utils/compositor.js +2 -1
- package/dist/{executor.js → utils/executor.js} +1 -1
- package/dist/utils/floating-panel.d.ts +17 -5
- package/dist/utils/floating-panel.js +218 -70
- package/dist/utils/llm-facade.d.ts +7 -3
- package/dist/utils/stream-transform.d.ts +1 -1
- package/dist/utils/terminal-buffer.d.ts +1 -1
- package/dist/utils/tool-display.js +4 -0
- package/dist/utils/tool-interactive.d.ts +1 -1
- package/dist/utils/tty.d.ts +7 -0
- package/dist/utils/tty.js +15 -0
- package/examples/extensions/ash-acp-bridge/README.md +4 -1
- package/examples/extensions/ash-acp-bridge/src/index.ts +654 -0
- package/examples/extensions/ash-mcp-bridge/index.ts +1 -1
- package/examples/extensions/ashi/README.md +250 -0
- package/examples/extensions/ashi/package.json +60 -0
- package/examples/extensions/ashi/src/autocomplete.ts +91 -0
- package/examples/extensions/ashi/src/capture.ts +34 -0
- package/examples/extensions/ashi/src/cli.ts +126 -0
- package/examples/extensions/ashi/src/commands.ts +82 -0
- package/examples/extensions/ashi/src/compaction.ts +157 -0
- package/examples/extensions/ashi/src/components.ts +332 -0
- package/examples/extensions/ashi/src/default-renderers.ts +153 -0
- package/examples/extensions/ashi/src/display-config.ts +62 -0
- package/examples/extensions/ashi/src/frontend.ts +735 -0
- package/examples/extensions/ashi/src/hooks.ts +136 -0
- package/examples/extensions/ashi/src/multi-session-store.ts +146 -0
- package/examples/extensions/ashi/src/session-commands.ts +76 -0
- package/examples/extensions/ashi/src/session-store.ts +264 -0
- package/examples/extensions/ashi/src/status-footer.ts +66 -0
- package/examples/extensions/ashi/src/theme.ts +151 -0
- package/examples/extensions/ashi/tsconfig.json +14 -0
- package/examples/extensions/emacs-buffer.ts +364 -0
- package/examples/extensions/interactive-prompts.ts +114 -69
- package/examples/extensions/latex-images.ts +3 -3
- package/examples/extensions/opencode-bridge/index.ts +1 -1
- package/examples/extensions/overlay-agent.ts +35 -10
- package/examples/extensions/peer-mesh.ts +1 -1
- package/examples/extensions/pi-bridge/index.ts +0 -1
- package/examples/extensions/questionnaire.ts +2 -1
- package/examples/extensions/rtk-proxy.ts +3 -3
- package/examples/extensions/solarized-theme.ts +3 -3
- package/examples/extensions/subagents.ts +6 -6
- package/examples/extensions/terminal-buffer.ts +174 -33
- package/examples/extensions/tmux-pane.ts +6 -4
- package/examples/extensions/tunnel-vision.ts +405 -0
- package/examples/extensions/user-shell.ts +1 -1
- package/examples/extensions/web-access.ts +8 -113
- package/package.json +26 -22
- package/dist/extensions/agent-backend.d.ts +0 -14
- package/dist/extensions/agent-backend.js +0 -307
- package/dist/types.d.ts +0 -227
- /package/dist/{types.js → agent/host-types.js} +0 -0
- /package/dist/{extensions → agent}/providers/openai-compatible.js +0 -0
- /package/dist/{index.d.ts → cli/index.d.ts} +0 -0
- /package/dist/{init.d.ts → cli/init.d.ts} +0 -0
- /package/dist/{install.d.ts → cli/install.d.ts} +0 -0
- /package/dist/{event-bus.js → core/event-bus.js} +0 -0
- /package/dist/{executor.d.ts → utils/executor.d.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EventBus } from "../event-bus.js";
|
|
1
|
+
import type { EventBus } from "../core/event-bus.js";
|
|
2
2
|
import { TuiInputView } from "./tui-input-view.js";
|
|
3
3
|
/** Narrow contract between InputHandler and its host (Shell). */
|
|
4
4
|
export interface InputContext {
|
|
@@ -10,6 +10,10 @@ export interface InputContext {
|
|
|
10
10
|
redrawPrompt(): void;
|
|
11
11
|
freshPrompt(): void;
|
|
12
12
|
}
|
|
13
|
+
export interface InputHandlers {
|
|
14
|
+
define: (name: string, fn: (...a: any[]) => any) => void;
|
|
15
|
+
call: (name: string, ...a: any[]) => any;
|
|
16
|
+
}
|
|
13
17
|
/** Line editor + shell-passthrough buffer. Delegates rendering to TuiInputView. */
|
|
14
18
|
export declare class InputHandler {
|
|
15
19
|
private ctx;
|
|
@@ -27,11 +31,13 @@ export declare class InputHandler {
|
|
|
27
31
|
private savedBuffer;
|
|
28
32
|
private escapeTimer;
|
|
29
33
|
private bus;
|
|
34
|
+
private handlers;
|
|
30
35
|
private onShowAgentInfo;
|
|
31
36
|
private view;
|
|
32
37
|
constructor(opts: {
|
|
33
38
|
ctx: InputContext;
|
|
34
39
|
bus: EventBus;
|
|
40
|
+
handlers: InputHandlers;
|
|
35
41
|
onShowAgentInfo: () => {
|
|
36
42
|
info: string;
|
|
37
43
|
model?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { LineEditor } from "../utils/line-editor.js";
|
|
4
|
-
import { CONFIG_DIR, getSettings } from "../settings.js";
|
|
4
|
+
import { CONFIG_DIR, getSettings } from "../core/settings.js";
|
|
5
5
|
import { TuiInputView } from "./tui-input-view.js";
|
|
6
6
|
const HISTORY_FILE = path.join(CONFIG_DIR, "input-history");
|
|
7
7
|
/** Line editor + shell-passthrough buffer. Delegates rendering to TuiInputView. */
|
|
@@ -21,11 +21,13 @@ export class InputHandler {
|
|
|
21
21
|
savedBuffer = "";
|
|
22
22
|
escapeTimer = null;
|
|
23
23
|
bus;
|
|
24
|
+
handlers;
|
|
24
25
|
onShowAgentInfo;
|
|
25
26
|
view;
|
|
26
27
|
constructor(opts) {
|
|
27
28
|
this.ctx = opts.ctx;
|
|
28
29
|
this.bus = opts.bus;
|
|
30
|
+
this.handlers = opts.handlers;
|
|
29
31
|
this.onShowAgentInfo = opts.onShowAgentInfo;
|
|
30
32
|
this.view = opts.view ?? new TuiInputView();
|
|
31
33
|
this.loadHistory();
|
|
@@ -46,6 +48,7 @@ export class InputHandler {
|
|
|
46
48
|
}
|
|
47
49
|
this.modes.set(config.trigger, config);
|
|
48
50
|
this.modesById.set(config.id, config);
|
|
51
|
+
this.handlers.define(`input-mode:${config.id}:submit`, config.onSubmit.bind(config));
|
|
49
52
|
}
|
|
50
53
|
loadHistory() {
|
|
51
54
|
try {
|
|
@@ -380,7 +383,7 @@ export class InputHandler {
|
|
|
380
383
|
}
|
|
381
384
|
else if (query) {
|
|
382
385
|
this.pendingReturnMode = currentMode.returnToSelf ? currentMode.id : null;
|
|
383
|
-
currentMode.
|
|
386
|
+
this.handlers.call(`input-mode:${currentMode.id}:submit`, query, this.bus);
|
|
384
387
|
}
|
|
385
388
|
else {
|
|
386
389
|
this.exitMode();
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* user-shell exchanges) signals. Frontends without a PTY skip this
|
|
5
5
|
* built-in and the agent runs cwd-aware via core's process.cwd() default.
|
|
6
6
|
*/
|
|
7
|
-
import type { ExtensionContext } from "
|
|
7
|
+
import type { ExtensionContext } from "./host-types.js";
|
|
8
8
|
export default function activate(ctx: ExtensionContext): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getSettings } from "../settings.js";
|
|
1
|
+
import { getSettings } from "../core/settings.js";
|
|
2
2
|
import { spillOutput } from "../utils/shell-output-spill.js";
|
|
3
3
|
export default function activate(ctx) {
|
|
4
4
|
const { bus } = ctx;
|
|
@@ -43,17 +43,23 @@ export default function activate(ctx) {
|
|
|
43
43
|
bus.on("shell:agent-exec-done", () => { agentShellActive = false; });
|
|
44
44
|
// Override core's process.cwd() default with the PTY-tracked value.
|
|
45
45
|
ctx.advise("cwd", () => currentCwd);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
// Advises the core handler directly: shell-context loads before the
|
|
47
|
+
// agent host attaches `ctx.agent`, so the sugar isn't available yet.
|
|
48
|
+
ctx.advise("query-context:build", (next) => {
|
|
49
|
+
const base = next();
|
|
50
|
+
const part = (() => {
|
|
51
|
+
const cwdTag = `<cwd>${currentCwd}</cwd>`;
|
|
52
|
+
const fresh = exchanges.filter((ex) => ex.id > lastSeq && ex.source !== "agent");
|
|
53
|
+
if (fresh.length === 0)
|
|
54
|
+
return cwdTag;
|
|
55
|
+
lastSeq = exchanges[exchanges.length - 1].id;
|
|
56
|
+
const text = fresh.map(formatExchangeTruncated).filter(Boolean).join("\n");
|
|
57
|
+
if (!text)
|
|
58
|
+
return cwdTag;
|
|
59
|
+
return `${cwdTag}\n<shell_events>\n${text}\n</shell_events>`;
|
|
60
|
+
})();
|
|
61
|
+
return base ? `${base}\n\n${part}` : part;
|
|
62
|
+
});
|
|
57
63
|
ctx.define("shell:context-recent", (n = 25) => {
|
|
58
64
|
const recent = exchanges.slice(-n);
|
|
59
65
|
if (recent.length === 0)
|
package/dist/shell/shell.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EventBus } from "../event-bus.js";
|
|
1
|
+
import type { EventBus } from "../core/event-bus.js";
|
|
2
2
|
import { type InputContext } from "./input-handler.js";
|
|
3
3
|
export interface ShellHandlers {
|
|
4
4
|
define: (name: string, fn: (...args: any[]) => any) => void;
|
|
@@ -24,7 +24,7 @@ export declare class Shell implements InputContext {
|
|
|
24
24
|
private unmuteScopes;
|
|
25
25
|
private pendingEchoSkips;
|
|
26
26
|
private agentActive;
|
|
27
|
-
private
|
|
27
|
+
private strategy;
|
|
28
28
|
private tmpDir?;
|
|
29
29
|
constructor(opts: {
|
|
30
30
|
bus: EventBus;
|
|
@@ -53,8 +53,10 @@ export declare class Shell implements InputContext {
|
|
|
53
53
|
isAgentActive(): boolean;
|
|
54
54
|
writeToPty(data: string): void;
|
|
55
55
|
/**
|
|
56
|
-
* Ask the shell to redraw its own prompt in place
|
|
57
|
-
*
|
|
56
|
+
* Ask the shell to redraw its own prompt in place. The escape sequence is
|
|
57
|
+
* defined per-strategy and bound in the generated rc file (zsh: ZLE widget,
|
|
58
|
+
* bash: readline redraw-current-line). When the strategy returns null we
|
|
59
|
+
* skip the in-place redraw and let freshPrompt do a heavy redraw instead.
|
|
58
60
|
*/
|
|
59
61
|
redrawPrompt(): void;
|
|
60
62
|
/**
|
package/dist/shell/shell.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as os from "os";
|
|
3
|
-
import * as path from "path";
|
|
4
3
|
import * as pty from "node-pty";
|
|
5
4
|
import { InputHandler } from "./input-handler.js";
|
|
6
5
|
import { OutputParser } from "./output-parser.js";
|
|
7
|
-
import { getSettings } from "../settings.js";
|
|
6
|
+
import { getSettings } from "../core/settings.js";
|
|
7
|
+
import { clearOpost } from "../utils/tty.js";
|
|
8
|
+
import { pickStrategy, FALLBACK_STRATEGY, SUPPORTED_SHELL_NAMES, } from "./strategies/index.js";
|
|
8
9
|
export class Shell {
|
|
9
10
|
ptyProcess;
|
|
10
11
|
bus;
|
|
@@ -19,7 +20,7 @@ export class Shell {
|
|
|
19
20
|
unmuteScopes = new Set();
|
|
20
21
|
pendingEchoSkips = 0;
|
|
21
22
|
agentActive = false;
|
|
22
|
-
|
|
23
|
+
strategy;
|
|
23
24
|
tmpDir;
|
|
24
25
|
constructor(opts) {
|
|
25
26
|
// Build environment — filter out undefined values (node-pty's native
|
|
@@ -35,98 +36,28 @@ export class Shell {
|
|
|
35
36
|
// - OSC 7: cwd tracking (required by OutputParser)
|
|
36
37
|
// - OSC 9999: prompt start marker (command boundary detection)
|
|
37
38
|
// - OSC 9998: prompt end marker (bracketed prompt capture)
|
|
38
|
-
// Prompt theming is left entirely to the user's shell config.
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.warn(`Warning: agent-sh only supports zsh and bash. ` +
|
|
39
|
+
// Prompt theming is left entirely to the user's shell config. Per-shell
|
|
40
|
+
// rc-file generation lives in src/shell/strategies/.
|
|
41
|
+
const matched = pickStrategy(opts.shell);
|
|
42
|
+
if (!matched) {
|
|
43
|
+
console.warn(`Warning: agent-sh only supports ${SUPPORTED_SHELL_NAMES.join(", ")}. ` +
|
|
44
44
|
`"${opts.shell}" may not work correctly — falling back to /bin/bash.`);
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
this.strategy = matched ?? FALLBACK_STRATEGY;
|
|
47
|
+
const shellBin = matched ? opts.shell : "/bin/bash";
|
|
48
48
|
// Per-instance tag so nested agent-sh hooks don't cross-trigger.
|
|
49
49
|
const instanceTag = `id=${opts.instanceId}`;
|
|
50
|
-
const osc7Cmd = 'printf "\\e]7;file://%s%s\\a" "$(hostname)" "$PWD"';
|
|
51
|
-
const promptMarker = `printf "\\e]9999;${instanceTag};PROMPT\\a"`;
|
|
52
|
-
const titleCmd = 'printf "\\e]0;⚡ agent-sh: %s\\a" "${PWD/#$HOME/~}"';
|
|
53
|
-
this.isZsh = isZsh;
|
|
54
50
|
const settings = getSettings();
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"# agent-sh hooks (invisible OSC sequences for cwd + prompt detection)",
|
|
66
|
-
"__agent_sh_precmd() {",
|
|
67
|
-
` ${osc7Cmd}`,
|
|
68
|
-
` ${promptMarker}`,
|
|
69
|
-
...(showIndicator ? [` ${titleCmd}`] : []),
|
|
70
|
-
"}",
|
|
71
|
-
"precmd_functions+=(__agent_sh_precmd)",
|
|
72
|
-
"",
|
|
73
|
-
"# Preexec hook: emit actual command text so agent-sh can track",
|
|
74
|
-
"# history-recalled and tab-completed commands accurately",
|
|
75
|
-
"__agent_sh_preexec() {",
|
|
76
|
-
` printf "\\e]9997;${instanceTag};%s\\a" "$1"`,
|
|
77
|
-
"}",
|
|
78
|
-
"preexec_functions+=(__agent_sh_preexec)",
|
|
79
|
-
];
|
|
80
|
-
zshrcLines.push("", "# End-of-prompt marker via zle-line-init (fires after prompt is rendered)", "# Chain onto existing widget (p10k uses zle-line-init) rather than clobbering", 'if (( ${+widgets[zle-line-init]} )); then', " zle -A zle-line-init __agent_sh_orig_line_init", " __agent_sh_line_init() {", " zle __agent_sh_orig_line_init", ` printf "\\e]9998;${instanceTag};READY\\a"`, " }", "else", " __agent_sh_line_init() {", ` printf "\\e]9998;${instanceTag};READY\\a"`, " }", "fi", "zle -N zle-line-init __agent_sh_line_init", "", "# Hidden widget to trigger prompt redraw from Node.js side", "# Bound to an unused escape sequence that no real key produces", "__agent_sh_redraw() {", " zle reset-prompt", "}", "zle -N __agent_sh_redraw", "bindkey '\\e[9999~' __agent_sh_redraw");
|
|
81
|
-
fs.writeFileSync(path.join(this.tmpDir, ".zshrc"), zshrcLines.join("\n") + "\n");
|
|
82
|
-
env.ZDOTDIR = this.tmpDir;
|
|
83
|
-
shellArgs = ["--no-globalrcs"];
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
// For bash: use --rcfile to source our wrapper, which sources the user's
|
|
87
|
-
// real bashrc then appends our hooks. No HOME override needed.
|
|
88
|
-
this.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-sh-"));
|
|
89
|
-
const userHome = env.HOME || os.homedir();
|
|
90
|
-
const bashrcLines = [
|
|
91
|
-
`[ -f "${userHome}/.bashrc" ] && source "${userHome}/.bashrc"`,
|
|
92
|
-
"",
|
|
93
|
-
"# agent-sh hooks (invisible OSC sequences for cwd + prompt detection)",
|
|
94
|
-
"# Wrapped in a function because inlining printf \"...\" into",
|
|
95
|
-
"# PROMPT_COMMAND=\"...\" breaks the outer quoting.",
|
|
96
|
-
"__agent_sh_precmd() {",
|
|
97
|
-
` ${osc7Cmd}`,
|
|
98
|
-
` ${promptMarker}`,
|
|
99
|
-
...(showIndicator ? [` ${titleCmd}`] : []),
|
|
100
|
-
" __agent_sh_preexec_ran=0",
|
|
101
|
-
"}",
|
|
102
|
-
`PROMPT_COMMAND="\${PROMPT_COMMAND%;}"`,
|
|
103
|
-
`PROMPT_COMMAND="\${PROMPT_COMMAND:+\$PROMPT_COMMAND;}__agent_sh_precmd"`,
|
|
104
|
-
"",
|
|
105
|
-
"# Preexec hook via DEBUG trap: emit actual command text so agent-sh",
|
|
106
|
-
"# can track history-recalled and tab-completed commands accurately",
|
|
107
|
-
"__agent_sh_preexec_ran=0",
|
|
108
|
-
"__agent_sh_emit_preexec() {",
|
|
109
|
-
' [[ $__agent_sh_preexec_ran == 1 ]] && return',
|
|
110
|
-
' [[ -n $COMP_LINE ]] && return',
|
|
111
|
-
" __agent_sh_preexec_ran=1",
|
|
112
|
-
" local this_cmd",
|
|
113
|
-
` this_cmd=$(HISTTIMEFORMAT='' builtin history 1 | command sed 's/^ *[0-9]* *//')`,
|
|
114
|
-
` printf '\\e]9997;${instanceTag};%s\\a' "$this_cmd"`,
|
|
115
|
-
"}",
|
|
116
|
-
"trap '__agent_sh_emit_preexec' DEBUG",
|
|
117
|
-
"",
|
|
118
|
-
"# End-of-prompt marker: append to PS1 (\\[...\\] marks it zero-width)",
|
|
119
|
-
`case "$PS1" in *9998*) ;; *) PS1="\${PS1}\\[\\e]9998;${instanceTag};READY\\a\\]";; esac`,
|
|
120
|
-
"",
|
|
121
|
-
"# Mirrors the zsh \\e[9999~ reset-prompt widget — used by agent-sh",
|
|
122
|
-
"# to repaint the prompt in place. All keymaps so `set -o vi` works.",
|
|
123
|
-
`bind -m emacs '"\\e[9999~":redraw-current-line' 2>/dev/null`,
|
|
124
|
-
`bind -m vi-insert '"\\e[9999~":redraw-current-line' 2>/dev/null`,
|
|
125
|
-
`bind -m vi-command '"\\e[9999~":redraw-current-line' 2>/dev/null`,
|
|
126
|
-
];
|
|
127
|
-
fs.writeFileSync(path.join(this.tmpDir, ".bashrc"), bashrcLines.join("\n") + "\n");
|
|
128
|
-
shellArgs = ["--rcfile", path.join(this.tmpDir, ".bashrc")];
|
|
129
|
-
}
|
|
51
|
+
const spawnConfig = this.strategy.prepareSpawn({
|
|
52
|
+
tmpDirRoot: os.tmpdir(),
|
|
53
|
+
instanceTag,
|
|
54
|
+
showIndicator: settings.promptIndicator !== false,
|
|
55
|
+
userHome: env.HOME || os.homedir(),
|
|
56
|
+
env,
|
|
57
|
+
});
|
|
58
|
+
this.tmpDir = spawnConfig.tmpDir;
|
|
59
|
+
Object.assign(env, spawnConfig.envOverrides);
|
|
60
|
+
const shellArgs = spawnConfig.args;
|
|
130
61
|
// Pause stdin before spawning PTY to avoid TTY contention on macOS.
|
|
131
62
|
// The PTY will become the controlling terminal for the child shell.
|
|
132
63
|
const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
|
|
@@ -158,6 +89,7 @@ export class Shell {
|
|
|
158
89
|
// Ignore - will be set up later in index.ts
|
|
159
90
|
}
|
|
160
91
|
}
|
|
92
|
+
clearOpost();
|
|
161
93
|
this.bus = opts.bus;
|
|
162
94
|
this.handlers = opts.handlers;
|
|
163
95
|
this.outputParser = new OutputParser(opts.bus, opts.cwd, instanceTag);
|
|
@@ -175,6 +107,7 @@ export class Shell {
|
|
|
175
107
|
this.inputHandler = new InputHandler({
|
|
176
108
|
ctx: this,
|
|
177
109
|
bus: opts.bus,
|
|
110
|
+
handlers: opts.handlers,
|
|
178
111
|
onShowAgentInfo: opts.onShowAgentInfo ?? (() => ({ info: "" })),
|
|
179
112
|
});
|
|
180
113
|
this.setupOutput();
|
|
@@ -272,8 +205,10 @@ export class Shell {
|
|
|
272
205
|
this.ptyProcess.write(data);
|
|
273
206
|
}
|
|
274
207
|
/**
|
|
275
|
-
* Ask the shell to redraw its own prompt in place
|
|
276
|
-
*
|
|
208
|
+
* Ask the shell to redraw its own prompt in place. The escape sequence is
|
|
209
|
+
* defined per-strategy and bound in the generated rc file (zsh: ZLE widget,
|
|
210
|
+
* bash: readline redraw-current-line). When the strategy returns null we
|
|
211
|
+
* skip the in-place redraw and let freshPrompt do a heavy redraw instead.
|
|
277
212
|
*/
|
|
278
213
|
redrawPrompt() {
|
|
279
214
|
const result = this.bus.emitPipe("shell:redraw-prompt", {
|
|
@@ -281,9 +216,11 @@ export class Shell {
|
|
|
281
216
|
kind: "redraw",
|
|
282
217
|
handled: false,
|
|
283
218
|
});
|
|
284
|
-
if (
|
|
285
|
-
|
|
286
|
-
|
|
219
|
+
if (result.handled)
|
|
220
|
+
return;
|
|
221
|
+
const escape = this.strategy.redrawEscape();
|
|
222
|
+
if (escape)
|
|
223
|
+
this.ptyProcess.write(escape);
|
|
287
224
|
}
|
|
288
225
|
/**
|
|
289
226
|
* Heavy redraw: send \n to PTY to trigger a full precmd → prompt cycle.
|
|
@@ -364,23 +301,10 @@ export class Shell {
|
|
|
364
301
|
this.bus.on("agent:processing-done", () => {
|
|
365
302
|
this.handlers.call("shell:on-processing-done");
|
|
366
303
|
});
|
|
367
|
-
// Permission UI is briefly visible during the prompt; an unmute scope
|
|
368
|
-
// overrides whatever mute is currently held, then releases cleanly.
|
|
369
|
-
// Doesn't touch agent-turn state, so suppressed handlers can't leak.
|
|
370
|
-
let permissionVisible = null;
|
|
371
|
-
this.bus.on("permission:request", () => {
|
|
372
|
-
permissionVisible?.release();
|
|
373
|
-
permissionVisible = this.acquireUnmute("permission-ui");
|
|
374
|
-
});
|
|
375
|
-
this.bus.onPipeAsync("permission:request", async (payload) => {
|
|
376
|
-
permissionVisible?.release();
|
|
377
|
-
permissionVisible = null;
|
|
378
|
-
return payload;
|
|
379
|
-
});
|
|
380
304
|
this.bus.onPipeAsync("shell:exec-request", async (payload) => {
|
|
381
305
|
const visible = this.acquireUnmute("exec-request");
|
|
382
306
|
this.skipNextLine();
|
|
383
|
-
process.stdout.write("\n");
|
|
307
|
+
process.stdout.write("\r\n");
|
|
384
308
|
this.bus.emit("shell:agent-exec-start", {});
|
|
385
309
|
try {
|
|
386
310
|
const output = await new Promise((resolve, reject) => {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
const OSC7_CMD = 'printf "\\e]7;file://%s%s\\a" "$(hostname)" "$PWD"';
|
|
4
|
+
const TITLE_CMD = 'printf "\\e]0;⚡ agent-sh: %s\\a" "${PWD/#$HOME/~}"';
|
|
5
|
+
export const bashStrategy = {
|
|
6
|
+
name: "bash",
|
|
7
|
+
matches(shellPath) {
|
|
8
|
+
return path.basename(shellPath).includes("bash");
|
|
9
|
+
},
|
|
10
|
+
prepareSpawn(opts) {
|
|
11
|
+
const { tmpDirRoot, instanceTag, showIndicator, env, userHome } = opts;
|
|
12
|
+
// Use --rcfile to source our wrapper, which sources the user's real
|
|
13
|
+
// bashrc then appends our hooks. No HOME override needed.
|
|
14
|
+
const tmpDir = fs.mkdtempSync(path.join(tmpDirRoot, "agent-sh-"));
|
|
15
|
+
const home = env.HOME || userHome;
|
|
16
|
+
const promptMarker = `printf "\\e]9999;${instanceTag};PROMPT\\a"`;
|
|
17
|
+
const lines = [
|
|
18
|
+
`[ -f "${home}/.bashrc" ] && source "${home}/.bashrc"`,
|
|
19
|
+
"",
|
|
20
|
+
"# agent-sh hooks (invisible OSC sequences for cwd + prompt detection)",
|
|
21
|
+
"# Wrapped in a function because inlining printf \"...\" into",
|
|
22
|
+
"# PROMPT_COMMAND=\"...\" breaks the outer quoting.",
|
|
23
|
+
"__agent_sh_precmd() {",
|
|
24
|
+
` ${OSC7_CMD}`,
|
|
25
|
+
` ${promptMarker}`,
|
|
26
|
+
...(showIndicator ? [` ${TITLE_CMD}`] : []),
|
|
27
|
+
" __agent_sh_preexec_ran=0",
|
|
28
|
+
"}",
|
|
29
|
+
`PROMPT_COMMAND="\${PROMPT_COMMAND%;}"`,
|
|
30
|
+
`PROMPT_COMMAND="\${PROMPT_COMMAND:+\$PROMPT_COMMAND;}__agent_sh_precmd"`,
|
|
31
|
+
"",
|
|
32
|
+
"# Preexec hook via DEBUG trap: emit actual command text so agent-sh",
|
|
33
|
+
"# can track history-recalled and tab-completed commands accurately",
|
|
34
|
+
"__agent_sh_preexec_ran=0",
|
|
35
|
+
"__agent_sh_emit_preexec() {",
|
|
36
|
+
' [[ $__agent_sh_preexec_ran == 1 ]] && return',
|
|
37
|
+
' [[ -n $COMP_LINE ]] && return',
|
|
38
|
+
" __agent_sh_preexec_ran=1",
|
|
39
|
+
" local this_cmd",
|
|
40
|
+
` this_cmd=$(HISTTIMEFORMAT='' builtin history 1 | command sed 's/^ *[0-9]* *//')`,
|
|
41
|
+
` printf '\\e]9997;${instanceTag};%s\\a' "$this_cmd"`,
|
|
42
|
+
"}",
|
|
43
|
+
"trap '__agent_sh_emit_preexec' DEBUG",
|
|
44
|
+
"",
|
|
45
|
+
"# End-of-prompt marker: append to PS1 (\\[...\\] marks it zero-width)",
|
|
46
|
+
`case "$PS1" in *9998*) ;; *) PS1="\${PS1}\\[\\e]9998;${instanceTag};READY\\a\\]";; esac`,
|
|
47
|
+
"",
|
|
48
|
+
"# Mirrors the zsh \\e[9999~ reset-prompt widget — used by agent-sh",
|
|
49
|
+
"# to repaint the prompt in place. All keymaps so `set -o vi` works.",
|
|
50
|
+
`bind -m emacs '"\\e[9999~":redraw-current-line' 2>/dev/null`,
|
|
51
|
+
`bind -m vi-insert '"\\e[9999~":redraw-current-line' 2>/dev/null`,
|
|
52
|
+
`bind -m vi-command '"\\e[9999~":redraw-current-line' 2>/dev/null`,
|
|
53
|
+
];
|
|
54
|
+
const rcPath = path.join(tmpDir, ".bashrc");
|
|
55
|
+
fs.writeFileSync(rcPath, lines.join("\n") + "\n");
|
|
56
|
+
return {
|
|
57
|
+
args: ["--rcfile", rcPath],
|
|
58
|
+
envOverrides: {},
|
|
59
|
+
tmpDir,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
envCaptureCommand() {
|
|
63
|
+
return "[ -f ~/.bashrc ] && source ~/.bashrc 2>/dev/null; env -0";
|
|
64
|
+
},
|
|
65
|
+
redrawEscape() {
|
|
66
|
+
return "\x1b[9999~";
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
const OSC7_CMD = 'printf "\\e]7;file://%s%s\\a" (hostname) "$PWD"';
|
|
4
|
+
const TITLE_CMD = 'printf "\\e]0;⚡ agent-sh: %s\\a" (string replace -- "$HOME" "~" "$PWD")';
|
|
5
|
+
export const fishStrategy = {
|
|
6
|
+
name: "fish",
|
|
7
|
+
matches(shellPath) {
|
|
8
|
+
return path.basename(shellPath).includes("fish");
|
|
9
|
+
},
|
|
10
|
+
prepareSpawn(opts) {
|
|
11
|
+
const { tmpDirRoot, instanceTag, showIndicator } = opts;
|
|
12
|
+
// Layer hooks via `-C` so they run after the user's config — our wrapper
|
|
13
|
+
// around fish_prompt needs to see the user's final definition.
|
|
14
|
+
const tmpDir = fs.mkdtempSync(path.join(tmpDirRoot, "agent-sh-"));
|
|
15
|
+
const initPath = path.join(tmpDir, "init.fish");
|
|
16
|
+
const promptMarker = `printf "\\e]9999;${instanceTag};PROMPT\\a"`;
|
|
17
|
+
const lines = [
|
|
18
|
+
"# agent-sh hooks (invisible OSC sequences for cwd + prompt detection)",
|
|
19
|
+
"function __agent_sh_precmd --on-event fish_prompt",
|
|
20
|
+
` ${OSC7_CMD}`,
|
|
21
|
+
` ${promptMarker}`,
|
|
22
|
+
...(showIndicator ? [` ${TITLE_CMD}`] : []),
|
|
23
|
+
"end",
|
|
24
|
+
"",
|
|
25
|
+
"# Preexec hook: emit actual command text so agent-sh can track",
|
|
26
|
+
"# history-recalled and tab-completed commands accurately",
|
|
27
|
+
"function __agent_sh_preexec --on-event fish_preexec",
|
|
28
|
+
` printf "\\e]9997;${instanceTag};%s\\a" "$argv"`,
|
|
29
|
+
"end",
|
|
30
|
+
"",
|
|
31
|
+
"# End-of-prompt marker: wrap fish_prompt so READY fires after render",
|
|
32
|
+
"if functions -q fish_prompt",
|
|
33
|
+
" functions --copy fish_prompt __agent_sh_orig_fish_prompt",
|
|
34
|
+
" function fish_prompt",
|
|
35
|
+
" __agent_sh_orig_fish_prompt",
|
|
36
|
+
` printf "\\e]9998;${instanceTag};READY\\a"`,
|
|
37
|
+
" end",
|
|
38
|
+
"else",
|
|
39
|
+
" function fish_prompt",
|
|
40
|
+
" printf '%s> ' (prompt_pwd)",
|
|
41
|
+
` printf "\\e]9998;${instanceTag};READY\\a"`,
|
|
42
|
+
" end",
|
|
43
|
+
"end",
|
|
44
|
+
"",
|
|
45
|
+
"# Redraw binding. fish 4 silently drops \\e[N~ outside the F-key table,",
|
|
46
|
+
"# so we use CSI-u with a private-use codepoint (U+E028) instead.",
|
|
47
|
+
"bind \\e\\[57400u 'commandline -f repaint' 2>/dev/null",
|
|
48
|
+
"bind -M insert \\e\\[57400u 'commandline -f repaint' 2>/dev/null",
|
|
49
|
+
"bind -M default \\e\\[57400u 'commandline -f repaint' 2>/dev/null",
|
|
50
|
+
];
|
|
51
|
+
fs.writeFileSync(initPath, lines.join("\n") + "\n");
|
|
52
|
+
return {
|
|
53
|
+
args: ["-l", "-i", "-C", `source ${initPath}`],
|
|
54
|
+
envOverrides: {},
|
|
55
|
+
tmpDir,
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
envCaptureCommand() {
|
|
59
|
+
// `fish -l` already sources config.fish + conf.d, so no explicit source.
|
|
60
|
+
return "env -0";
|
|
61
|
+
},
|
|
62
|
+
redrawEscape() {
|
|
63
|
+
return "\x1b[57400u";
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ShellStrategy } from "./types.js";
|
|
2
|
+
export type { ShellStrategy, PrepareSpawnOpts, ShellSpawnConfig } from "./types.js";
|
|
3
|
+
/** Strategy used when the requested shell isn't recognized. */
|
|
4
|
+
export declare const FALLBACK_STRATEGY: ShellStrategy;
|
|
5
|
+
/** Names of supported shells, used for warning messages. */
|
|
6
|
+
export declare const SUPPORTED_SHELL_NAMES: readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* Pick the strategy that matches the given shell binary path. Returns null
|
|
9
|
+
* when no strategy claims the path — caller decides whether to warn and how
|
|
10
|
+
* to fall back (e.g. shell.ts swaps the binary to /bin/bash; env capture
|
|
11
|
+
* just runs the fallback strategy's syntax against the original binary).
|
|
12
|
+
*/
|
|
13
|
+
export declare function pickStrategy(shellPath: string): ShellStrategy | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { zshStrategy } from "./zsh.js";
|
|
2
|
+
import { bashStrategy } from "./bash.js";
|
|
3
|
+
import { fishStrategy } from "./fish.js";
|
|
4
|
+
const STRATEGIES = [zshStrategy, bashStrategy, fishStrategy];
|
|
5
|
+
/** Strategy used when the requested shell isn't recognized. */
|
|
6
|
+
export const FALLBACK_STRATEGY = bashStrategy;
|
|
7
|
+
/** Names of supported shells, used for warning messages. */
|
|
8
|
+
export const SUPPORTED_SHELL_NAMES = STRATEGIES.map((s) => s.name);
|
|
9
|
+
/**
|
|
10
|
+
* Pick the strategy that matches the given shell binary path. Returns null
|
|
11
|
+
* when no strategy claims the path — caller decides whether to warn and how
|
|
12
|
+
* to fall back (e.g. shell.ts swaps the binary to /bin/bash; env capture
|
|
13
|
+
* just runs the fallback strategy's syntax against the original binary).
|
|
14
|
+
*/
|
|
15
|
+
export function pickStrategy(shellPath) {
|
|
16
|
+
return STRATEGIES.find((s) => s.matches(shellPath)) ?? null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-shell adapter for the bits of agent-sh that are inherently shell-syntax
|
|
3
|
+
* specific: rc-file generation, spawn args/env, env-capture command, and the
|
|
4
|
+
* escape sequence used to repaint the prompt in place.
|
|
5
|
+
*
|
|
6
|
+
* Everything else (PTY I/O, OSC parsing, mute scopes, prompt boundary
|
|
7
|
+
* detection) is shell-agnostic and lives in shell.ts / output-parser.ts.
|
|
8
|
+
*/
|
|
9
|
+
export interface PrepareSpawnOpts {
|
|
10
|
+
/** Root for mkdtemp — typically os.tmpdir(). */
|
|
11
|
+
tmpDirRoot: string;
|
|
12
|
+
/** Per-instance tag (e.g. "id=abc123") so nested agent-sh hooks don't cross-trigger. */
|
|
13
|
+
instanceTag: string;
|
|
14
|
+
/** Whether to emit the terminal title indicator from the prompt hook. */
|
|
15
|
+
showIndicator: boolean;
|
|
16
|
+
/** Resolved user home (env.HOME ?? os.homedir()). */
|
|
17
|
+
userHome: string;
|
|
18
|
+
/** Inherited env at spawn time — strategies may read ZDOTDIR etc. */
|
|
19
|
+
env: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export interface ShellSpawnConfig {
|
|
22
|
+
/** Args to pass to pty.spawn after the shell binary. */
|
|
23
|
+
args: string[];
|
|
24
|
+
/** Env vars the strategy needs to set on the child (e.g. ZDOTDIR, XDG_CONFIG_HOME). */
|
|
25
|
+
envOverrides: Record<string, string>;
|
|
26
|
+
/** Temp directory the strategy created, if any — caller cleans up on exit. */
|
|
27
|
+
tmpDir?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ShellStrategy {
|
|
30
|
+
/** Short name used for fallback warnings ("zsh", "bash", "fish"). */
|
|
31
|
+
readonly name: string;
|
|
32
|
+
/** Does this strategy claim the binary at `shellPath`? */
|
|
33
|
+
matches(shellPath: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Generate any rc files and return spawn args + env overrides. May create
|
|
36
|
+
* a tmp directory; caller is responsible for cleanup via the returned path.
|
|
37
|
+
*/
|
|
38
|
+
prepareSpawn(opts: PrepareSpawnOpts): ShellSpawnConfig;
|
|
39
|
+
/**
|
|
40
|
+
* Shell-syntax command run via `<shell> -l -c "<cmd>"` to source the user's
|
|
41
|
+
* config and dump env. Used at startup to inherit shell-only env vars.
|
|
42
|
+
*/
|
|
43
|
+
envCaptureCommand(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Escape sequence to write to the PTY to ask the shell to repaint its
|
|
46
|
+
* prompt in place. The corresponding binding is set up in prepareSpawn.
|
|
47
|
+
* Returns null if the shell can't redraw — caller falls back to freshPrompt.
|
|
48
|
+
*/
|
|
49
|
+
redrawEscape(): string | null;
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-shell adapter for the bits of agent-sh that are inherently shell-syntax
|
|
3
|
+
* specific: rc-file generation, spawn args/env, env-capture command, and the
|
|
4
|
+
* escape sequence used to repaint the prompt in place.
|
|
5
|
+
*
|
|
6
|
+
* Everything else (PTY I/O, OSC parsing, mute scopes, prompt boundary
|
|
7
|
+
* detection) is shell-agnostic and lives in shell.ts / output-parser.ts.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|