@oh-my-pi/pi-coding-agent 15.7.5 → 15.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 +159 -182
- package/dist/types/async/job-manager.d.ts +3 -3
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/claude-trace-cli.d.ts +54 -0
- package/dist/types/cli/session-picker.d.ts +10 -3
- package/dist/types/cli/update-cli.d.ts +17 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +11 -2
- package/dist/types/config/settings.d.ts +13 -0
- package/dist/types/eval/__tests__/heartbeat.test.d.ts +1 -0
- package/dist/types/eval/concurrency-bridge.d.ts +26 -0
- package/dist/types/eval/heartbeat.d.ts +45 -0
- package/dist/types/eval/js/tool-bridge.d.ts +2 -1
- package/dist/types/extensibility/extensions/loader.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +24 -5
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +11 -0
- package/dist/types/lsp/diagnostics-ledger.d.ts +10 -0
- package/dist/types/lsp/index.d.ts +2 -0
- package/dist/types/lsp/utils.d.ts +4 -0
- package/dist/types/main.d.ts +5 -0
- package/dist/types/modes/acp/acp-agent.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/custom-editor.d.ts +3 -2
- package/dist/types/modes/components/hook-selector.d.ts +10 -1
- package/dist/types/modes/components/session-selector.d.ts +32 -5
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +3 -3
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -3
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +5 -3
- package/dist/types/registry/agent-registry.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +12 -3
- package/dist/types/session/history-storage.d.ts +16 -1
- package/dist/types/session/session-manager.d.ts +4 -0
- package/dist/types/task/output-manager.d.ts +6 -15
- package/dist/types/tools/ask.d.ts +8 -6
- package/dist/types/tools/eval-backends.d.ts +12 -0
- package/dist/types/tools/eval-render.d.ts +52 -0
- package/dist/types/tools/eval.d.ts +2 -35
- package/dist/types/tools/find.d.ts +0 -9
- package/dist/types/tools/index.d.ts +5 -12
- package/dist/types/tools/path-utils.d.ts +16 -0
- package/dist/types/tools/sqlite-reader.d.ts +25 -8
- package/dist/types/tui/output-block.d.ts +4 -3
- package/dist/types/utils/clipboard.d.ts +4 -0
- package/dist/types/web/kagi.d.ts +76 -0
- package/dist/types/web/search/providers/exa.d.ts +7 -1
- package/dist/types/web/search/providers/kagi.d.ts +1 -0
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/thinking-note.ts +13 -0
- package/package.json +9 -9
- package/src/async/job-manager.ts +3 -3
- package/src/cli/args.ts +6 -2
- package/src/cli/claude-trace-cli.ts +783 -0
- package/src/cli/session-picker.ts +36 -10
- package/src/cli/update-cli.ts +35 -2
- package/src/commands/launch.ts +3 -0
- package/src/config/keybindings.ts +6 -0
- package/src/config/model-registry.ts +33 -4
- package/src/config/settings-schema.ts +12 -2
- package/src/config/settings.ts +23 -0
- package/src/discovery/claude-plugins.ts +7 -9
- package/src/discovery/claude.ts +41 -22
- package/src/edit/index.ts +23 -3
- package/src/eval/__tests__/agent-bridge.test.ts +148 -4
- package/src/eval/__tests__/heartbeat.test.ts +84 -0
- package/src/eval/__tests__/llm-bridge.test.ts +30 -0
- package/src/eval/agent-bridge.ts +44 -38
- package/src/eval/concurrency-bridge.ts +34 -0
- package/src/eval/heartbeat.ts +74 -0
- package/src/eval/js/executor.ts +13 -9
- package/src/eval/js/shared/prelude.txt +20 -17
- package/src/eval/js/tool-bridge.ts +5 -0
- package/src/eval/llm-bridge.ts +20 -14
- package/src/eval/py/executor.ts +14 -18
- package/src/eval/py/prelude.py +23 -15
- package/src/exec/bash-executor.ts +31 -5
- package/src/extensibility/extensions/loader.ts +16 -18
- package/src/extensibility/extensions/runner.ts +22 -17
- package/src/extensibility/extensions/types.ts +39 -5
- package/src/extensibility/plugins/legacy-pi-compat.ts +115 -131
- package/src/extensibility/skills.ts +0 -1
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/index.ts +9 -22
- package/src/lsp/utils.ts +21 -0
- package/src/main.ts +92 -24
- package/src/modes/acp/acp-agent.ts +8 -4
- package/src/modes/acp/acp-event-mapper.ts +54 -4
- package/src/modes/components/assistant-message.ts +28 -1
- package/src/modes/components/custom-editor.ts +19 -7
- package/src/modes/components/hook-selector.ts +229 -44
- package/src/modes/components/oauth-selector.ts +12 -6
- package/src/modes/components/session-selector.ts +179 -24
- package/src/modes/components/tool-execution.ts +36 -7
- package/src/modes/controllers/command-controller.ts +2 -11
- package/src/modes/controllers/event-controller.ts +5 -2
- package/src/modes/controllers/extension-ui-controller.ts +6 -4
- package/src/modes/controllers/input-controller.ts +19 -16
- package/src/modes/controllers/selector-controller.ts +61 -21
- package/src/modes/interactive-mode.ts +127 -16
- package/src/modes/rpc/rpc-mode.ts +17 -6
- package/src/modes/theme/theme-schema.json +30 -0
- package/src/modes/theme/theme.ts +39 -2
- package/src/modes/types.ts +7 -3
- package/src/modes/utils/ui-helpers.ts +5 -2
- package/src/prompts/system/orchestrate-notice.md +5 -3
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/ask.md +2 -1
- package/src/prompts/tools/eval.md +6 -6
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/irc.md +6 -6
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -1
- package/src/registry/agent-registry.ts +1 -1
- package/src/sdk.ts +85 -31
- package/src/session/agent-session.ts +127 -57
- package/src/session/history-storage.ts +56 -12
- package/src/session/session-manager.ts +34 -0
- package/src/task/output-manager.ts +40 -48
- package/src/task/render.ts +3 -8
- package/src/tools/ask.ts +74 -32
- package/src/tools/browser/tab-worker.ts +8 -5
- package/src/tools/eval-backends.ts +38 -0
- package/src/tools/eval-render.ts +750 -0
- package/src/tools/eval.ts +27 -754
- package/src/tools/find.ts +5 -29
- package/src/tools/index.ts +8 -38
- package/src/tools/path-utils.ts +144 -1
- package/src/tools/read.ts +47 -0
- package/src/tools/renderers.ts +1 -1
- package/src/tools/search.ts +2 -27
- package/src/tools/sqlite-reader.ts +92 -9
- package/src/tools/write.ts +9 -1
- package/src/tui/output-block.ts +5 -4
- package/src/utils/clipboard.ts +38 -1
- package/src/utils/open.ts +37 -2
- package/src/web/kagi.ts +168 -49
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/exa.ts +20 -86
- package/src/web/search/providers/kagi.ts +4 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { FileDiagnosticsResult } from "./index";
|
|
2
|
+
import { summarizeDiagnosticMessages } from "./utils";
|
|
3
|
+
|
|
4
|
+
const DIAGNOSTIC_LOCATION_PREFIX_RE = /^.*?:\d+:\d+\s+/;
|
|
5
|
+
|
|
6
|
+
export function diagnosticIdentity(message: string): string {
|
|
7
|
+
return message.replace(DIAGNOSTIC_LOCATION_PREFIX_RE, "");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class DiagnosticsLedger {
|
|
11
|
+
readonly #seen = new Map<string, Set<string>>();
|
|
12
|
+
|
|
13
|
+
reduce(absPath: string, result: FileDiagnosticsResult): FileDiagnosticsResult {
|
|
14
|
+
const previous = this.#seen.get(absPath);
|
|
15
|
+
const currentIdentities = new Set<string>();
|
|
16
|
+
const fresh: string[] = [];
|
|
17
|
+
|
|
18
|
+
for (const message of result.messages) {
|
|
19
|
+
const identity = diagnosticIdentity(message);
|
|
20
|
+
currentIdentities.add(identity);
|
|
21
|
+
if (!previous?.has(identity)) {
|
|
22
|
+
fresh.push(message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (currentIdentities.size === 0) {
|
|
27
|
+
this.#seen.delete(absPath);
|
|
28
|
+
} else {
|
|
29
|
+
this.#seen.set(absPath, currentIdentities);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (fresh.length === result.messages.length) {
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
...result,
|
|
38
|
+
messages: fresh,
|
|
39
|
+
...summarizeDiagnosticMessages(fresh),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DiagnosticsLedgerOwner {
|
|
45
|
+
diagnosticsLedger?: DiagnosticsLedger;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getDiagnosticsLedger(owner: DiagnosticsLedgerOwner): DiagnosticsLedger {
|
|
49
|
+
owner.diagnosticsLedger ??= new DiagnosticsLedger();
|
|
50
|
+
return owner.diagnosticsLedger;
|
|
51
|
+
}
|
package/src/lsp/index.ts
CHANGED
|
@@ -79,6 +79,7 @@ import {
|
|
|
79
79
|
resolveDiagnosticTargets,
|
|
80
80
|
resolveSymbolColumn,
|
|
81
81
|
sortDiagnostics,
|
|
82
|
+
summarizeDiagnosticMessages,
|
|
82
83
|
symbolKindToIcon,
|
|
83
84
|
uriToFile,
|
|
84
85
|
} from "./utils";
|
|
@@ -816,12 +817,15 @@ export interface WritethroughOptions {
|
|
|
816
817
|
onDeferredDiagnostics?: (diagnostics: FileDiagnosticsResult) => void;
|
|
817
818
|
/** Signal to cancel a pending deferred diagnostics fetch. */
|
|
818
819
|
deferredSignal?: AbortSignal;
|
|
820
|
+
/** Transform diagnostics before surfacing them after a successful fetch. */
|
|
821
|
+
transformDiagnostics?: (absPath: string, result: FileDiagnosticsResult) => FileDiagnosticsResult;
|
|
819
822
|
}
|
|
820
823
|
|
|
821
824
|
/** Internal resolved form of {@link WritethroughOptions} that the writethrough machinery operates on. */
|
|
822
825
|
type ResolvedWritethroughOptions = {
|
|
823
826
|
enableFormat: boolean;
|
|
824
827
|
enableDiagnostics: boolean;
|
|
828
|
+
transformDiagnostics?: (absPath: string, result: FileDiagnosticsResult) => FileDiagnosticsResult;
|
|
825
829
|
};
|
|
826
830
|
|
|
827
831
|
/** Per-file deferred LSP diagnostics wiring for {@link WritethroughCallback}. */
|
|
@@ -881,6 +885,7 @@ function getOrCreateWritethroughBatch(id: string, options: ResolvedWritethroughO
|
|
|
881
885
|
if (existing) {
|
|
882
886
|
existing.options.enableFormat ||= options.enableFormat;
|
|
883
887
|
existing.options.enableDiagnostics ||= options.enableDiagnostics;
|
|
888
|
+
existing.options.transformDiagnostics ??= options.transformDiagnostics;
|
|
884
889
|
return existing;
|
|
885
890
|
}
|
|
886
891
|
const batch: LspWritethroughBatchState = {
|
|
@@ -904,27 +909,6 @@ export async function flushLspWritethroughBatch(
|
|
|
904
909
|
return flushWritethroughBatch(Array.from(state.entries.values()), cwd, state.options, signal);
|
|
905
910
|
}
|
|
906
911
|
|
|
907
|
-
function summarizeDiagnosticMessages(messages: string[]): { summary: string; errored: boolean } {
|
|
908
|
-
const counts = { error: 0, warning: 0, info: 0, hint: 0 };
|
|
909
|
-
for (const message of messages) {
|
|
910
|
-
const match = message.match(/\[(error|warning|info|hint)\]/i);
|
|
911
|
-
if (!match) continue;
|
|
912
|
-
const key = match[1].toLowerCase() as keyof typeof counts;
|
|
913
|
-
counts[key] += 1;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
const parts: string[] = [];
|
|
917
|
-
if (counts.error > 0) parts.push(`${counts.error} error(s)`);
|
|
918
|
-
if (counts.warning > 0) parts.push(`${counts.warning} warning(s)`);
|
|
919
|
-
if (counts.info > 0) parts.push(`${counts.info} info(s)`);
|
|
920
|
-
if (counts.hint > 0) parts.push(`${counts.hint} hint(s)`);
|
|
921
|
-
|
|
922
|
-
return {
|
|
923
|
-
summary: parts.length > 0 ? parts.join(", ") : "no issues",
|
|
924
|
-
errored: counts.error > 0,
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
|
|
928
912
|
function mergeDiagnostics(
|
|
929
913
|
results: Array<FileDiagnosticsResult | undefined>,
|
|
930
914
|
options: ResolvedWritethroughOptions,
|
|
@@ -1083,12 +1067,14 @@ async function runLspWritethrough(
|
|
|
1083
1067
|
|
|
1084
1068
|
// 6. Get diagnostics from all servers (wait for fresh results)
|
|
1085
1069
|
if (enableDiagnostics) {
|
|
1086
|
-
|
|
1070
|
+
const fetched = await getDiagnosticsForFile(dst, cwd, servers, {
|
|
1087
1071
|
signal: operationSignal,
|
|
1088
1072
|
minVersions,
|
|
1089
1073
|
expectedDocumentVersions,
|
|
1090
1074
|
allowUnversionedLspDiagnostics: false,
|
|
1091
1075
|
});
|
|
1076
|
+
diagnostics =
|
|
1077
|
+
fetched && options.transformDiagnostics ? options.transformDiagnostics(dst, fetched) : fetched;
|
|
1092
1078
|
}
|
|
1093
1079
|
});
|
|
1094
1080
|
} catch {
|
|
@@ -1155,6 +1141,7 @@ export function createLspWritethrough(cwd: string, options?: WritethroughOptions
|
|
|
1155
1141
|
const resolvedOptions: ResolvedWritethroughOptions = {
|
|
1156
1142
|
enableFormat: options?.enableFormat ?? false,
|
|
1157
1143
|
enableDiagnostics: options?.enableDiagnostics ?? false,
|
|
1144
|
+
transformDiagnostics: options?.transformDiagnostics,
|
|
1158
1145
|
};
|
|
1159
1146
|
if (!resolvedOptions.enableFormat && !resolvedOptions.enableDiagnostics) {
|
|
1160
1147
|
return writethroughNoop;
|
package/src/lsp/utils.ts
CHANGED
|
@@ -221,6 +221,27 @@ export function formatDiagnosticsSummary(diagnostics: Diagnostic[]): string {
|
|
|
221
221
|
return parts.length > 0 ? parts.join(", ") : "no issues";
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
export function summarizeDiagnosticMessages(messages: string[]): { summary: string; errored: boolean } {
|
|
225
|
+
const counts = { error: 0, warning: 0, info: 0, hint: 0 };
|
|
226
|
+
for (const message of messages) {
|
|
227
|
+
const match = message.match(/\[(error|warning|info|hint)\]/i);
|
|
228
|
+
if (!match) continue;
|
|
229
|
+
const key = match[1].toLowerCase() as keyof typeof counts;
|
|
230
|
+
counts[key] += 1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const parts: string[] = [];
|
|
234
|
+
if (counts.error > 0) parts.push(`${counts.error} error(s)`);
|
|
235
|
+
if (counts.warning > 0) parts.push(`${counts.warning} warning(s)`);
|
|
236
|
+
if (counts.info > 0) parts.push(`${counts.info} info(s)`);
|
|
237
|
+
if (counts.hint > 0) parts.push(`${counts.hint} hint(s)`);
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
summary: parts.length > 0 ? parts.join(", ") : "no issues",
|
|
241
|
+
errored: counts.error > 0,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
224
245
|
// =============================================================================
|
|
225
246
|
// Location Formatting
|
|
226
247
|
// =============================================================================
|
package/src/main.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
VERSION,
|
|
22
22
|
} from "@oh-my-pi/pi-utils";
|
|
23
23
|
import chalk from "chalk";
|
|
24
|
+
import { reset as resetCapabilities } from "./capability";
|
|
24
25
|
import type { Args } from "./cli/args";
|
|
25
26
|
import { applyExtensionFlags, type ExtensionFlagSink } from "./cli/extension-flags";
|
|
26
27
|
import { processFileArguments } from "./cli/file-processor";
|
|
@@ -91,15 +92,11 @@ async function checkForNewVersion(currentVersion: string): Promise<string | unde
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
const
|
|
95
|
+
const HOST_DEFAULTED_SETTING_PATHS: SettingPath[] = [
|
|
95
96
|
"todo.enabled",
|
|
96
97
|
"todo.reminders",
|
|
97
98
|
"todo.reminders.max",
|
|
98
99
|
"todo.eager",
|
|
99
|
-
"async.enabled",
|
|
100
|
-
"async.maxJobs",
|
|
101
|
-
"bash.autoBackground.enabled",
|
|
102
|
-
"bash.autoBackground.thresholdMs",
|
|
103
100
|
"task.isolation.mode",
|
|
104
101
|
"task.isolation.merge",
|
|
105
102
|
"task.isolation.commits",
|
|
@@ -109,18 +106,34 @@ const RPC_DEFAULTED_SETTING_PATHS: SettingPath[] = [
|
|
|
109
106
|
"task.maxRecursionDepth",
|
|
110
107
|
"task.disabledAgents",
|
|
111
108
|
"task.agentModelOverrides",
|
|
112
|
-
// Memory subsystems are off-by-default for RPC hosts; embedders that want
|
|
109
|
+
// Memory subsystems are off-by-default for RPC/ACP hosts; embedders that want
|
|
113
110
|
// memory should opt in explicitly through their own settings layer.
|
|
114
111
|
"memory.backend",
|
|
115
112
|
"memories.enabled",
|
|
116
113
|
];
|
|
117
114
|
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
const RPC_BACKGROUND_DEFAULTED_SETTING_PATHS: SettingPath[] = [
|
|
116
|
+
"async.enabled",
|
|
117
|
+
"async.maxJobs",
|
|
118
|
+
"bash.autoBackground.enabled",
|
|
119
|
+
"bash.autoBackground.thresholdMs",
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
function applyDefaultSettingOverrides(settingPaths: SettingPath[], targetSettings: Settings): void {
|
|
123
|
+
for (const settingPath of settingPaths) {
|
|
120
124
|
targetSettings.override(settingPath, getDefault(settingPath));
|
|
121
125
|
}
|
|
122
126
|
}
|
|
123
127
|
|
|
128
|
+
function applyRpcDefaultSettingOverrides(targetSettings: Settings = settings): void {
|
|
129
|
+
applyDefaultSettingOverrides(HOST_DEFAULTED_SETTING_PATHS, targetSettings);
|
|
130
|
+
applyDefaultSettingOverrides(RPC_BACKGROUND_DEFAULTED_SETTING_PATHS, targetSettings);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function applyAcpDefaultSettingOverrides(targetSettings: Settings = settings): void {
|
|
134
|
+
applyDefaultSettingOverrides(HOST_DEFAULTED_SETTING_PATHS, targetSettings);
|
|
135
|
+
}
|
|
136
|
+
|
|
124
137
|
async function readPipedInput(): Promise<string | undefined> {
|
|
125
138
|
if (process.stdin.isTTY !== false) return undefined;
|
|
126
139
|
try {
|
|
@@ -312,15 +325,19 @@ async function runInteractiveMode(
|
|
|
312
325
|
}
|
|
313
326
|
}
|
|
314
327
|
|
|
315
|
-
|
|
328
|
+
type ForkSessionPromptResult = "accepted" | "declined" | "unavailable";
|
|
329
|
+
|
|
330
|
+
type ForkSessionPrompt = (session: SessionInfo) => Promise<ForkSessionPromptResult>;
|
|
331
|
+
|
|
332
|
+
async function promptForkSession(session: SessionInfo): Promise<ForkSessionPromptResult> {
|
|
316
333
|
if (!process.stdin.isTTY) {
|
|
317
|
-
return
|
|
334
|
+
return "unavailable";
|
|
318
335
|
}
|
|
319
336
|
const message = `Session found in different project: ${session.cwd}. Fork into current directory? [y/N] `;
|
|
320
337
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
321
338
|
try {
|
|
322
339
|
const answer = (await rl.question(message)).trim().toLowerCase();
|
|
323
|
-
return answer === "y" || answer === "yes";
|
|
340
|
+
return answer === "y" || answer === "yes" ? "accepted" : "declined";
|
|
324
341
|
} finally {
|
|
325
342
|
rl.close();
|
|
326
343
|
}
|
|
@@ -366,10 +383,12 @@ async function flushChangelogVersion(): Promise<void> {
|
|
|
366
383
|
}
|
|
367
384
|
}
|
|
368
385
|
|
|
369
|
-
|
|
386
|
+
/** Resolves CLI session flags into an existing, forked, in-memory, or cancelled session manager. */
|
|
387
|
+
export async function createSessionManager(
|
|
370
388
|
parsed: Args,
|
|
371
389
|
cwd: string,
|
|
372
390
|
activeSettings: Settings = settings,
|
|
391
|
+
askToForkSession: ForkSessionPrompt = promptForkSession,
|
|
373
392
|
): Promise<SessionManager | undefined> {
|
|
374
393
|
if (parsed.fork) {
|
|
375
394
|
if (parsed.noSession) {
|
|
@@ -402,9 +421,17 @@ async function createSessionManager(
|
|
|
402
421
|
const normalizedCwd = normalizePathForComparison(cwd);
|
|
403
422
|
const normalizedMatchCwd = normalizePathForComparison(match.session.cwd || cwd);
|
|
404
423
|
if (normalizedCwd !== normalizedMatchCwd) {
|
|
405
|
-
const
|
|
406
|
-
if (
|
|
407
|
-
throw new Error(
|
|
424
|
+
const forkPromptResult = await askToForkSession(match.session);
|
|
425
|
+
if (forkPromptResult === "unavailable") {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`Session "${sessionArg}" is in another project (${match.session.cwd}); run interactively to fork it into the current project.`,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (forkPromptResult === "declined") {
|
|
431
|
+
// User declined the cross-project fork prompt. Caller distinguishes
|
|
432
|
+
// this cancellation from the "default new session" undefined return
|
|
433
|
+
// by checking `typeof parsed.resume === "string"`.
|
|
434
|
+
return undefined;
|
|
408
435
|
}
|
|
409
436
|
return await SessionManager.forkFrom(match.session.path, cwd, parsed.sessionDir);
|
|
410
437
|
}
|
|
@@ -776,15 +803,17 @@ export async function runRootCommand(
|
|
|
776
803
|
}
|
|
777
804
|
}
|
|
778
805
|
|
|
779
|
-
|
|
806
|
+
let cwd = getProjectDir();
|
|
780
807
|
const settingsInstance = deps.settings ?? (await logger.time("settings:init", Settings.init, { cwd }));
|
|
781
808
|
if (parsedArgs.approvalMode) {
|
|
782
809
|
// Runtime override (not persisted): every settings.get("tools.approvalMode") downstream
|
|
783
810
|
// sees this value. The wrapper still honours --auto-approve / --yolo on top of it.
|
|
784
811
|
settingsInstance.override("tools.approvalMode", parsedArgs.approvalMode);
|
|
785
812
|
}
|
|
786
|
-
if (parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui"
|
|
813
|
+
if (parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui") {
|
|
787
814
|
applyRpcDefaultSettingOverrides(settingsInstance);
|
|
815
|
+
} else if (parsedArgs.mode === "acp") {
|
|
816
|
+
applyAcpDefaultSettingOverrides(settingsInstance);
|
|
788
817
|
}
|
|
789
818
|
if (parsedArgs.noPty || parsedArgs.mode === "rpc-ui") {
|
|
790
819
|
Bun.env.PI_NO_PTY = "1";
|
|
@@ -812,6 +841,11 @@ export async function runRootCommand(
|
|
|
812
841
|
});
|
|
813
842
|
}
|
|
814
843
|
|
|
844
|
+
// Apply --hide-thinking CLI flag (ephemeral, not persisted)
|
|
845
|
+
if (parsedArgs.hideThinking) {
|
|
846
|
+
settingsInstance.override("hideThinkingBlock", true);
|
|
847
|
+
}
|
|
848
|
+
|
|
815
849
|
await logger.time(
|
|
816
850
|
"initTheme:final",
|
|
817
851
|
initTheme,
|
|
@@ -846,19 +880,53 @@ export async function runRootCommand(
|
|
|
846
880
|
settingsInstance,
|
|
847
881
|
);
|
|
848
882
|
|
|
883
|
+
// User declined the cross-project fork prompt — exit cleanly with a friendly
|
|
884
|
+
// message rather than letting the decline bubble up as an uncaught exception
|
|
885
|
+
// (see issue #1668).
|
|
886
|
+
if (typeof parsedArgs.resume === "string" && !sessionManager) {
|
|
887
|
+
process.stdout.write(`${chalk.dim("Resume cancelled: session is in another project.")}\n`);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
|
|
849
891
|
// Handle --resume (no value): show session picker
|
|
850
892
|
if (parsedArgs.resume === true && !parsedArgs.fork) {
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
893
|
+
const folderSessions = await logger.time("SessionManager.list", SessionManager.list, cwd, parsedArgs.sessionDir);
|
|
894
|
+
let preloadedAllSessions: SessionInfo[] | undefined;
|
|
895
|
+
let startInAllScope = false;
|
|
896
|
+
if (folderSessions.length === 0) {
|
|
897
|
+
// Nothing in the current folder — fall back to a global scan so the
|
|
898
|
+
// picker can still open in all-projects scope instead of dead-ending.
|
|
899
|
+
preloadedAllSessions = await logger.time("SessionManager.listAll", SessionManager.listAll);
|
|
900
|
+
if (preloadedAllSessions.length === 0) {
|
|
901
|
+
process.stdout.write(`${chalk.dim("No sessions found")}\n`);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
startInAllScope = true;
|
|
855
905
|
}
|
|
856
|
-
const
|
|
857
|
-
|
|
906
|
+
const selected = await logger.time("selectSession", selectSession, folderSessions, {
|
|
907
|
+
allSessions: preloadedAllSessions,
|
|
908
|
+
startInAllScope,
|
|
909
|
+
});
|
|
910
|
+
if (!selected) {
|
|
858
911
|
process.stdout.write(`${chalk.dim("No session selected")}\n`);
|
|
859
912
|
return;
|
|
860
913
|
}
|
|
861
|
-
|
|
914
|
+
// Resuming a session from another project: switch the process into that
|
|
915
|
+
// project's directory and refresh cwd-derived caches before the session is
|
|
916
|
+
// built, so settings discovery, plugins, and capabilities all scope to it.
|
|
917
|
+
if (selected.cwd && normalizePathForComparison(selected.cwd) !== normalizePathForComparison(getProjectDir())) {
|
|
918
|
+
// Let the original (launch-cwd) plugin-root preload settle first so its
|
|
919
|
+
// late resolution can't clobber the re-warm we trigger below.
|
|
920
|
+
await pluginPreloadPromise.catch(() => {});
|
|
921
|
+
setProjectDir(selected.cwd);
|
|
922
|
+
clearPluginRootsAndCaches();
|
|
923
|
+
resetCapabilities();
|
|
924
|
+
cwd = getProjectDir();
|
|
925
|
+
// Re-scope project settings (.claude/settings.yml etc.) to the resumed
|
|
926
|
+
// project in place so the session is built with its configuration.
|
|
927
|
+
await settingsInstance.reloadForCwd(cwd);
|
|
928
|
+
}
|
|
929
|
+
sessionManager = await SessionManager.open(selected.path);
|
|
862
930
|
}
|
|
863
931
|
|
|
864
932
|
await pluginPreloadPromise;
|
|
@@ -47,7 +47,11 @@ import { logger, VERSION } from "@oh-my-pi/pi-utils";
|
|
|
47
47
|
import { disableProvider, enableProvider, reset as resetCapabilities } from "../../capability";
|
|
48
48
|
import { Settings } from "../../config/settings";
|
|
49
49
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
50
|
-
import
|
|
50
|
+
import {
|
|
51
|
+
type ExtensionUIContext,
|
|
52
|
+
type ExtensionUIDialogOptions,
|
|
53
|
+
getExtensionUISelectOptionLabel,
|
|
54
|
+
} from "../../extensibility/extensions";
|
|
51
55
|
import { runExtensionCompact } from "../../extensibility/extensions/compact-handler";
|
|
52
56
|
import { getSessionSlashCommands } from "../../extensibility/extensions/get-commands-handler";
|
|
53
57
|
import { buildSkillPromptMessage, getSkillSlashCommandName } from "../../extensibility/skills";
|
|
@@ -302,7 +306,7 @@ export function createAcpExtensionUiContext(
|
|
|
302
306
|
getSessionId(),
|
|
303
307
|
"select",
|
|
304
308
|
title,
|
|
305
|
-
{ type: "string", enum: options },
|
|
309
|
+
{ type: "string", enum: options.map(getExtensionUISelectOptionLabel) },
|
|
306
310
|
dialogOptions,
|
|
307
311
|
);
|
|
308
312
|
return typeof value === "string" ? value : undefined;
|
|
@@ -1981,7 +1985,7 @@ export class AcpAgent implements Agent {
|
|
|
1981
1985
|
}
|
|
1982
1986
|
if (servers.length === 0) {
|
|
1983
1987
|
record.mcpManager = undefined;
|
|
1984
|
-
await record.session.refreshMCPTools([]);
|
|
1988
|
+
await record.session.refreshMCPTools([], { activateAll: true });
|
|
1985
1989
|
return;
|
|
1986
1990
|
}
|
|
1987
1991
|
|
|
@@ -2008,7 +2012,7 @@ export class AcpAgent implements Agent {
|
|
|
2008
2012
|
}
|
|
2009
2013
|
|
|
2010
2014
|
record.mcpManager = manager;
|
|
2011
|
-
await record.session.refreshMCPTools(result.tools);
|
|
2015
|
+
await record.session.refreshMCPTools(result.tools, { activateAll: true });
|
|
2012
2016
|
}
|
|
2013
2017
|
|
|
2014
2018
|
#toMcpConfig(server: McpServer): MCPServerConfig {
|
|
@@ -69,6 +69,16 @@ interface CommandContainer {
|
|
|
69
69
|
command?: unknown;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
interface EvalCellContainer {
|
|
73
|
+
cells?: unknown;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface EvalCellLike {
|
|
77
|
+
language?: unknown;
|
|
78
|
+
title?: unknown;
|
|
79
|
+
code?: unknown;
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
interface PatternContainer {
|
|
73
83
|
pattern?: unknown;
|
|
74
84
|
}
|
|
@@ -435,11 +445,43 @@ function getToolExecutionEndArgs(
|
|
|
435
445
|
}
|
|
436
446
|
|
|
437
447
|
function buildToolStartContent(toolName: string, args: unknown): ToolCallContent[] {
|
|
438
|
-
|
|
439
|
-
|
|
448
|
+
const text = buildToolStartText(toolName, args);
|
|
449
|
+
return text ? [textToolCallContent(text)] : [];
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function buildToolStartText(toolName: string, args: unknown): string | undefined {
|
|
453
|
+
if (isCommandToolName(toolName)) {
|
|
454
|
+
const command = extractStringProperty<CommandContainer>(args, "command");
|
|
455
|
+
return command ? limitText(`$ ${command}`) : undefined;
|
|
456
|
+
}
|
|
457
|
+
if (toolName === "eval") {
|
|
458
|
+
return buildEvalStartText(args);
|
|
459
|
+
}
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function buildEvalStartText(args: unknown): string | undefined {
|
|
464
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
const cells = (args as EvalCellContainer).cells;
|
|
468
|
+
if (!Array.isArray(cells) || cells.length === 0) {
|
|
469
|
+
return undefined;
|
|
440
470
|
}
|
|
441
|
-
const
|
|
442
|
-
|
|
471
|
+
const lines: string[] = [];
|
|
472
|
+
for (const cell of cells) {
|
|
473
|
+
if (typeof cell !== "object" || cell === null || Array.isArray(cell)) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const language = extractStringProperty<EvalCellLike>(cell, "language") ?? "?";
|
|
477
|
+
const title = extractStringProperty<EvalCellLike>(cell, "title");
|
|
478
|
+
const code = extractStringProperty<EvalCellLike>(cell, "code");
|
|
479
|
+
if (!code) {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
lines.push(title ? `[${language}] ${title}` : `[${language}]`, code);
|
|
483
|
+
}
|
|
484
|
+
return lines.length > 0 ? limitText(lines.join("\n")) : undefined;
|
|
443
485
|
}
|
|
444
486
|
|
|
445
487
|
function mergeToolUpdateContent(startContent: ToolCallContent[], resultContent: ToolCallContent[]): ToolCallContent[] {
|
|
@@ -465,6 +507,14 @@ function isCommandToolName(toolName: string): boolean {
|
|
|
465
507
|
}
|
|
466
508
|
|
|
467
509
|
function buildToolTitle(toolName: string, args: unknown, intent: string | undefined): string {
|
|
510
|
+
if (isCommandToolName(toolName)) {
|
|
511
|
+
const commandText = buildToolStartText(toolName, args);
|
|
512
|
+
if (commandText) return commandText;
|
|
513
|
+
}
|
|
514
|
+
if (toolName === "eval") {
|
|
515
|
+
const evalText = buildEvalStartText(args);
|
|
516
|
+
if (evalText) return evalText;
|
|
517
|
+
}
|
|
468
518
|
const trimmedIntent = intent?.trim();
|
|
469
519
|
if (trimmedIntent) {
|
|
470
520
|
return trimmedIntent;
|
|
@@ -2,6 +2,7 @@ import type { AssistantMessage, ImageContent, Usage } from "@oh-my-pi/pi-ai";
|
|
|
2
2
|
import { Container, Image, ImageProtocol, Markdown, Spacer, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { settings } from "../../config/settings";
|
|
5
|
+
import type { AssistantThinkingRenderer } from "../../extensibility/extensions/types";
|
|
5
6
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
6
7
|
import { isSilentAbort } from "../../session/messages";
|
|
7
8
|
import { resolveImageOptions } from "../../tools/render-utils";
|
|
@@ -21,6 +22,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
21
22
|
message?: AssistantMessage,
|
|
22
23
|
private hideThinkingBlock = false,
|
|
23
24
|
private readonly onImageUpdate?: () => void,
|
|
25
|
+
private readonly thinkingRenderers: readonly AssistantThinkingRenderer[] = [],
|
|
24
26
|
) {
|
|
25
27
|
super();
|
|
26
28
|
|
|
@@ -131,6 +133,27 @@ export class AssistantMessageComponent extends Container {
|
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
#appendThinkingExtensions(contentIndex: number, thinkingIndex: number, text: string): void {
|
|
137
|
+
for (const renderer of this.thinkingRenderers) {
|
|
138
|
+
try {
|
|
139
|
+
const component = renderer(
|
|
140
|
+
{
|
|
141
|
+
contentIndex,
|
|
142
|
+
thinkingIndex,
|
|
143
|
+
text,
|
|
144
|
+
requestRender: () => this.onImageUpdate?.(),
|
|
145
|
+
},
|
|
146
|
+
theme,
|
|
147
|
+
);
|
|
148
|
+
if (component) {
|
|
149
|
+
this.#contentContainer.addChild(component);
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// Ignore extension renderer failures and keep the original thinking block visible.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
134
157
|
updateContent(message: AssistantMessage): void {
|
|
135
158
|
this.#lastMessage = message;
|
|
136
159
|
|
|
@@ -146,6 +169,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
146
169
|
}
|
|
147
170
|
|
|
148
171
|
// Render content in order
|
|
172
|
+
let thinkingIndex = 0;
|
|
149
173
|
for (let i = 0; i < message.content.length; i++) {
|
|
150
174
|
const content = message.content[i];
|
|
151
175
|
if (content.type === "text" && content.text.trim()) {
|
|
@@ -166,13 +190,16 @@ export class AssistantMessageComponent extends Container {
|
|
|
166
190
|
this.#contentContainer.addChild(new Spacer(1));
|
|
167
191
|
}
|
|
168
192
|
} else {
|
|
193
|
+
const thinkingText = content.thinking.trim();
|
|
169
194
|
// Thinking traces in thinkingText color, italic
|
|
170
195
|
this.#contentContainer.addChild(
|
|
171
|
-
new Markdown(
|
|
196
|
+
new Markdown(thinkingText, 1, 0, getMarkdownTheme(), {
|
|
172
197
|
color: (text: string) => theme.fg("thinkingText", text),
|
|
173
198
|
italic: true,
|
|
174
199
|
}),
|
|
175
200
|
);
|
|
201
|
+
this.#appendThinkingExtensions(i, thinkingIndex, thinkingText);
|
|
202
|
+
thinkingIndex += 1;
|
|
176
203
|
if (hasVisibleContentAfter) {
|
|
177
204
|
this.#contentContainer.addChild(new Spacer(1));
|
|
178
205
|
}
|
|
@@ -19,6 +19,7 @@ type ConfigurableEditorAction = Extract<
|
|
|
19
19
|
| "app.history.search"
|
|
20
20
|
| "app.message.dequeue"
|
|
21
21
|
| "app.clipboard.pasteImage"
|
|
22
|
+
| "app.clipboard.pasteTextRaw"
|
|
22
23
|
| "app.clipboard.copyPrompt"
|
|
23
24
|
>;
|
|
24
25
|
|
|
@@ -38,6 +39,7 @@ const DEFAULT_ACTION_KEYS: Record<ConfigurableEditorAction, KeyId[]> = {
|
|
|
38
39
|
"app.history.search": ["ctrl+r"],
|
|
39
40
|
"app.message.dequeue": ["alt+up"],
|
|
40
41
|
"app.clipboard.pasteImage": ["ctrl+v"],
|
|
42
|
+
"app.clipboard.pasteTextRaw": ["ctrl+shift+v", "alt+shift+v"],
|
|
41
43
|
"app.clipboard.copyPrompt": ["alt+shift+c"],
|
|
42
44
|
};
|
|
43
45
|
|
|
@@ -49,7 +51,6 @@ export class CustomEditor extends Editor {
|
|
|
49
51
|
* them, skipping any occurrence inside code spans, fenced blocks, or XML sections. */
|
|
50
52
|
decorateText = (text: string): string => highlightMagicKeywords(text);
|
|
51
53
|
onEscape?: () => void;
|
|
52
|
-
shouldBypassAutocompleteOnEscape?: () => boolean;
|
|
53
54
|
onClear?: () => void;
|
|
54
55
|
onExit?: () => void;
|
|
55
56
|
onCycleThinkingLevel?: () => void;
|
|
@@ -66,6 +67,8 @@ export class CustomEditor extends Editor {
|
|
|
66
67
|
onCopyPrompt?: () => void;
|
|
67
68
|
/** Called when the configured image-paste shortcut is pressed. */
|
|
68
69
|
onPasteImage?: () => Promise<boolean>;
|
|
70
|
+
/** Called when the configured raw text-paste shortcut is pressed. */
|
|
71
|
+
onPasteTextRaw?: () => void;
|
|
69
72
|
/** Called when the configured dequeue shortcut is pressed. */
|
|
70
73
|
onDequeue?: () => void;
|
|
71
74
|
/** Called when Caps Lock is pressed. */
|
|
@@ -125,6 +128,12 @@ export class CustomEditor extends Editor {
|
|
|
125
128
|
return;
|
|
126
129
|
}
|
|
127
130
|
|
|
131
|
+
// Intercept configured raw text paste (fires and handles result)
|
|
132
|
+
if (this.#matchesAction(data, "app.clipboard.pasteTextRaw") && this.onPasteTextRaw) {
|
|
133
|
+
this.onPasteTextRaw();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
128
137
|
// Intercept configured external editor shortcut
|
|
129
138
|
if (this.#matchesAction(data, "app.editor.external") && this.onExternalEditor) {
|
|
130
139
|
this.onExternalEditor();
|
|
@@ -186,12 +195,15 @@ export class CustomEditor extends Editor {
|
|
|
186
195
|
}
|
|
187
196
|
|
|
188
197
|
// Intercept configured interrupt shortcut.
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
// When the autocomplete popup is visible, ESC's first job is to dismiss
|
|
199
|
+
// the popup — let super.handleInput() route it to #cancelAutocomplete().
|
|
200
|
+
// The user can press ESC again afterward to fire the global interrupt
|
|
201
|
+
// handler. This matches the standard TUI/IDE pattern and prevents a
|
|
202
|
+
// single ESC from both closing an @ completion and aborting an active
|
|
203
|
+
// agent run (#1655).
|
|
204
|
+
if (this.#matchesAction(data, "app.interrupt") && this.onEscape && !this.isShowingAutocomplete()) {
|
|
205
|
+
this.onEscape();
|
|
206
|
+
return;
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
// Intercept configured clear shortcut
|