@oh-my-pi/pi-coding-agent 17.1.8 → 17.2.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 +92 -0
- package/dist/{CHANGELOG-vt8ene9g.md → CHANGELOG-be1f2t8h.md} +92 -0
- package/dist/cli.js +9986 -9776
- package/dist/{template-dys3vk5b.js → template-8vdv6xb4.js} +1 -1
- package/dist/types/advisor/runtime.d.ts +8 -2
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +63 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +2 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +12 -12
- package/src/advisor/runtime.ts +138 -12
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli.ts +9 -0
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-registry.ts +103 -25
- package/src/config/settings-schema.ts +65 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +12 -13
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +4 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +15 -9
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +31 -17
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/grep.md +7 -6
- package/src/sdk.ts +178 -15
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +302 -175
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +11 -3
- package/src/session/session-advisors.ts +140 -16
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +131 -3
- package/src/system-prompt.ts +3 -2
- package/src/task/executor.ts +17 -4
- package/src/task/render.ts +20 -4
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/browser.ts +5 -0
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +28 -4
- package/src/tools/path-utils.ts +88 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/write.ts +9 -3
- package/src/tools/xdev.ts +39 -14
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/types.ts +1 -1
|
@@ -97,7 +97,10 @@ function formatAvailableResources(mcpManager: MCPManager): string {
|
|
|
97
97
|
.getConnectedServers()
|
|
98
98
|
.flatMap(name => {
|
|
99
99
|
const serverResources = mcpManager.getServerResources(name);
|
|
100
|
-
|
|
100
|
+
if (!serverResources) return [];
|
|
101
|
+
const concrete = serverResources.resources.map(r => ` ${r.uri} (${name})`);
|
|
102
|
+
const templates = serverResources.templates.map(t => ` ${t.uriTemplate} (${name}, template)`);
|
|
103
|
+
return [...concrete, ...templates];
|
|
101
104
|
})
|
|
102
105
|
.join("\n");
|
|
103
106
|
return available || " (none)";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { workerHostEntry } from "@oh-my-pi/pi-utils/worker-host";
|
|
2
|
+
import type { TerminalOutputOptions } from "./terminal-output";
|
|
3
|
+
import {
|
|
4
|
+
TERMINAL_OUTPUT_WORKER_ARG,
|
|
5
|
+
type TerminalOutputWorkerRequest,
|
|
6
|
+
type TerminalOutputWorkerResult,
|
|
7
|
+
} from "./terminal-output-worker-protocol";
|
|
8
|
+
|
|
9
|
+
/** Replay legacy broker PTY bytes without evaluating xterm in the client process. */
|
|
10
|
+
export async function renderTerminalOutputIsolated(
|
|
11
|
+
output: string,
|
|
12
|
+
options: TerminalOutputOptions,
|
|
13
|
+
): Promise<string[] | undefined> {
|
|
14
|
+
const hostEntry = workerHostEntry();
|
|
15
|
+
const worker = hostEntry
|
|
16
|
+
? new Worker(hostEntry, { type: "module", argv: [TERMINAL_OUTPUT_WORKER_ARG] })
|
|
17
|
+
: new Worker(new URL("./terminal-output-worker.ts", import.meta.url).href, { type: "module" });
|
|
18
|
+
const pending = Promise.withResolvers<string[] | undefined>();
|
|
19
|
+
const onMessage = (event: MessageEvent<TerminalOutputWorkerResult>): void => {
|
|
20
|
+
if (event.data.ok) pending.resolve(event.data.rows);
|
|
21
|
+
else pending.reject(new Error(event.data.error));
|
|
22
|
+
};
|
|
23
|
+
const onError = (event: ErrorEvent): void => {
|
|
24
|
+
pending.reject(event.error instanceof Error ? event.error : new Error(event.message));
|
|
25
|
+
};
|
|
26
|
+
const onClose = (): void => {
|
|
27
|
+
pending.reject(new Error("Terminal output worker exited before responding"));
|
|
28
|
+
};
|
|
29
|
+
worker.addEventListener("message", onMessage);
|
|
30
|
+
worker.addEventListener("error", onError);
|
|
31
|
+
worker.addEventListener("close", onClose);
|
|
32
|
+
try {
|
|
33
|
+
const request: TerminalOutputWorkerRequest = { output, options };
|
|
34
|
+
worker.postMessage(request);
|
|
35
|
+
return await pending.promise;
|
|
36
|
+
} finally {
|
|
37
|
+
worker.removeEventListener("message", onMessage);
|
|
38
|
+
worker.removeEventListener("error", onError);
|
|
39
|
+
worker.removeEventListener("close", onClose);
|
|
40
|
+
worker.terminate();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Distribution smoke for source, npm-bundle, and compiled worker routing. */
|
|
45
|
+
export async function smokeTestTerminalOutputWorker(): Promise<void> {
|
|
46
|
+
const rows = await renderTerminalOutputIsolated("old\r\x1b[2K\x1b[1;32mready\x1b[0m", {
|
|
47
|
+
head: false,
|
|
48
|
+
maxRows: 10,
|
|
49
|
+
});
|
|
50
|
+
if (rows?.length !== 1 || rows[0] !== "\x1b[0m\x1b[1;38;5;2mready") {
|
|
51
|
+
throw new Error("terminal output worker smoke mismatch");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TerminalOutputOptions } from "./terminal-output";
|
|
2
|
+
|
|
3
|
+
/** Hidden CLI selector for legacy PTY replay outside the client process. */
|
|
4
|
+
export const TERMINAL_OUTPUT_WORKER_ARG = "__omp_worker_terminal_output";
|
|
5
|
+
|
|
6
|
+
export interface TerminalOutputWorkerRequest {
|
|
7
|
+
output: string;
|
|
8
|
+
options: TerminalOutputOptions;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type TerminalOutputWorkerResult = { ok: true; rows: string[] | undefined } | { ok: false; error: string };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { parentPort } from "node:worker_threads";
|
|
2
|
+
import { consumeWorkerInbox } from "@oh-my-pi/pi-utils/worker-host";
|
|
3
|
+
import { renderTerminalOutput } from "./terminal-output";
|
|
4
|
+
import type { TerminalOutputWorkerRequest, TerminalOutputWorkerResult } from "./terminal-output-worker-protocol";
|
|
5
|
+
|
|
6
|
+
if (!parentPort) throw new Error("terminal-output-worker: missing parentPort");
|
|
7
|
+
|
|
8
|
+
const port = parentPort;
|
|
9
|
+
const inbox = consumeWorkerInbox();
|
|
10
|
+
const handle = async (message: unknown): Promise<void> => {
|
|
11
|
+
const request = message as TerminalOutputWorkerRequest;
|
|
12
|
+
let result: TerminalOutputWorkerResult;
|
|
13
|
+
try {
|
|
14
|
+
result = { ok: true, rows: await renderTerminalOutput(request.output, request.options) };
|
|
15
|
+
} catch (error) {
|
|
16
|
+
result = { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
17
|
+
}
|
|
18
|
+
port.postMessage(result);
|
|
19
|
+
port.close();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (inbox) inbox.bind(message => void handle(message));
|
|
23
|
+
else port.on("message", message => void handle(message));
|
package/src/lsp/client.ts
CHANGED
|
@@ -27,6 +27,7 @@ const fileOperationLocks = new Map<string, Promise<void>>();
|
|
|
27
27
|
/** Negative cache of recent init failures so a broken server fails fast instead of re-spawning per call. */
|
|
28
28
|
const INIT_FAILURE_BACKOFF_MS = 3 * 60 * 1000;
|
|
29
29
|
const initFailures = new Map<string, { at: number; message: string }>();
|
|
30
|
+
const READER_EXIT_GRACE_MS = 100;
|
|
30
31
|
|
|
31
32
|
// Idle timeout configuration (disabled by default)
|
|
32
33
|
let idleTimeoutMs: number | null = null;
|
|
@@ -303,6 +304,7 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
303
304
|
|
|
304
305
|
const framer = new MessageFramer(Buffer.from(client.messageBuffer));
|
|
305
306
|
|
|
307
|
+
let readerFailed = false;
|
|
306
308
|
try {
|
|
307
309
|
while (true) {
|
|
308
310
|
const { done, value } = await reader.read();
|
|
@@ -391,6 +393,7 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
391
393
|
}
|
|
392
394
|
}
|
|
393
395
|
} catch (err) {
|
|
396
|
+
readerFailed = true;
|
|
394
397
|
// Connection closed or error - reject all pending requests
|
|
395
398
|
for (const pending of Array.from(client.pendingRequests.values())) {
|
|
396
399
|
pending.reject(new Error(`LSP connection closed: ${err}`));
|
|
@@ -401,6 +404,9 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
401
404
|
client.messageBuffer = framer.remainder();
|
|
402
405
|
reader.releaseLock();
|
|
403
406
|
client.isReading = false;
|
|
407
|
+
if (!readerFailed && client.proc.exitCode === null) {
|
|
408
|
+
await waitForExit(client, READER_EXIT_GRACE_MS);
|
|
409
|
+
}
|
|
404
410
|
// Reader exited while the server process is still alive (unrecoverable
|
|
405
411
|
// read error or bad stream state): nothing will route responses anymore,
|
|
406
412
|
// so tear the client down — the next call respawns instead of timing out.
|
|
@@ -676,6 +682,15 @@ const PROJECT_LOAD_TIMEOUT_MS = 15_000;
|
|
|
676
682
|
const SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
677
683
|
const EXIT_TIMEOUT_MS = 1_000;
|
|
678
684
|
|
|
685
|
+
function clientKey(config: ServerConfig, cwd: string): string {
|
|
686
|
+
return `${config.command}:${cwd}`;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** Allow an explicit user reload to retry a matching initialization failure immediately. */
|
|
690
|
+
export function clearInitializationFailure(config: ServerConfig, cwd: string): void {
|
|
691
|
+
initFailures.delete(clientKey(config, cwd));
|
|
692
|
+
}
|
|
693
|
+
|
|
679
694
|
/**
|
|
680
695
|
* Get or create an LSP client for the given server configuration and working directory.
|
|
681
696
|
* @param config - Server configuration
|
|
@@ -692,7 +707,7 @@ export async function getOrCreateClient(
|
|
|
692
707
|
initTimeoutMs?: number,
|
|
693
708
|
signal?: AbortSignal,
|
|
694
709
|
): Promise<LspClient> {
|
|
695
|
-
const key =
|
|
710
|
+
const key = clientKey(config, cwd);
|
|
696
711
|
|
|
697
712
|
// Check if client already exists
|
|
698
713
|
const existingClient = clients.get(key);
|
|
@@ -865,13 +880,13 @@ export async function getActiveOrPendingClient(
|
|
|
865
880
|
signal?: AbortSignal,
|
|
866
881
|
): Promise<LspClient | undefined> {
|
|
867
882
|
throwIfAborted(signal);
|
|
868
|
-
const client = clients.get(
|
|
883
|
+
const client = clients.get(clientKey(config, cwd));
|
|
869
884
|
if (client) {
|
|
870
885
|
client.lastActivity = Date.now();
|
|
871
886
|
return client;
|
|
872
887
|
}
|
|
873
888
|
|
|
874
|
-
const pending = clientLocks.get(
|
|
889
|
+
const pending = clientLocks.get(clientKey(config, cwd));
|
|
875
890
|
if (!pending) return undefined;
|
|
876
891
|
try {
|
|
877
892
|
return await untilAborted(signal, pending);
|
package/src/lsp/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { formatPathRelativeToCwd, resolveToCwd } from "../tools/path-utils";
|
|
|
17
17
|
import { ToolAbortError, ToolError, throwIfAborted } from "../tools/tool-errors";
|
|
18
18
|
import { clampTimeout } from "../tools/tool-timeouts";
|
|
19
19
|
import {
|
|
20
|
+
clearInitializationFailure,
|
|
20
21
|
ensureFileOpen,
|
|
21
22
|
FileChangeType,
|
|
22
23
|
getActiveClients,
|
|
@@ -2345,6 +2346,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2345
2346
|
const outputs: string[] = [];
|
|
2346
2347
|
for (const [workspaceServerName, workspaceServerConfig] of servers) {
|
|
2347
2348
|
throwIfAborted(signal);
|
|
2349
|
+
clearInitializationFailure(workspaceServerConfig, this.session.cwd);
|
|
2348
2350
|
try {
|
|
2349
2351
|
const workspaceClient = await getOrCreateClient(
|
|
2350
2352
|
workspaceServerConfig,
|
|
@@ -2377,6 +2379,8 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2377
2379
|
|
|
2378
2380
|
const [serverName, serverConfig] = serverInfo;
|
|
2379
2381
|
|
|
2382
|
+
if (action === "reload") clearInitializationFailure(serverConfig, this.session.cwd);
|
|
2383
|
+
|
|
2380
2384
|
try {
|
|
2381
2385
|
const client = await getOrCreateClient(serverConfig, this.session.cwd, undefined, signal);
|
|
2382
2386
|
const targetFile = resolvedFile;
|
package/src/lsp/render.ts
CHANGED
|
@@ -582,7 +582,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
582
582
|
for (let i = 0; i < lines.length; i++) {
|
|
583
583
|
const isLast = i === lines.length - 1;
|
|
584
584
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
585
|
-
output += `\n ${theme.fg("dim", branch)} ${lines[i]}`;
|
|
585
|
+
output += `\n ${theme.fg("dim", branch)} ${truncateToWidth(replaceTabs(lines[i]), TRUNCATE_LENGTHS.CONTENT)}`;
|
|
586
586
|
}
|
|
587
587
|
return output.split("\n");
|
|
588
588
|
}
|
package/src/main.ts
CHANGED
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
type ScopedModel,
|
|
42
42
|
} from "./config/model-resolver";
|
|
43
43
|
import { ModelsConfigFile } from "./config/models-config";
|
|
44
|
-
import { getDefault, type SettingPath, Settings, settings } from "./config/settings";
|
|
44
|
+
import { getDefault, type SettingPath, Settings, type SettingValue, settings } from "./config/settings";
|
|
45
45
|
import { initializeWithSettings } from "./discovery";
|
|
46
46
|
import {
|
|
47
47
|
clearPluginRootsAndCaches,
|
|
@@ -74,6 +74,13 @@ import {
|
|
|
74
74
|
import type { AgentSession } from "./session/agent-session";
|
|
75
75
|
import type { AuthStorage } from "./session/auth-storage";
|
|
76
76
|
import { describePendingToolCalls } from "./session/exit-diagnostics";
|
|
77
|
+
import {
|
|
78
|
+
createForeignSessionStore,
|
|
79
|
+
foreignSessionInfoToSessionInfo,
|
|
80
|
+
foreignSessionSourceName,
|
|
81
|
+
persistForeignSession,
|
|
82
|
+
} from "./session/foreign-session-import";
|
|
83
|
+
import type { ForeignSessionInfo, ForeignSessionSource, ForeignSessionStore } from "./session/foreign-session-store";
|
|
77
84
|
import { resolveResumableSession, type SessionInfo } from "./session/session-listing";
|
|
78
85
|
import { SessionManager } from "./session/session-manager";
|
|
79
86
|
import { executeBuiltinSlashCommand } from "./slash-commands/builtin-registry";
|
|
@@ -83,14 +90,7 @@ import { createPersistedSubagentReviverFactory } from "./task/persisted-revive";
|
|
|
83
90
|
import { createTelemetryExportConfig, initTelemetryExport, isTelemetryExportEnabled } from "./telemetry-export";
|
|
84
91
|
import { concreteThinkingLevel, parseConfiguredThinkingLevel } from "./thinking";
|
|
85
92
|
import type { LspStartupServerInfo } from "./tools";
|
|
86
|
-
import {
|
|
87
|
-
getChangelogPath,
|
|
88
|
-
parseChangelog,
|
|
89
|
-
parseChangelogVersion,
|
|
90
|
-
readLastChangelogVersion,
|
|
91
|
-
selectStartupChangelog,
|
|
92
|
-
writeLastChangelogVersion,
|
|
93
|
-
} from "./utils/changelog";
|
|
93
|
+
import { getChangelogPath, resolveStartupChangelogForDisplay, type StartupChangelogSelection } from "./utils/changelog";
|
|
94
94
|
import { EventBus } from "./utils/event-bus";
|
|
95
95
|
import { withTimeoutSignal } from "./utils/fetch-timeout";
|
|
96
96
|
|
|
@@ -408,7 +408,7 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
|
|
|
408
408
|
async function runInteractiveMode(
|
|
409
409
|
session: AgentSession,
|
|
410
410
|
version: string,
|
|
411
|
-
|
|
411
|
+
startupChangelog: StartupChangelogSelection | undefined,
|
|
412
412
|
notifs: (InteractiveModeNotify | null)[],
|
|
413
413
|
versionCheckPromise: Promise<string | undefined>,
|
|
414
414
|
initialMessages: string[],
|
|
@@ -426,7 +426,7 @@ async function runInteractiveMode(
|
|
|
426
426
|
const mode = new InteractiveMode(
|
|
427
427
|
session,
|
|
428
428
|
version,
|
|
429
|
-
|
|
429
|
+
startupChangelog,
|
|
430
430
|
setExtensionUIContext,
|
|
431
431
|
lspServers,
|
|
432
432
|
mcpManager,
|
|
@@ -565,6 +565,27 @@ export class SessionResolutionError extends Error {
|
|
|
565
565
|
}
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
+
function resolveForeignSessionSource(
|
|
569
|
+
parsed: Pick<Args, "continue" | "fork" | "fromClaude" | "fromCodex" | "noSession" | "resume">,
|
|
570
|
+
): ForeignSessionSource | undefined {
|
|
571
|
+
if (parsed.fromClaude && parsed.fromCodex) {
|
|
572
|
+
throw new SessionResolutionError("--from-claude and --from-codex cannot be used together");
|
|
573
|
+
}
|
|
574
|
+
const source = parsed.fromClaude ? "claude" : parsed.fromCodex ? "codex" : undefined;
|
|
575
|
+
if (!source) return undefined;
|
|
576
|
+
if (parsed.noSession) {
|
|
577
|
+
throw new SessionResolutionError(`--from-${source} requires session persistence`);
|
|
578
|
+
}
|
|
579
|
+
if (parsed.continue || parsed.resume || parsed.fork) {
|
|
580
|
+
throw new SessionResolutionError(`--from-${source} cannot be combined with --continue, --resume, or --fork`);
|
|
581
|
+
}
|
|
582
|
+
return source;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function isForeignSessionImport(parsed: Pick<Args, "fromClaude" | "fromCodex">): boolean {
|
|
586
|
+
return parsed.fromClaude === true || parsed.fromCodex === true;
|
|
587
|
+
}
|
|
588
|
+
|
|
568
589
|
type MissingCwdMoveResult =
|
|
569
590
|
| { status: "not-needed" }
|
|
570
591
|
| { status: "declined" }
|
|
@@ -651,33 +672,19 @@ async function resolveScopedModels(
|
|
|
651
672
|
);
|
|
652
673
|
}
|
|
653
674
|
|
|
654
|
-
async function getChangelogForDisplay(
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const lastVersion = await readLastChangelogVersion();
|
|
660
|
-
const parsedLastVersion = parseChangelogVersion(lastVersion);
|
|
661
|
-
if (!parsedLastVersion) {
|
|
662
|
-
await writeLastChangelogVersion(VERSION);
|
|
663
|
-
return undefined;
|
|
664
|
-
}
|
|
665
|
-
if (lastVersion === VERSION) {
|
|
666
|
-
// Steady state: user already saw the current version's changelog. Skip the file read + parse.
|
|
675
|
+
async function getChangelogForDisplay(
|
|
676
|
+
parsed: Args,
|
|
677
|
+
mode: SettingValue<"startup.changelogMode">,
|
|
678
|
+
): Promise<StartupChangelogSelection | undefined> {
|
|
679
|
+
if (parsed.continue || parsed.resume || isForeignSessionImport(parsed)) {
|
|
667
680
|
return undefined;
|
|
668
681
|
}
|
|
669
682
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
if (startupChangelog.markdown) {
|
|
677
|
-
return startupChangelog.markdown;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
return undefined;
|
|
683
|
+
return resolveStartupChangelogForDisplay({
|
|
684
|
+
mode,
|
|
685
|
+
currentVersion: VERSION,
|
|
686
|
+
changelogPath: getChangelogPath(),
|
|
687
|
+
});
|
|
681
688
|
}
|
|
682
689
|
|
|
683
690
|
const SESSION_ID_ARG_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -852,6 +859,7 @@ export async function buildSessionOptions(
|
|
|
852
859
|
cwd: parsed.cwd ?? getProjectDir(),
|
|
853
860
|
autoApprove: parsed.autoApprove ?? false,
|
|
854
861
|
};
|
|
862
|
+
const restoringSession = Boolean(parsed.continue || parsed.resume || isForeignSessionImport(parsed));
|
|
855
863
|
const cliDirs = parsed.addDir ?? [];
|
|
856
864
|
const settingsDirs = activeSettings.get("workspace.additionalDirectories");
|
|
857
865
|
if (cliDirs.length > 0 || settingsDirs.length > 0) {
|
|
@@ -882,7 +890,7 @@ export async function buildSessionOptions(
|
|
|
882
890
|
options.providerPromptCacheKeySource = "explicit";
|
|
883
891
|
} else {
|
|
884
892
|
const header = sessionManager?.getHeader();
|
|
885
|
-
const scopedModelOverride = scopedModels.length > 0 && !
|
|
893
|
+
const scopedModelOverride = scopedModels.length > 0 && !restoringSession;
|
|
886
894
|
const forkCacheShapeChanged =
|
|
887
895
|
scopedModelOverride ||
|
|
888
896
|
parsed.model !== undefined ||
|
|
@@ -939,7 +947,7 @@ export async function buildSessionOptions(
|
|
|
939
947
|
options.thinkingLevel = resolved.thinkingLevel;
|
|
940
948
|
}
|
|
941
949
|
}
|
|
942
|
-
} else if (scopedModels.length > 0 && !
|
|
950
|
+
} else if (scopedModels.length > 0 && !restoringSession) {
|
|
943
951
|
const remembered = activeSettings.getModelRole("default");
|
|
944
952
|
if (remembered) {
|
|
945
953
|
const rememberedSpec = resolveModelRoleValue(
|
|
@@ -1042,8 +1050,7 @@ export async function buildSessionOptions(
|
|
|
1042
1050
|
// thinking suffix) after extensions register; seeding the fallback
|
|
1043
1051
|
// scoped model's level here would override it in createAgentSession.
|
|
1044
1052
|
!deferredDefaultRole &&
|
|
1045
|
-
!
|
|
1046
|
-
!parsed.resume
|
|
1053
|
+
!restoringSession
|
|
1047
1054
|
) {
|
|
1048
1055
|
options.thinkingLevel = scopedModels[0].thinkingLevel;
|
|
1049
1056
|
}
|
|
@@ -1119,6 +1126,7 @@ interface RunRootCommandDependencies {
|
|
|
1119
1126
|
discoverAuthStorage?: typeof discoverAuthStorage;
|
|
1120
1127
|
selectSession?: typeof selectSession;
|
|
1121
1128
|
runAcpMode?: RunAcpMode;
|
|
1129
|
+
createForeignSessionStore?: (source: ForeignSessionSource) => ForeignSessionStore;
|
|
1122
1130
|
settings?: Settings;
|
|
1123
1131
|
forceSetupWizard?: boolean;
|
|
1124
1132
|
}
|
|
@@ -1282,19 +1290,77 @@ export async function runRootCommand(
|
|
|
1282
1290
|
// id from UUID-shaped values owned by later extension flags.
|
|
1283
1291
|
normalizeContinueSessionArgs(parsedArgs, rawArgs);
|
|
1284
1292
|
|
|
1285
|
-
//
|
|
1286
|
-
//
|
|
1287
|
-
// prompt, --fork with --no-session): print + exit cleanly instead of letting
|
|
1288
|
-
// it surface as `[Uncaught Exception]` (see issue #2084).
|
|
1293
|
+
// Resolve native resume/fork flags or import one foreign transcript into a
|
|
1294
|
+
// fresh persisted OMP session before constructing the AgentSession.
|
|
1289
1295
|
let sessionManager: SessionManager | undefined;
|
|
1296
|
+
let foreignSource: ForeignSessionSource | undefined;
|
|
1290
1297
|
try {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
+
foreignSource = resolveForeignSessionSource(parsedArgs);
|
|
1299
|
+
if (foreignSource) {
|
|
1300
|
+
if (isProtocolMode) {
|
|
1301
|
+
throw new SessionResolutionError(`--from-${foreignSource} is not supported in ${mode} mode`);
|
|
1302
|
+
}
|
|
1303
|
+
const sourceName = foreignSessionSourceName(foreignSource);
|
|
1304
|
+
const store = (deps.createForeignSessionStore ?? createForeignSessionStore)(foreignSource);
|
|
1305
|
+
let foreignSessions: ForeignSessionInfo[];
|
|
1306
|
+
try {
|
|
1307
|
+
foreignSessions = await logger.time(`list${sourceName}Sessions`, () => store.list());
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1310
|
+
throw new SessionResolutionError(`Failed to list ${sourceName} sessions: ${message}`);
|
|
1311
|
+
}
|
|
1312
|
+
if (foreignSessions.length === 0) {
|
|
1313
|
+
writeStartupNotice(parsedArgs, `${chalk.dim(`No ${sourceName} sessions found`)}\n`);
|
|
1314
|
+
stopStartupWatchdog();
|
|
1315
|
+
process.exit(0);
|
|
1316
|
+
}
|
|
1317
|
+
const choices = foreignSessions.map(foreignSessionInfoToSessionInfo);
|
|
1318
|
+
pauseStartupWatchdog();
|
|
1319
|
+
let selected: SessionInfo | null;
|
|
1320
|
+
try {
|
|
1321
|
+
selected = await logger.time(`select${sourceName}Session`, deps.selectSession ?? selectSession, choices, {
|
|
1322
|
+
title: `Import ${sourceName} Session`,
|
|
1323
|
+
scopeLabel: false,
|
|
1324
|
+
showCwd: true,
|
|
1325
|
+
allowDelete: false,
|
|
1326
|
+
allowGlobalScope: false,
|
|
1327
|
+
historySearch: false,
|
|
1328
|
+
});
|
|
1329
|
+
} finally {
|
|
1330
|
+
resumeStartupWatchdog();
|
|
1331
|
+
}
|
|
1332
|
+
if (!selected) {
|
|
1333
|
+
writeStartupNotice(parsedArgs, `${chalk.dim(`No ${sourceName} session selected`)}\n`);
|
|
1334
|
+
stopStartupWatchdog();
|
|
1335
|
+
process.exit(0);
|
|
1336
|
+
}
|
|
1337
|
+
const foreignSession = foreignSessions.find(
|
|
1338
|
+
session => session.id === selected.id && session.path === selected.path,
|
|
1339
|
+
);
|
|
1340
|
+
if (!foreignSession) {
|
|
1341
|
+
throw new SessionResolutionError(`Selected ${sourceName} session is no longer available`);
|
|
1342
|
+
}
|
|
1343
|
+
try {
|
|
1344
|
+
sessionManager = await logger.time(
|
|
1345
|
+
`import${sourceName}Session`,
|
|
1346
|
+
persistForeignSession,
|
|
1347
|
+
store,
|
|
1348
|
+
foreignSession,
|
|
1349
|
+
{ fallbackCwd: cwd, sessionDir: parsedArgs.sessionDir },
|
|
1350
|
+
);
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1353
|
+
throw new SessionResolutionError(`Failed to import ${sourceName} session: ${message}`);
|
|
1354
|
+
}
|
|
1355
|
+
} else {
|
|
1356
|
+
sessionManager = await logger.time(
|
|
1357
|
+
"createSessionManager",
|
|
1358
|
+
createSessionManager,
|
|
1359
|
+
parsedArgs,
|
|
1360
|
+
cwd,
|
|
1361
|
+
settingsInstance,
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1298
1364
|
} catch (error: unknown) {
|
|
1299
1365
|
if (error instanceof SessionResolutionError) {
|
|
1300
1366
|
process.stderr.write(`${chalk.red(`Error: ${error.message}`)}\n`);
|
|
@@ -1306,7 +1372,7 @@ export async function runRootCommand(
|
|
|
1306
1372
|
throw error;
|
|
1307
1373
|
}
|
|
1308
1374
|
|
|
1309
|
-
if (typeof parsedArgs.resume === "string" && sessionManager) {
|
|
1375
|
+
if ((typeof parsedArgs.resume === "string" || foreignSource) && sessionManager) {
|
|
1310
1376
|
const previousCwd = cwd;
|
|
1311
1377
|
cwd = await switchToResumedProject(sessionManager.getCwd(), settingsInstance, pluginPreloadPromise);
|
|
1312
1378
|
if (cwd !== previousCwd) {
|
|
@@ -1372,7 +1438,7 @@ export async function runRootCommand(
|
|
|
1372
1438
|
sessionManager = await SessionManager.open(selected.path);
|
|
1373
1439
|
}
|
|
1374
1440
|
|
|
1375
|
-
if (sessionManager && (parsedArgs.continue || parsedArgs.resume || parsedArgs.fork)) {
|
|
1441
|
+
if (sessionManager && (parsedArgs.continue || parsedArgs.resume || parsedArgs.fork || foreignSource)) {
|
|
1376
1442
|
const pendingToolWarning = describePendingToolCalls(sessionManager.getBranch());
|
|
1377
1443
|
if (pendingToolWarning) {
|
|
1378
1444
|
logger.warn("Resumed session has pending tool calls", {
|
|
@@ -1515,7 +1581,7 @@ export async function runRootCommand(
|
|
|
1515
1581
|
const showStartupSplash = shouldShowStartupSplash({
|
|
1516
1582
|
configured: settingsInstance.get("startup.showSplash"),
|
|
1517
1583
|
isInteractive,
|
|
1518
|
-
resuming: Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork),
|
|
1584
|
+
resuming: Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork || foreignSource),
|
|
1519
1585
|
quiet: settingsInstance.get("startup.quiet"),
|
|
1520
1586
|
timing: Boolean($env.PI_TIMING),
|
|
1521
1587
|
stdinIsTTY: process.stdin.isTTY,
|
|
@@ -1580,7 +1646,12 @@ export async function runRootCommand(
|
|
|
1580
1646
|
await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined, eventBus, rpcInput);
|
|
1581
1647
|
} else if (isInteractive) {
|
|
1582
1648
|
const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
|
|
1583
|
-
const
|
|
1649
|
+
const startupChangelog = await logger.time(
|
|
1650
|
+
"main:getChangelogForDisplay",
|
|
1651
|
+
getChangelogForDisplay,
|
|
1652
|
+
parsedArgs,
|
|
1653
|
+
settingsInstance.get("startup.changelogMode"),
|
|
1654
|
+
);
|
|
1584
1655
|
|
|
1585
1656
|
const modelScopeNotification = buildModelScopeNotification(
|
|
1586
1657
|
scopedModels,
|
|
@@ -1605,14 +1676,14 @@ export async function runRootCommand(
|
|
|
1605
1676
|
await runInteractiveMode(
|
|
1606
1677
|
session,
|
|
1607
1678
|
VERSION,
|
|
1608
|
-
|
|
1679
|
+
startupChangelog,
|
|
1609
1680
|
notifs,
|
|
1610
1681
|
versionCheckPromise,
|
|
1611
1682
|
initialArgs.messages,
|
|
1612
1683
|
setToolUIContext,
|
|
1613
1684
|
lspServers,
|
|
1614
1685
|
mcpManager,
|
|
1615
|
-
Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork),
|
|
1686
|
+
Boolean(parsedArgs.continue || parsedArgs.resume || parsedArgs.fork || foreignSource),
|
|
1616
1687
|
deps.forceSetupWizard === true,
|
|
1617
1688
|
showStartupSplash,
|
|
1618
1689
|
eventBus,
|