Skip to content

Commit b63eae9

Browse files
committed
fix(terminal): preserve history for startup chat commands
1 parent c657e54 commit b63eae9

5 files changed

Lines changed: 39 additions & 4 deletions

File tree

‎apps/server/src/terminal/Manager.ts‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ interface TerminalStartInput {
279279
readonly cols: number;
280280
readonly rows: number;
281281
readonly initialCommand?: string;
282+
readonly initialCommandShellHistory?: "preserve" | "suppress";
282283
}
283284

284285
interface TerminalSessionState {
@@ -608,18 +609,34 @@ function formatShellCandidate(candidate: ShellCandidate): string {
608609
}
609610

610611
function quotePosixShellArgument(value: string): string {
611-
return `'${value.replaceAll("'", `'\\''`)}'`;
612+
return `'${value.replaceAll("'", "'\"'\"'")}'`;
612613
}
613614

614615
function posixShellCandidateWithInitialCommand(
615616
candidate: ShellCandidate,
616617
initialCommand: string,
618+
shellHistory: "preserve" | "suppress" | undefined,
617619
): ShellCandidate {
618620
const baseArgs = candidate.args ?? [];
619621
const reenterShell = [candidate.shell, ...baseArgs].map(quotePosixShellArgument).join(" ");
622+
const shellName = basenameForPlatform(candidate.shell, "linux").toLowerCase();
623+
const quotedCommand = quotePosixShellArgument(initialCommand);
624+
const recordHistory =
625+
shellHistory !== "preserve"
626+
? ""
627+
: shellName === "bash"
628+
? `history -s -- ${quotedCommand}; history -a\n`
629+
: shellName === "zsh"
630+
? `print -s -- ${quotedCommand}; fc -AI\n`
631+
: "";
620632
return {
621633
shell: candidate.shell,
622-
args: [...baseArgs, "-i", "-c", `trap ':' INT\n${initialCommand}\nexec ${reenterShell}`],
634+
args: [
635+
...baseArgs,
636+
"-i",
637+
"-c",
638+
`trap ':' INT\n${recordHistory}${initialCommand}\nexec ${reenterShell}`,
639+
],
623640
};
624641
}
625642

@@ -2495,7 +2512,11 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func
24952512
) {
24962513
initialCommandHandled = true;
24972514
shellCandidates = resolvedShellCandidates.map((candidate) =>
2498-
posixShellCandidateWithInitialCommand(candidate, initialCommand),
2515+
posixShellCandidateWithInitialCommand(
2516+
candidate,
2517+
initialCommand,
2518+
input.initialCommandShellHistory,
2519+
),
24992520
);
25002521
}
25012522
const terminalEnv = createTerminalSpawnEnv(
@@ -2953,6 +2974,9 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func
29532974
cols,
29542975
rows,
29552976
...(input.initialCommand ? { initialCommand: input.initialCommand } : {}),
2977+
...(input.initialCommandShellHistory
2978+
? { initialCommandShellHistory: input.initialCommandShellHistory }
2979+
: {}),
29562980
},
29572981
"started",
29582982
);
@@ -3004,6 +3028,9 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func
30043028
cols: targetCols,
30053029
rows: targetRows,
30063030
...(input.initialCommand ? { initialCommand: input.initialCommand } : {}),
3031+
...(input.initialCommandShellHistory
3032+
? { initialCommandShellHistory: input.initialCommandShellHistory }
3033+
: {}),
30073034
},
30083035
"started",
30093036
);

‎apps/web/src/components/chat/useChatTerminalActions.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ export function useChatTerminalActions(input: ChatTerminalActionsInput) {
257257
location: { cwd: targetCwd, worktreePath: targetWorktreePath },
258258
stopOnSettle: script.stopOnSettle ?? false,
259259
...(options?.env ? { extraEnv: options.env } : {}),
260-
initialCommand: script.command,
260+
...(script.command.length <= 65_536 ? { initialCommand: script.command } : {}),
261+
...(options?.shellHistory ? { initialCommandShellHistory: options.shellHistory } : {}),
261262
});
262263
if (!launched) return;
263264
const targetTerminalId = launched.terminalId;

‎apps/web/src/components/terminal/useThreadTerminalActions.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export function useThreadTerminalActions(threadRef: ScopedThreadRef | null) {
9090
readonly stopOnSettle?: boolean;
9191
readonly size?: { readonly cols: number; readonly rows: number };
9292
readonly initialCommand?: string;
93+
readonly initialCommandShellHistory?: "preserve" | "suppress";
9394
}) => {
9495
if (!threadRef || !canWrite() || !project) return null;
9596
const location = input.location ?? defaultLaunchLocation();
@@ -111,6 +112,9 @@ export function useThreadTerminalActions(threadRef: ScopedThreadRef | null) {
111112
...input.size,
112113
...(input.stopOnSettle !== undefined ? { stopOnSettle: input.stopOnSettle } : {}),
113114
...(input.initialCommand !== undefined ? { initialCommand: input.initialCommand } : {}),
115+
...(input.initialCommandShellHistory
116+
? { initialCommandShellHistory: input.initialCommandShellHistory }
117+
: {}),
114118
},
115119
});
116120
return { terminalId, result };

‎packages/contracts/src/terminal.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ describe("TerminalOpenInput", () => {
5555
terminalId: DEFAULT_TERMINAL_ID,
5656
cwd: "/tmp/project",
5757
initialCommand: "npm run dev",
58+
initialCommandShellHistory: "preserve",
5859
});
5960
expect(parsed.initialCommand).toBe("npm run dev");
61+
expect(parsed.initialCommandShellHistory).toBe("preserve");
6062
});
6163
});

‎packages/contracts/src/terminal.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const TerminalOpenInput = Schema.Struct({
4242
env: Schema.optional(TerminalEnvSchema),
4343
providerInstanceId: Schema.optional(ProviderInstanceId),
4444
stopOnSettle: Schema.optional(Schema.Boolean),
45+
initialCommandShellHistory: Schema.optional(Schema.Literals(["preserve", "suppress"])),
4546
/** Ask the server to run a command during shell startup when supported. */
4647
initialCommand: Schema.optional(
4748
Schema.String.check(Schema.isNonEmpty()).check(Schema.isMaxLength(65_536)),

0 commit comments

Comments
 (0)