@psnext/slingcli 2.5.20260623-1 → 2.5.20260629-1
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/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +5 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +2 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +51 -47
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +9 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +9 -6
- package/node_modules/@earendil-works/pi-ai/dist/api/cloudflare.js +0 -18
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +6 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +6 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +15 -25
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +30 -26
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +189 -204
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +24 -21
- package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js +2 -5
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js +11 -2
- package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +19 -4
- package/node_modules/@earendil-works/pi-ai/dist/compat.js +81 -7
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +45 -0
- package/node_modules/@earendil-works/pi-ai/dist/images-models.js +7 -3
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/legacy-api-aliases.js +49 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.js +7 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +25 -8
- package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.js +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +0 -34
- package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js +21 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-auth.js +85 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.js +13 -13
- package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js +1 -20
- package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js +19 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.js +72 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.js +4 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.js +4 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js +19 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +11 -11
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +15 -15
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +310 -39
- package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js +37 -18
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +349 -77
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.js +7 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.js +6 -6
- package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.js +6 -6
- package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js +95 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/headers.js +10 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +86 -0
- package/node_modules/@earendil-works/pi-ai/package.json +1 -33
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/file-processor.js +13 -25
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +15 -30
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +6 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +5 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +2 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +9 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/timings.js +24 -14
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +12 -25
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +20 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +9 -9
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +12 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/session-selector.js +14 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +37 -25
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +14 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +16 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/rpc-entry.js +10 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-convert.js +21 -15
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-process.js +83 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/mime.js +41 -0
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +12 -12
- package/node_modules/@earendil-works/pi-coding-agent/package.json +8 -5
- package/node_modules/@earendil-works/pi-orchestrator/dist/cli.js +136 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/config.js +55 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/handler.js +105 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/index.js +11 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/ipc/client.js +56 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/ipc/protocol.js +12 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/ipc/server.js +145 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/radius.js +365 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/rpc-process.js +168 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/serve.js +70 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/storage.js +58 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/supervisor.js +290 -0
- package/node_modules/@earendil-works/pi-orchestrator/dist/types.js +2 -0
- package/node_modules/@earendil-works/pi-orchestrator/package.json +45 -0
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +3 -0
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/diagnostics_channel.d.ts +17 -20
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/brace-expansion/dist/commonjs/index.js +24 -14
- package/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -1
- package/node_modules/brace-expansion/dist/esm/index.js +24 -14
- package/node_modules/brace-expansion/dist/esm/index.js.map +1 -1
- package/node_modules/brace-expansion/package.json +2 -2
- package/node_modules/google-auth-library/build/src/auth/googleauth.js +1 -1
- package/node_modules/google-auth-library/package.json +4 -4
- package/package.json +7 -6
- package/slingshot/index.js +302 -304
- package/node_modules/@earendil-works/pi-ai/dist/api-registry.js +0 -44
|
@@ -11,7 +11,7 @@ export const defaultModelPerProvider = {
|
|
|
11
11
|
"amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
|
|
12
12
|
"ant-ling": "Ring-2.6-1T",
|
|
13
13
|
anthropic: "claude-opus-4-8",
|
|
14
|
-
openai: "gpt-5.
|
|
14
|
+
openai: "gpt-5.5",
|
|
15
15
|
"azure-openai-responses": "gpt-5.4",
|
|
16
16
|
"openai-codex": "gpt-5.5",
|
|
17
17
|
nvidia: "nvidia/nemotron-3-super-120b-a12b",
|
|
@@ -11,6 +11,7 @@ import { loadPromptTemplates } from "./prompt-templates.js";
|
|
|
11
11
|
import { SettingsManager } from "./settings-manager.js";
|
|
12
12
|
import { loadSkills } from "./skills.js";
|
|
13
13
|
import { createSourceInfo } from "./source-info.js";
|
|
14
|
+
import { resetTimings } from "./timings.js";
|
|
14
15
|
function resolvePromptInput(input, description) {
|
|
15
16
|
if (!input) {
|
|
16
17
|
return undefined;
|
|
@@ -216,6 +217,7 @@ export class DefaultResourceLoader {
|
|
|
216
217
|
return this.loadCurrentExtensionSet({ includeInlineFactories: true });
|
|
217
218
|
}
|
|
218
219
|
async reload(options) {
|
|
220
|
+
resetTimings("extensions");
|
|
219
221
|
if (this.loaded) {
|
|
220
222
|
clearExtensionCache();
|
|
221
223
|
}
|
|
@@ -533,10 +533,13 @@ export class SessionManager {
|
|
|
533
533
|
this.sessionFile = resolvePath(sessionFile);
|
|
534
534
|
if (existsSync(this.sessionFile)) {
|
|
535
535
|
this.fileEntries = loadEntriesFromFile(this.sessionFile);
|
|
536
|
-
// If file was empty
|
|
537
|
-
//
|
|
536
|
+
// If file was empty, initialize it with a valid session header. If it was
|
|
537
|
+
// non-empty but did not parse as a pi session, fail without modifying it.
|
|
538
538
|
if (this.fileEntries.length === 0) {
|
|
539
539
|
const explicitPath = this.sessionFile;
|
|
540
|
+
if (statSync(explicitPath).size > 0) {
|
|
541
|
+
throw new Error(`Session file is not a valid pi session: ${explicitPath}`);
|
|
542
|
+
}
|
|
540
543
|
this.newSession();
|
|
541
544
|
this.sessionFile = explicitPath;
|
|
542
545
|
this._rewriteFile();
|
|
@@ -744,12 +747,13 @@ export class SessionManager {
|
|
|
744
747
|
}
|
|
745
748
|
/** Append a session info entry (e.g., display name). Returns entry id. */
|
|
746
749
|
appendSessionInfo(name) {
|
|
750
|
+
const sanitizedName = name.replace(/[\r\n]+/g, " ").trim();
|
|
747
751
|
const entry = {
|
|
748
752
|
type: "session_info",
|
|
749
753
|
id: generateId(this.byId),
|
|
750
754
|
parentId: this.leafId,
|
|
751
755
|
timestamp: new Date().toISOString(),
|
|
752
|
-
name:
|
|
756
|
+
name: sanitizedName,
|
|
753
757
|
};
|
|
754
758
|
this._appendEntry(entry);
|
|
755
759
|
return entry.id;
|
|
@@ -1112,8 +1116,8 @@ export class SessionManager {
|
|
|
1112
1116
|
return new SessionManager(cwd, dir, undefined, true);
|
|
1113
1117
|
}
|
|
1114
1118
|
/** Create an in-memory session (no file persistence) */
|
|
1115
|
-
static inMemory(cwd = process.cwd()) {
|
|
1116
|
-
return new SessionManager(cwd, "", undefined, false);
|
|
1119
|
+
static inMemory(cwd = process.cwd(), options) {
|
|
1120
|
+
return new SessionManager(cwd, "", undefined, false, options);
|
|
1117
1121
|
}
|
|
1118
1122
|
/**
|
|
1119
1123
|
* Fork a session from another project directory into the current project.
|
|
@@ -581,6 +581,17 @@ export class SettingsManager {
|
|
|
581
581
|
getHideThinkingBlock() {
|
|
582
582
|
return this.settings.hideThinkingBlock ?? false;
|
|
583
583
|
}
|
|
584
|
+
getExternalEditorCommand() {
|
|
585
|
+
const configuredEditor = this.settings.externalEditor;
|
|
586
|
+
if (typeof configuredEditor === "string" && configuredEditor.trim() !== "") {
|
|
587
|
+
return configuredEditor;
|
|
588
|
+
}
|
|
589
|
+
const environmentEditor = process.env.VISUAL || process.env.EDITOR;
|
|
590
|
+
if (environmentEditor) {
|
|
591
|
+
return environmentEditor;
|
|
592
|
+
}
|
|
593
|
+
return process.platform === "win32" ? "notepad" : "nano";
|
|
594
|
+
}
|
|
584
595
|
setHideThinkingBlock(hide) {
|
|
585
596
|
this.globalSettings.hideThinkingBlock = hide;
|
|
586
597
|
this.markModified("hideThinkingBlock");
|
|
@@ -3,29 +3,39 @@
|
|
|
3
3
|
* Enable with PI_TIMING=1 environment variable.
|
|
4
4
|
*/
|
|
5
5
|
const ENABLED = process.env.PI_TIMING === "1";
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
export function resetTimings() {
|
|
6
|
+
const timingNamespaces = new Map();
|
|
7
|
+
export function resetTimings(namespace = "main") {
|
|
9
8
|
if (!ENABLED)
|
|
10
9
|
return;
|
|
11
|
-
timings.
|
|
12
|
-
lastTime = Date.now();
|
|
10
|
+
timingNamespaces.set(namespace, { timings: [], lastTime: Date.now() });
|
|
13
11
|
}
|
|
14
|
-
export function time(label) {
|
|
12
|
+
export function time(label, namespace = "main") {
|
|
15
13
|
if (!ENABLED)
|
|
16
14
|
return;
|
|
17
15
|
const now = Date.now();
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
if (!timingNamespaces.has(namespace)) {
|
|
17
|
+
resetTimings(namespace);
|
|
18
|
+
}
|
|
19
|
+
const timingNamespace = timingNamespaces.get(namespace);
|
|
20
|
+
timingNamespace.timings.push({ label, ms: now - timingNamespace.lastTime });
|
|
21
|
+
timingNamespace.lastTime = now;
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
function printTimingGroup(title, timings) {
|
|
24
|
+
const printableTimings = timings.filter((timing) => timing.ms >= 0);
|
|
25
|
+
if (printableTimings.length === 0)
|
|
23
26
|
return;
|
|
24
|
-
console.error(
|
|
25
|
-
for (const t of
|
|
27
|
+
console.error(`\n--- ${title} ---`);
|
|
28
|
+
for (const t of printableTimings) {
|
|
26
29
|
console.error(` ${t.label}: ${t.ms}ms`);
|
|
27
30
|
}
|
|
28
|
-
console.error(` TOTAL: ${
|
|
29
|
-
console.error("
|
|
31
|
+
console.error(` TOTAL: ${printableTimings.reduce((a, b) => a + b.ms, 0)}ms`);
|
|
32
|
+
console.error(`${"-".repeat(title.length + 8)}\n`);
|
|
33
|
+
}
|
|
34
|
+
export function printTimings() {
|
|
35
|
+
if (!ENABLED)
|
|
36
|
+
return;
|
|
37
|
+
for (const [namespace, timingNamespace] of timingNamespaces) {
|
|
38
|
+
printTimingGroup(`Startup Timings: ${namespace}`, timingNamespace.timings);
|
|
39
|
+
}
|
|
30
40
|
}
|
|
31
41
|
//# sourceMappingURL=timings.js.map
|
|
@@ -6,7 +6,7 @@ import { Type } from "typebox";
|
|
|
6
6
|
import { getReadmePath } from "../../config.js";
|
|
7
7
|
import { keyHint, keyText } from "../../modes/interactive/components/keybinding-hints.js";
|
|
8
8
|
import { getLanguageFromPath, highlightCode } from "../../modes/interactive/theme/theme.js";
|
|
9
|
-
import {
|
|
9
|
+
import { processImage } from "../../utils/image-process.js";
|
|
10
10
|
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
|
11
11
|
import { formatPathRelativeToCwdOrAbsolute } from "../../utils/paths.js";
|
|
12
12
|
import { resolveReadPathAsync, resolveToCwd } from "./path-utils.js";
|
|
@@ -133,7 +133,7 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
133
133
|
return {
|
|
134
134
|
name: "read",
|
|
135
135
|
label: "read",
|
|
136
|
-
description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,
|
|
136
|
+
description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,
|
|
137
137
|
promptSnippet: "Read file contents",
|
|
138
138
|
promptGuidelines: ["Use read to examine files instead of cat or sed."],
|
|
139
139
|
parameters: readSchema,
|
|
@@ -165,35 +165,22 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
165
165
|
if (mimeType) {
|
|
166
166
|
// Read image as binary.
|
|
167
167
|
const buffer = await ops.readFile(absolutePath);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
textNote += `\n${nonVisionImageNote}`;
|
|
175
|
-
content = [{ type: "text", text: textNote }];
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
const dimensionNote = formatDimensionNote(resized);
|
|
179
|
-
let textNote = `Read image file [${resized.mimeType}]`;
|
|
180
|
-
if (dimensionNote)
|
|
181
|
-
textNote += `\n${dimensionNote}`;
|
|
182
|
-
if (nonVisionImageNote)
|
|
183
|
-
textNote += `\n${nonVisionImageNote}`;
|
|
184
|
-
content = [
|
|
185
|
-
{ type: "text", text: textNote },
|
|
186
|
-
{ type: "image", data: resized.data, mimeType: resized.mimeType },
|
|
187
|
-
];
|
|
188
|
-
}
|
|
168
|
+
const processed = await processImage(buffer, mimeType, { autoResizeImages });
|
|
169
|
+
if (!processed.ok) {
|
|
170
|
+
let textNote = `Read image file [${mimeType}]\n${processed.message}`;
|
|
171
|
+
if (nonVisionImageNote)
|
|
172
|
+
textNote += `\n${nonVisionImageNote}`;
|
|
173
|
+
content = [{ type: "text", text: textNote }];
|
|
189
174
|
}
|
|
190
175
|
else {
|
|
191
|
-
let textNote = `Read image file [${mimeType}]`;
|
|
176
|
+
let textNote = `Read image file [${processed.mimeType}]`;
|
|
177
|
+
if (processed.hints.length > 0)
|
|
178
|
+
textNote += `\n${processed.hints.join("\n")}`;
|
|
192
179
|
if (nonVisionImageNote)
|
|
193
180
|
textNote += `\n${nonVisionImageNote}`;
|
|
194
181
|
content = [
|
|
195
182
|
{ type: "text", text: textNote },
|
|
196
|
-
{ type: "image", data:
|
|
183
|
+
{ type: "image", data: processed.data, mimeType: processed.mimeType },
|
|
197
184
|
];
|
|
198
185
|
}
|
|
199
186
|
}
|
|
@@ -35,6 +35,7 @@ import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js"
|
|
|
35
35
|
import { handleConfigCommand, handlePackageCommand } from "./package-manager-cli.js";
|
|
36
36
|
import { isLocalPath, normalizePath, resolvePath } from "./utils/paths.js";
|
|
37
37
|
import { cleanupWindowsSelfUpdateQuarantine } from "./utils/windows-self-update.js";
|
|
38
|
+
const EXTENSION_LOAD_FAILURE_HINT = 'Hint: Start without extensions using "sling -ne".';
|
|
38
39
|
/**
|
|
39
40
|
* Read all content from piped stdin.
|
|
40
41
|
* Returns undefined if stdin is a TTY (interactive terminal).
|
|
@@ -167,7 +168,6 @@ function validateSessionIdFlags(parsed) {
|
|
|
167
168
|
parsed.session ? "--session" : undefined,
|
|
168
169
|
parsed.continue ? "--continue" : undefined,
|
|
169
170
|
parsed.resume ? "--resume" : undefined,
|
|
170
|
-
parsed.noSession ? "--no-session" : undefined,
|
|
171
171
|
].filter((flag) => flag !== undefined);
|
|
172
172
|
if (conflictingFlags.length > 0) {
|
|
173
173
|
console.error(chalk.red(`Error: --session-id cannot be combined with ${conflictingFlags.join(", ")}`));
|
|
@@ -182,6 +182,16 @@ function validateSessionIdFlags(parsed) {
|
|
|
182
182
|
process.exit(1);
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
|
+
function openSessionOrExit(path, sessionDir) {
|
|
186
|
+
try {
|
|
187
|
+
return SessionManager.open(path, sessionDir);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
191
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
185
195
|
function forkSessionOrExit(sourcePath, cwd, sessionDir, sessionId) {
|
|
186
196
|
try {
|
|
187
197
|
return SessionManager.forkFrom(sourcePath, cwd, sessionDir, { id: sessionId });
|
|
@@ -194,7 +204,7 @@ function forkSessionOrExit(sourcePath, cwd, sessionDir, sessionId) {
|
|
|
194
204
|
}
|
|
195
205
|
async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
|
|
196
206
|
if (parsed.noSession || parsed.help || parsed.listModels !== undefined) {
|
|
197
|
-
return SessionManager.inMemory(cwd);
|
|
207
|
+
return SessionManager.inMemory(cwd, parsed.sessionId !== undefined ? { id: parsed.sessionId } : undefined);
|
|
198
208
|
}
|
|
199
209
|
if (parsed.fork) {
|
|
200
210
|
if (parsed.sessionId) {
|
|
@@ -220,7 +230,7 @@ async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
|
|
|
220
230
|
switch (resolved.type) {
|
|
221
231
|
case "path":
|
|
222
232
|
case "local":
|
|
223
|
-
return
|
|
233
|
+
return openSessionOrExit(resolved.path, sessionDir);
|
|
224
234
|
case "global": {
|
|
225
235
|
console.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));
|
|
226
236
|
const shouldFork = await promptConfirm("Fork this session into current directory?");
|
|
@@ -621,6 +631,9 @@ export async function main(args, options) {
|
|
|
621
631
|
time("resolveModelScope");
|
|
622
632
|
reportDiagnostics(runtime.diagnostics);
|
|
623
633
|
if (runtime.diagnostics.some((diagnostic) => diagnostic.type === "error")) {
|
|
634
|
+
if (runtime.diagnostics.some((diagnostic) => diagnostic.message.includes("Failed to load extension"))) {
|
|
635
|
+
console.error(chalk.yellow(EXTENSION_LOAD_FAILURE_HINT));
|
|
636
|
+
}
|
|
624
637
|
process.exit(1);
|
|
625
638
|
}
|
|
626
639
|
time("createAgentSession");
|
|
@@ -650,9 +663,12 @@ export async function main(args, options) {
|
|
|
650
663
|
if (startupBenchmark) {
|
|
651
664
|
await interactiveMode.init();
|
|
652
665
|
time("interactiveMode.init");
|
|
653
|
-
|
|
666
|
+
// Give the TUI's stdin handler a brief chance to consume terminal query replies
|
|
667
|
+
// (Kitty keyboard protocol, device attributes, cell size) before restoring the terminal.
|
|
668
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
654
669
|
interactiveMode.stop();
|
|
655
670
|
stopThemeWatcher();
|
|
671
|
+
printTimings();
|
|
656
672
|
if (process.stdout.writableLength > 0) {
|
|
657
673
|
await new Promise((resolve) => process.stdout.once("drain", resolve));
|
|
658
674
|
}
|
|
@@ -93,21 +93,21 @@ export class AssistantMessageComponent extends Container {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
// Check if
|
|
97
|
-
//
|
|
96
|
+
// Check if incomplete/failed - show after partial content.
|
|
97
|
+
// For aborted/error tool calls, tool execution components show the error.
|
|
98
|
+
// Length stops can happen before a tool call is complete, so surface them here too.
|
|
98
99
|
const hasToolCalls = message.content.some((c) => c.type === "toolCall");
|
|
99
100
|
this.hasToolCalls = hasToolCalls;
|
|
100
|
-
if (
|
|
101
|
+
if (message.stopReason === "length") {
|
|
102
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
103
|
+
this.contentContainer.addChild(new Text(theme.fg("error", "Error: Model stopped because it reached the maximum output token limit. The response may be incomplete."), 1, 0));
|
|
104
|
+
}
|
|
105
|
+
else if (!hasToolCalls) {
|
|
101
106
|
if (message.stopReason === "aborted") {
|
|
102
107
|
const abortMessage = message.errorMessage && message.errorMessage !== "Request was aborted"
|
|
103
108
|
? message.errorMessage
|
|
104
109
|
: "Operation aborted";
|
|
105
|
-
|
|
106
|
-
this.contentContainer.addChild(new Spacer(1));
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
this.contentContainer.addChild(new Spacer(1));
|
|
110
|
-
}
|
|
110
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
111
111
|
this.contentContainer.addChild(new Text(theme.fg("error", abortMessage), 1, 0));
|
|
112
112
|
}
|
|
113
113
|
else if (message.stopReason === "error") {
|
|
@@ -16,6 +16,7 @@ export class ExtensionEditorComponent extends Container {
|
|
|
16
16
|
onCancelCallback;
|
|
17
17
|
tui;
|
|
18
18
|
keybindings;
|
|
19
|
+
externalEditorCommand;
|
|
19
20
|
_focused = false;
|
|
20
21
|
get focused() {
|
|
21
22
|
return this._focused;
|
|
@@ -24,10 +25,11 @@ export class ExtensionEditorComponent extends Container {
|
|
|
24
25
|
this._focused = value;
|
|
25
26
|
this.editor.focused = value;
|
|
26
27
|
}
|
|
27
|
-
constructor(tui, keybindings, title, prefill, onSubmit, onCancel, options) {
|
|
28
|
+
constructor(tui, keybindings, title, prefill, onSubmit, onCancel, options, externalEditorCommand) {
|
|
28
29
|
super();
|
|
29
30
|
this.tui = tui;
|
|
30
31
|
this.keybindings = keybindings;
|
|
32
|
+
this.externalEditorCommand = externalEditorCommand;
|
|
31
33
|
this.onSubmitCallback = onSubmit;
|
|
32
34
|
this.onCancelCallback = onCancel;
|
|
33
35
|
// Add top border
|
|
@@ -48,7 +50,7 @@ export class ExtensionEditorComponent extends Container {
|
|
|
48
50
|
this.addChild(this.editor);
|
|
49
51
|
this.addChild(new Spacer(1));
|
|
50
52
|
// Add hint
|
|
51
|
-
const hasExternalEditor = !!(
|
|
53
|
+
const hasExternalEditor = !!this.getExternalEditorCommand();
|
|
52
54
|
const hint = keyHint("tui.select.confirm", "submit") +
|
|
53
55
|
" " +
|
|
54
56
|
keyHint("tui.input.newLine", "newline") +
|
|
@@ -75,8 +77,15 @@ export class ExtensionEditorComponent extends Container {
|
|
|
75
77
|
// Forward to editor
|
|
76
78
|
this.editor.handleInput(keyData);
|
|
77
79
|
}
|
|
80
|
+
getExternalEditorCommand() {
|
|
81
|
+
const editorCmd = this.externalEditorCommand || process.env.VISUAL || process.env.EDITOR;
|
|
82
|
+
if (editorCmd) {
|
|
83
|
+
return editorCmd;
|
|
84
|
+
}
|
|
85
|
+
return process.platform === "win32" ? "notepad" : "nano";
|
|
86
|
+
}
|
|
78
87
|
async openExternalEditor() {
|
|
79
|
-
const editorCmd =
|
|
88
|
+
const editorCmd = this.getExternalEditorCommand();
|
|
80
89
|
if (!editorCmd) {
|
|
81
90
|
return;
|
|
82
91
|
}
|
|
@@ -168,7 +168,7 @@ function buildSessionTree(sessions) {
|
|
|
168
168
|
const byPath = new Map();
|
|
169
169
|
for (const session of sessions) {
|
|
170
170
|
const sessionPath = canonicalizePath(session.path) ?? session.path;
|
|
171
|
-
byPath.set(sessionPath, { session, children: [] });
|
|
171
|
+
byPath.set(sessionPath, { session, children: [], latestActivity: session.modified.getTime() });
|
|
172
172
|
}
|
|
173
173
|
const roots = [];
|
|
174
174
|
for (const session of sessions) {
|
|
@@ -182,9 +182,20 @@ function buildSessionTree(sessions) {
|
|
|
182
182
|
roots.push(node);
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
const updateLatestActivity = (node) => {
|
|
186
|
+
let latestActivity = node.session.modified.getTime();
|
|
187
|
+
for (const child of node.children) {
|
|
188
|
+
latestActivity = Math.max(latestActivity, updateLatestActivity(child));
|
|
189
|
+
}
|
|
190
|
+
node.latestActivity = latestActivity;
|
|
191
|
+
return latestActivity;
|
|
192
|
+
};
|
|
193
|
+
for (const root of roots) {
|
|
194
|
+
updateLatestActivity(root);
|
|
195
|
+
}
|
|
196
|
+
// Sort children and roots by latest activity in each subtree (descending)
|
|
186
197
|
const sortNodes = (nodes) => {
|
|
187
|
-
nodes.sort((a, b) => b.
|
|
198
|
+
nodes.sort((a, b) => b.latestActivity - a.latestActivity);
|
|
188
199
|
for (const node of nodes) {
|
|
189
200
|
sortNodes(node.children);
|
|
190
201
|
}
|
|
@@ -13,7 +13,7 @@ export class UserMessageComponent extends Container {
|
|
|
13
13
|
this.contentBox = new Box(1, 1, (content) => theme.bg("userMessageBg", content));
|
|
14
14
|
this.contentBox.addChild(new Markdown(text, 0, 0, markdownTheme, {
|
|
15
15
|
color: (content) => theme.fg("userMessageText", content),
|
|
16
|
-
}, { preserveOrderedListMarkers: true }));
|
|
16
|
+
}, { preserveOrderedListMarkers: true, preserveBackslashEscapes: true }));
|
|
17
17
|
this.addChild(this.contentBox);
|
|
18
18
|
}
|
|
19
19
|
render(width) {
|
package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js
CHANGED
|
@@ -134,6 +134,7 @@ export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProvi
|
|
|
134
134
|
export class InteractiveMode {
|
|
135
135
|
runtimeHost;
|
|
136
136
|
ui;
|
|
137
|
+
loadedResourcesContainer;
|
|
137
138
|
chatContainer;
|
|
138
139
|
pendingMessagesContainer;
|
|
139
140
|
statusContainer;
|
|
@@ -246,6 +247,7 @@ export class InteractiveMode {
|
|
|
246
247
|
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
|
|
247
248
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
248
249
|
this.headerContainer = new Container();
|
|
250
|
+
this.loadedResourcesContainer = new Container();
|
|
249
251
|
this.chatContainer = new Container();
|
|
250
252
|
this.pendingMessagesContainer = new Container();
|
|
251
253
|
this.statusContainer = new Container();
|
|
@@ -439,8 +441,10 @@ export class InteractiveMode {
|
|
|
439
441
|
: "";
|
|
440
442
|
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
|
|
441
443
|
}
|
|
442
|
-
// Add header container as first child. Populate it after
|
|
444
|
+
// Add header container as first child. Populate it after applying theme settings.
|
|
445
|
+
// Keep loaded resources before chat so restored session messages never precede them.
|
|
443
446
|
this.ui.addChild(this.headerContainer);
|
|
447
|
+
this.ui.addChild(this.loadedResourcesContainer);
|
|
444
448
|
this.ui.addChild(this.chatContainer);
|
|
445
449
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
446
450
|
this.ui.addChild(this.statusContainer);
|
|
@@ -490,7 +494,7 @@ export class InteractiveMode {
|
|
|
490
494
|
hint("app.tools.expand", "more"),
|
|
491
495
|
].join(theme.fg("muted", " · "));
|
|
492
496
|
const compactOnboarding = theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`);
|
|
493
|
-
const onboarding = theme.fg("dim", `
|
|
497
|
+
const onboarding = theme.fg("dim", `Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.`);
|
|
494
498
|
this.builtInHeader = new ExpandableText(() => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, this.getStartupExpansionState(), 1, 0);
|
|
495
499
|
// Setup UI layout
|
|
496
500
|
this.headerContainer.addChild(new Spacer(1));
|
|
@@ -657,7 +661,7 @@ export class InteractiveMode {
|
|
|
657
661
|
return "tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.";
|
|
658
662
|
}
|
|
659
663
|
if (extendedKeysFormat === "xterm") {
|
|
660
|
-
return "tmux extended-keys-format is xterm.
|
|
664
|
+
return "tmux extended-keys-format is xterm. Pi works best with csi-u. Add `set -g extended-keys-format csi-u` to ~/.tmux.conf and restart tmux.";
|
|
661
665
|
}
|
|
662
666
|
return undefined;
|
|
663
667
|
}
|
|
@@ -990,6 +994,8 @@ export class InteractiveMode {
|
|
|
990
994
|
return lines.join("\n");
|
|
991
995
|
}
|
|
992
996
|
showLoadedResources(options) {
|
|
997
|
+
// Resource rendering is idempotent; chat clears no longer clear this separate container.
|
|
998
|
+
this.loadedResourcesContainer.clear();
|
|
993
999
|
const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
|
994
1000
|
const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
|
|
995
1001
|
if (!showListing && !showDiagnostics) {
|
|
@@ -1005,8 +1011,8 @@ export class InteractiveMode {
|
|
|
1005
1011
|
};
|
|
1006
1012
|
const addLoadedSection = (name, collapsedBody, expandedBody = collapsedBody, color = "mdHeading") => {
|
|
1007
1013
|
const section = new ExpandableText(() => `${sectionHeader(name, color)}\n${collapsedBody}`, () => `${sectionHeader(name, color)}\n${expandedBody}`, this.getStartupExpansionState(), 0, 0);
|
|
1008
|
-
this.
|
|
1009
|
-
this.
|
|
1014
|
+
this.loadedResourcesContainer.addChild(section);
|
|
1015
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1010
1016
|
};
|
|
1011
1017
|
const skillsResult = this.session.resourceLoader.getSkills();
|
|
1012
1018
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
@@ -1040,7 +1046,7 @@ export class InteractiveMode {
|
|
|
1040
1046
|
if (showListing) {
|
|
1041
1047
|
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
|
1042
1048
|
if (contextFiles.length > 0) {
|
|
1043
|
-
this.
|
|
1049
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1044
1050
|
const contextList = contextFiles
|
|
1045
1051
|
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
|
1046
1052
|
.join("\n");
|
|
@@ -1103,14 +1109,14 @@ export class InteractiveMode {
|
|
|
1103
1109
|
const skillDiagnostics = skillsResult.diagnostics;
|
|
1104
1110
|
if (skillDiagnostics.length > 0) {
|
|
1105
1111
|
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
|
1106
|
-
this.
|
|
1107
|
-
this.
|
|
1112
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
|
1113
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1108
1114
|
}
|
|
1109
1115
|
const promptDiagnostics = promptsResult.diagnostics;
|
|
1110
1116
|
if (promptDiagnostics.length > 0) {
|
|
1111
1117
|
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
|
1112
|
-
this.
|
|
1113
|
-
this.
|
|
1118
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
|
1119
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1114
1120
|
}
|
|
1115
1121
|
const extensionDiagnostics = [];
|
|
1116
1122
|
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
@@ -1126,14 +1132,14 @@ export class InteractiveMode {
|
|
|
1126
1132
|
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
1127
1133
|
if (extensionDiagnostics.length > 0) {
|
|
1128
1134
|
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
|
1129
|
-
this.
|
|
1130
|
-
this.
|
|
1135
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
1136
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1131
1137
|
}
|
|
1132
1138
|
const themeDiagnostics = themesResult.diagnostics;
|
|
1133
1139
|
if (themeDiagnostics.length > 0) {
|
|
1134
1140
|
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
1135
|
-
this.
|
|
1136
|
-
this.
|
|
1141
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
1142
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1137
1143
|
}
|
|
1138
1144
|
}
|
|
1139
1145
|
}
|
|
@@ -1261,6 +1267,7 @@ export class InteractiveMode {
|
|
|
1261
1267
|
process.exit(1);
|
|
1262
1268
|
}
|
|
1263
1269
|
renderCurrentSessionState() {
|
|
1270
|
+
this.loadedResourcesContainer.clear();
|
|
1264
1271
|
this.chatContainer.clear();
|
|
1265
1272
|
this.pendingMessagesContainer.clear();
|
|
1266
1273
|
this.compactionQueuedMessages = [];
|
|
@@ -1741,7 +1748,7 @@ export class InteractiveMode {
|
|
|
1741
1748
|
}, () => {
|
|
1742
1749
|
this.hideExtensionEditor();
|
|
1743
1750
|
resolve(undefined);
|
|
1744
|
-
});
|
|
1751
|
+
}, undefined, this.settingsManager.getExternalEditorCommand());
|
|
1745
1752
|
this.editorContainer.clear();
|
|
1746
1753
|
this.editorContainer.addChild(this.extensionEditor);
|
|
1747
1754
|
this.ui.setFocus(this.extensionEditor);
|
|
@@ -2677,7 +2684,7 @@ export class InteractiveMode {
|
|
|
2677
2684
|
if (this.chatContainer.children.length > 0) {
|
|
2678
2685
|
this.chatContainer.addChild(new Spacer(1));
|
|
2679
2686
|
}
|
|
2680
|
-
this.chatContainer.addChild(new Text(theme.fg("warning", `This project is not trusted. Project ${CONFIG_DIR_NAME} resources and packages are ignored. Use /trust to save a trust decision, then restart
|
|
2687
|
+
this.chatContainer.addChild(new Text(theme.fg("warning", `This project is not trusted. Project ${CONFIG_DIR_NAME} resources and packages are ignored. Use /trust to save a trust decision, then restart pi.`), 1, 0));
|
|
2681
2688
|
}
|
|
2682
2689
|
async getUserInput() {
|
|
2683
2690
|
const queuedInput = this.pendingUserInputs.shift();
|
|
@@ -2791,7 +2798,7 @@ export class InteractiveMode {
|
|
|
2791
2798
|
this.ui.stop();
|
|
2792
2799
|
}
|
|
2793
2800
|
catch { }
|
|
2794
|
-
console.error("
|
|
2801
|
+
console.error("pi exiting due to uncaughtException:");
|
|
2795
2802
|
console.error(error);
|
|
2796
2803
|
process.exit(1);
|
|
2797
2804
|
}
|
|
@@ -2965,9 +2972,11 @@ export class InteractiveMode {
|
|
|
2965
2972
|
if (isExpandable(activeHeader)) {
|
|
2966
2973
|
activeHeader.setExpanded(expanded);
|
|
2967
2974
|
}
|
|
2968
|
-
for (const
|
|
2969
|
-
|
|
2970
|
-
child
|
|
2975
|
+
for (const container of [this.loadedResourcesContainer, this.chatContainer]) {
|
|
2976
|
+
for (const child of container.children) {
|
|
2977
|
+
if (isExpandable(child)) {
|
|
2978
|
+
child.setExpanded(expanded);
|
|
2979
|
+
}
|
|
2971
2980
|
}
|
|
2972
2981
|
}
|
|
2973
2982
|
this.ui.requestRender();
|
|
@@ -2987,10 +2996,9 @@ export class InteractiveMode {
|
|
|
2987
2996
|
this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
|
|
2988
2997
|
}
|
|
2989
2998
|
async openExternalEditor() {
|
|
2990
|
-
|
|
2991
|
-
const editorCmd = process.env.VISUAL || process.env.EDITOR;
|
|
2999
|
+
const editorCmd = this.settingsManager.getExternalEditorCommand();
|
|
2992
3000
|
if (!editorCmd) {
|
|
2993
|
-
this.showWarning("No editor configured. Set
|
|
3001
|
+
this.showWarning("No editor configured. Set externalEditor in settings.json or $VISUAL/$EDITOR.");
|
|
2994
3002
|
return;
|
|
2995
3003
|
}
|
|
2996
3004
|
const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
|
|
@@ -3527,7 +3535,7 @@ export class InteractiveMode {
|
|
|
3527
3535
|
onSelect: (selection) => {
|
|
3528
3536
|
trustStore.setMany(selection.updates);
|
|
3529
3537
|
done();
|
|
3530
|
-
this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart
|
|
3538
|
+
this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart pi for this to take effect.`);
|
|
3531
3539
|
},
|
|
3532
3540
|
onCancel: () => {
|
|
3533
3541
|
done();
|
|
@@ -4454,8 +4462,12 @@ export class InteractiveMode {
|
|
|
4454
4462
|
return;
|
|
4455
4463
|
}
|
|
4456
4464
|
this.session.setSessionName(name);
|
|
4465
|
+
const sessionName = this.sessionManager.getSessionName();
|
|
4466
|
+
if (sessionName !== name) {
|
|
4467
|
+
this.showWarning(`Session name was normalized from ${JSON.stringify(name)} to ${JSON.stringify(sessionName)}`);
|
|
4468
|
+
}
|
|
4457
4469
|
this.chatContainer.addChild(new Spacer(1));
|
|
4458
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
|
|
4470
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${sessionName ?? name}`), 1, 0));
|
|
4459
4471
|
this.ui.requestRender();
|
|
4460
4472
|
}
|
|
4461
4473
|
handleSessionCommand() {
|
|
@@ -298,6 +298,20 @@ export class RpcClient {
|
|
|
298
298
|
const response = await this.send({ type: "get_fork_messages" });
|
|
299
299
|
return this.getData(response).messages;
|
|
300
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Get session entries in append order, optionally only those after the `since` entry id.
|
|
303
|
+
*/
|
|
304
|
+
async getEntries(since) {
|
|
305
|
+
const response = await this.send({ type: "get_entries", since });
|
|
306
|
+
return this.getData(response);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Get the session entry tree.
|
|
310
|
+
*/
|
|
311
|
+
async getTree() {
|
|
312
|
+
const response = await this.send({ type: "get_tree" });
|
|
313
|
+
return this.getData(response);
|
|
314
|
+
}
|
|
301
315
|
/**
|
|
302
316
|
* Get text of last assistant message.
|
|
303
317
|
*/
|
|
@@ -477,6 +477,22 @@ export async function runRpcMode(runtimeHost) {
|
|
|
477
477
|
const messages = session.getUserMessagesForForking();
|
|
478
478
|
return success(id, "get_fork_messages", { messages });
|
|
479
479
|
}
|
|
480
|
+
case "get_entries": {
|
|
481
|
+
const sessionManager = session.sessionManager;
|
|
482
|
+
let entries = sessionManager.getEntries();
|
|
483
|
+
if (command.since !== undefined) {
|
|
484
|
+
const sinceIndex = entries.findIndex((e) => e.id === command.since);
|
|
485
|
+
if (sinceIndex === -1) {
|
|
486
|
+
return error(id, "get_entries", `Entry not found: ${command.since}`);
|
|
487
|
+
}
|
|
488
|
+
entries = entries.slice(sinceIndex + 1);
|
|
489
|
+
}
|
|
490
|
+
return success(id, "get_entries", { entries, leafId: sessionManager.getLeafId() });
|
|
491
|
+
}
|
|
492
|
+
case "get_tree": {
|
|
493
|
+
const sessionManager = session.sessionManager;
|
|
494
|
+
return success(id, "get_tree", { tree: sessionManager.getTree(), leafId: sessionManager.getLeafId() });
|
|
495
|
+
}
|
|
480
496
|
case "get_last_assistant_text": {
|
|
481
497
|
const text = session.getLastAssistantText();
|
|
482
498
|
return success(id, "get_last_assistant_text", { text });
|