@howaboua/pi-codex-conversion 1.0.7 → 1.0.9-dev.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 +37 -0
- package/package.json +7 -2
- package/src/adapter/runtime-shell.ts +13 -0
- package/src/index.ts +2 -1
- package/src/prompt/build-system-prompt.ts +6 -1
- package/src/shell/bash.ts +261 -0
- package/src/shell/parse-command.ts +616 -0
- package/src/shell/parse.ts +370 -93
- package/src/shell/summary.ts +19 -51
- package/src/shell/tokenize.ts +28 -5
- package/src/tools/exec-session-manager.ts +61 -5
package/src/shell/summary.ts
CHANGED
|
@@ -1,62 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { splitOnConnectors, normalizeTokens, shellSplit } from "./tokenize.ts";
|
|
1
|
+
import { parseCommandString, type ParsedShellCommand } from "./parse-command.ts";
|
|
3
2
|
import type { CommandSummary, ShellAction } from "./types.ts";
|
|
4
3
|
|
|
5
4
|
export type { CommandSummary, ShellAction } from "./types.ts";
|
|
6
5
|
|
|
7
|
-
// The adapter only masks commands when every parsed segment still looks like
|
|
8
|
-
// repository exploration. The moment we see an actual side-effectful run, we
|
|
9
|
-
// fall back to raw command rendering so the UI does not hide meaningful work.
|
|
10
6
|
export function summarizeShellCommand(command: string): CommandSummary {
|
|
11
|
-
const
|
|
12
|
-
const parts = splitOnConnectors(normalized);
|
|
13
|
-
const fallback = runSummary(command);
|
|
14
|
-
|
|
15
|
-
if (parts.length === 0) {
|
|
16
|
-
return fallback;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const actions: ShellAction[] = [];
|
|
20
|
-
let cwd: string | undefined;
|
|
21
|
-
|
|
22
|
-
for (const part of parts) {
|
|
23
|
-
if (part.length === 0) continue;
|
|
24
|
-
|
|
25
|
-
cwd = nextCwd(cwd, part);
|
|
26
|
-
const parsed = parseShellPart(part, cwd);
|
|
27
|
-
if (parsed === null) continue;
|
|
28
|
-
if (parsed.kind === "run") {
|
|
29
|
-
return fallback;
|
|
30
|
-
}
|
|
31
|
-
actions.push(parsed);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const deduped = dedupeActions(actions);
|
|
35
|
-
if (deduped.length === 0) {
|
|
36
|
-
return fallback;
|
|
37
|
-
}
|
|
38
|
-
|
|
7
|
+
const actions = parseCommandString(command).map(parsedToAction);
|
|
39
8
|
return {
|
|
40
|
-
maskAsExplored:
|
|
41
|
-
actions
|
|
9
|
+
maskAsExplored: actions.every((action) => action.kind !== "run"),
|
|
10
|
+
actions,
|
|
42
11
|
};
|
|
43
12
|
}
|
|
44
13
|
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
deduped.push(action);
|
|
14
|
+
function parsedToAction(command: ParsedShellCommand): ShellAction {
|
|
15
|
+
if (command.kind === "unknown") {
|
|
16
|
+
return { kind: "run", command: command.command };
|
|
17
|
+
}
|
|
18
|
+
if (command.kind === "list") {
|
|
19
|
+
return command.path ? { kind: "list", command: command.command, path: command.path } : { kind: "list", command: command.command };
|
|
20
|
+
}
|
|
21
|
+
if (command.kind === "search") {
|
|
22
|
+
return {
|
|
23
|
+
kind: "search",
|
|
24
|
+
command: command.command,
|
|
25
|
+
...(command.query ? { query: command.query } : {}),
|
|
26
|
+
...(command.path ? { path: command.path } : {}),
|
|
27
|
+
};
|
|
60
28
|
}
|
|
61
|
-
return
|
|
29
|
+
return { kind: "read", command: command.command, name: command.name, path: command.path };
|
|
62
30
|
}
|
package/src/shell/tokenize.ts
CHANGED
|
@@ -23,9 +23,19 @@ export function shellSplit(input: string): string[] {
|
|
|
23
23
|
continue;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
if (char === "\\"
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
if (char === "\\") {
|
|
27
|
+
if (!quote) {
|
|
28
|
+
escaping = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (quote === '"') {
|
|
32
|
+
if (next && (next === "\\" || next === '"' || next === "$" || next === "`")) {
|
|
33
|
+
escaping = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
current += char;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
if (quote) {
|
|
@@ -72,13 +82,26 @@ export function shellSplit(input: string): string[] {
|
|
|
72
82
|
return tokens;
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
export function shellQuote(token: string): string {
|
|
86
|
+
if (token.length === 0) return "''";
|
|
87
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(token)) return token;
|
|
88
|
+
return `'${token.replace(/'/g, `'"'"'`)}'`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function joinCommandTokens(tokens: string[]): string {
|
|
92
|
+
return tokens
|
|
93
|
+
.map((token) => (token === "&&" || token === "||" || token === "|" || token === ";" ? token : shellQuote(token)))
|
|
94
|
+
.join(" ");
|
|
95
|
+
}
|
|
96
|
+
|
|
75
97
|
export function normalizeTokens(tokens: string[]): string[] {
|
|
76
98
|
if (tokens.length >= 3 && (tokens[0] === "yes" || tokens[0] === "y" || tokens[0] === "no" || tokens[0] === "n") && tokens[1] === "|") {
|
|
77
99
|
return normalizeTokens(tokens.slice(2));
|
|
78
100
|
}
|
|
101
|
+
const shell = tokens[0]?.replace(/\\/g, "/").split("/").pop();
|
|
79
102
|
if (
|
|
80
103
|
tokens.length === 3 &&
|
|
81
|
-
(
|
|
104
|
+
(shell === "bash" || shell === "zsh" || shell === "sh") &&
|
|
82
105
|
(tokens[1] === "-c" || tokens[1] === "-lc")
|
|
83
106
|
) {
|
|
84
107
|
return normalizeTokens(shellSplit(tokens[2]));
|
|
@@ -109,7 +132,7 @@ export function shortDisplayPath(path: string): string {
|
|
|
109
132
|
const normalized = path.replace(/\\/g, "/").replace(/\/$/, "");
|
|
110
133
|
const parts = normalized
|
|
111
134
|
.split("/")
|
|
112
|
-
.filter((part) => part.length > 0 && part !== "
|
|
135
|
+
.filter((part) => part.length > 0 && part !== "src" && part !== "dist" && part !== "build" && part !== "node_modules");
|
|
113
136
|
return parts[parts.length - 1] ?? normalized;
|
|
114
137
|
}
|
|
115
138
|
|
|
@@ -3,6 +3,7 @@ import { spawn, type ChildProcessByStdio } from "node:child_process";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import type { Readable } from "node:stream";
|
|
5
5
|
import * as pty from "node-pty";
|
|
6
|
+
import { CODEX_FALLBACK_SHELL, getCodexRuntimeShell, isFishShell } from "../adapter/runtime-shell.ts";
|
|
6
7
|
|
|
7
8
|
export interface UnifiedExecResult {
|
|
8
9
|
chunk_id: string;
|
|
@@ -77,7 +78,60 @@ function resolveWorkdir(baseCwd: string, workdir?: string): string {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
function resolveShell(shell?: string): string {
|
|
80
|
-
return shell || process.env.SHELL
|
|
81
|
+
return getCodexRuntimeShell(shell || process.env.SHELL);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const BASH_SYNC_ENV_KEYS = [
|
|
85
|
+
"PATH",
|
|
86
|
+
"SHELL",
|
|
87
|
+
"HOME",
|
|
88
|
+
"XDG_CONFIG_HOME",
|
|
89
|
+
"XDG_DATA_HOME",
|
|
90
|
+
"XDG_CACHE_HOME",
|
|
91
|
+
"BUN_INSTALL",
|
|
92
|
+
"PNPM_HOME",
|
|
93
|
+
"MISE_DATA_DIR",
|
|
94
|
+
"MISE_CONFIG_DIR",
|
|
95
|
+
"MISE_SHIMS_DIR",
|
|
96
|
+
"CARGO_HOME",
|
|
97
|
+
"GOPATH",
|
|
98
|
+
"ANDROID_HOME",
|
|
99
|
+
"ANDROID_NDK_HOME",
|
|
100
|
+
"JAVA_HOME",
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
function shellEscape(value: string): string {
|
|
104
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
|
|
105
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function shouldSyncBashEnv(requestedShell: string | undefined, effectiveShell: string): boolean {
|
|
109
|
+
return effectiveShell === CODEX_FALLBACK_SHELL && isFishShell(requestedShell || process.env.SHELL);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildSyncedBashCommand(command: string, env: NodeJS.ProcessEnv): string {
|
|
113
|
+
const assignments: string[] = [];
|
|
114
|
+
for (const key of BASH_SYNC_ENV_KEYS) {
|
|
115
|
+
const value = key === "SHELL" ? CODEX_FALLBACK_SHELL : env[key];
|
|
116
|
+
if (typeof value !== "string") continue;
|
|
117
|
+
assignments.push(`export ${key}=${shellEscape(value)}`);
|
|
118
|
+
}
|
|
119
|
+
if (assignments.length === 0) return command;
|
|
120
|
+
return `${assignments.join("; ")}; ${command}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveExecution(requestedShell: string | undefined, command: string): { shell: string; command: string; env: NodeJS.ProcessEnv } {
|
|
124
|
+
const shell = resolveShell(requestedShell);
|
|
125
|
+
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
126
|
+
if (!shouldSyncBashEnv(requestedShell, shell)) {
|
|
127
|
+
return { shell, command, env };
|
|
128
|
+
}
|
|
129
|
+
env.SHELL = CODEX_FALLBACK_SHELL;
|
|
130
|
+
return {
|
|
131
|
+
shell,
|
|
132
|
+
command: buildSyncedBashCommand(command, env),
|
|
133
|
+
env,
|
|
134
|
+
};
|
|
81
135
|
}
|
|
82
136
|
|
|
83
137
|
function clampYieldTime(yieldTimeMs: number | undefined, fallback: number): number {
|
|
@@ -341,11 +395,12 @@ export function createExecSessionManager(): ExecSessionManager {
|
|
|
341
395
|
|
|
342
396
|
function createPipeSession(input: ExecCommandInput, workdir: string, shell: string, signal?: AbortSignal): PipeExecSession {
|
|
343
397
|
const login = input.login ?? true;
|
|
344
|
-
const
|
|
398
|
+
const execution = resolveExecution(input.shell, input.cmd);
|
|
399
|
+
const shellArgs = login ? ["-lc", execution.command] : ["-c", execution.command];
|
|
345
400
|
const child = spawn(shell, shellArgs, {
|
|
346
401
|
cwd: workdir,
|
|
347
402
|
stdio: ["ignore", "pipe", "pipe"],
|
|
348
|
-
env:
|
|
403
|
+
env: execution.env,
|
|
349
404
|
});
|
|
350
405
|
|
|
351
406
|
const session: PipeExecSession = {
|
|
@@ -387,10 +442,11 @@ export function createExecSessionManager(): ExecSessionManager {
|
|
|
387
442
|
|
|
388
443
|
function createPtySession(input: ExecCommandInput, workdir: string, shell: string, signal?: AbortSignal): PtyExecSession {
|
|
389
444
|
const login = input.login ?? true;
|
|
390
|
-
const
|
|
445
|
+
const execution = resolveExecution(input.shell, input.cmd);
|
|
446
|
+
const shellArgs = login ? ["-lc", execution.command] : ["-c", execution.command];
|
|
391
447
|
const child = pty.spawn(shell, shellArgs, {
|
|
392
448
|
cwd: workdir,
|
|
393
|
-
env:
|
|
449
|
+
env: execution.env,
|
|
394
450
|
name: process.env.TERM || "xterm-256color",
|
|
395
451
|
cols: 80,
|
|
396
452
|
rows: 24,
|