neoctl 0.2.10 → 0.2.11
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/dist/context/compaction.js +3 -3
- package/dist/context/compaction.js.map +1 -1
- package/dist/context/prompts.js +5 -1
- package/dist/context/prompts.js.map +1 -1
- package/dist/core/image-note-prompt.d.ts +8 -0
- package/dist/core/image-note-prompt.js +20 -0
- package/dist/core/image-note-prompt.js.map +1 -0
- package/dist/core/image-notes.d.ts +21 -0
- package/dist/core/image-notes.js +112 -0
- package/dist/core/image-notes.js.map +1 -0
- package/dist/core/image-registry.d.ts +5 -0
- package/dist/core/image-registry.js +41 -3
- package/dist/core/image-registry.js.map +1 -1
- package/dist/core/query-engine.js +6 -1
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.js +189 -1
- package/dist/core/query.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/model/anthropic-mapper.js +3 -1
- package/dist/model/anthropic-mapper.js.map +1 -1
- package/dist/model/openai-mappers.js +3 -1
- package/dist/model/openai-mappers.js.map +1 -1
- package/dist/repl/browser.d.ts +232 -0
- package/dist/repl/browser.js +156 -0
- package/dist/repl/browser.js.map +1 -0
- package/dist/repl/env-file.d.ts +4 -0
- package/dist/repl/env-file.js +97 -0
- package/dist/repl/env-file.js.map +1 -0
- package/dist/repl/foreground-exec.d.ts +10 -0
- package/dist/repl/foreground-exec.js +34 -0
- package/dist/repl/foreground-exec.js.map +1 -0
- package/dist/repl/index.js +141 -2919
- package/dist/repl/index.js.map +1 -1
- package/dist/repl/login-view.d.ts +75 -0
- package/dist/repl/login-view.js +38 -0
- package/dist/repl/login-view.js.map +1 -0
- package/dist/repl/login.d.ts +14 -0
- package/dist/repl/login.js +165 -0
- package/dist/repl/login.js.map +1 -0
- package/dist/repl/message-rendering.d.ts +99 -0
- package/dist/repl/message-rendering.js +476 -0
- package/dist/repl/message-rendering.js.map +1 -0
- package/dist/repl/prompt-payload.d.ts +9 -0
- package/dist/repl/prompt-payload.js +64 -0
- package/dist/repl/prompt-payload.js.map +1 -0
- package/dist/repl/prompt-view.d.ts +235 -0
- package/dist/repl/prompt-view.js +184 -0
- package/dist/repl/prompt-view.js.map +1 -0
- package/dist/repl/repl-types.d.ts +88 -0
- package/dist/repl/repl-types.js +2 -0
- package/dist/repl/repl-types.js.map +1 -0
- package/dist/repl/runtime.d.ts +33 -0
- package/dist/repl/runtime.js +202 -0
- package/dist/repl/runtime.js.map +1 -0
- package/dist/repl/slash-completion.d.ts +28 -0
- package/dist/repl/slash-completion.js +287 -0
- package/dist/repl/slash-completion.js.map +1 -0
- package/dist/repl/status-panel.d.ts +234 -0
- package/dist/repl/status-panel.js +509 -0
- package/dist/repl/status-panel.js.map +1 -0
- package/dist/repl/terminal.d.ts +19 -0
- package/dist/repl/terminal.js +81 -0
- package/dist/repl/terminal.js.map +1 -0
- package/dist/repl/tool-rendering.d.ts +6 -0
- package/dist/repl/tool-rendering.js +502 -0
- package/dist/repl/tool-rendering.js.map +1 -0
- package/dist/repl/usage.d.ts +17 -0
- package/dist/repl/usage.js +57 -0
- package/dist/repl/usage.js.map +1 -0
- package/dist/tools/builtins/image-generation-tool.d.ts +3 -0
- package/dist/tools/builtins/image-generation-tool.js +63 -17
- package/dist/tools/builtins/image-generation-tool.js.map +1 -1
- package/dist/tools/builtins/image-loader-tool.d.ts +7 -0
- package/dist/tools/builtins/image-loader-tool.js +29 -1
- package/dist/tools/builtins/image-loader-tool.js.map +1 -1
- package/dist/tools/builtins/image-note-tool.d.ts +29 -0
- package/dist/tools/builtins/image-note-tool.js +183 -0
- package/dist/tools/builtins/image-note-tool.js.map +1 -0
- package/dist/tools/smoke-tool-system.js +4 -2
- package/dist/tools/smoke-tool-system.js.map +1 -1
- package/dist/web/index.js +2 -0
- package/dist/web/index.js.map +1 -1
- package/package.json +1 -1
package/dist/repl/index.js
CHANGED
|
@@ -1,137 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import { stdin, stdout } from "node:process";
|
|
6
4
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
7
|
-
import { Box, Static,
|
|
8
|
-
import stripAnsi from "strip-ansi";
|
|
9
|
-
import wrapAnsi from "wrap-ansi";
|
|
10
|
-
import { QueryEngine } from "../core/query-engine.js";
|
|
11
|
-
import { getUserDotEnvPath, loadDefaultDotEnvFiles } from "../model/env.js";
|
|
5
|
+
import { Box, Static, render, useApp, useInput } from "ink";
|
|
12
6
|
import { readModelProviderConfig } from "../model/config.js";
|
|
13
|
-
import { findModelMetadata,
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { ToolRegistry } from "../tools/registry.js";
|
|
17
|
-
import { editTool, writeTool } from "../tools/builtins/edit-tool.js";
|
|
18
|
-
import { createExecTool } from "../tools/builtins/exec-tool.js";
|
|
19
|
-
import { listDirectoryTool, readFileTool } from "../tools/builtins/filesystem-tools.js";
|
|
20
|
-
import { grepTool } from "../tools/builtins/grep-tool.js";
|
|
21
|
-
import { searchTool } from "../tools/builtins/search-tool.js";
|
|
22
|
-
import { planTool } from "../tools/builtins/plan-tool.js";
|
|
23
|
-
import { createOpenAIImageGenerationTool } from "../tools/builtins/image-generation-tool.js";
|
|
24
|
-
import { createLoadImageTool } from "../tools/builtins/image-loader-tool.js";
|
|
25
|
-
import { createSecretTools } from "../tools/builtins/secret-tools.js";
|
|
26
|
-
import { SecretStore } from "../secrets/secret-store.js";
|
|
27
|
-
import { InMemorySecretRedactionRegistry } from "../secrets/secret-redaction.js";
|
|
28
|
-
import { createAgentTool, resumeAgentTask } from "../agents/agent-tool.js";
|
|
29
|
-
import { AgentActivityStore } from "../agents/agent-activity.js";
|
|
30
|
-
import { createTaskTools } from "../tasks/task-tools.js";
|
|
31
|
-
import { TaskStore } from "../tasks/task-store.js";
|
|
32
|
-
import { cliHelpText, isModelReasoningArgument, isValidReplCommandLine, parseCliReplCommandArgs, parseReplCommand, helpText, replCommandDefinitions } from "./commands.js";
|
|
33
|
-
import { estimateMarkdownLineCount, markdownRenderKey, MarkdownText } from "./markdown-renderer.js";
|
|
34
|
-
import { DefaultContextManager } from "../context/context-manager.js";
|
|
35
|
-
import { buildEffectiveSystemPrompt } from "../context/prompts.js";
|
|
7
|
+
import { findModelMetadata, reasoningEffortsForModel, resolveContextWindowTokens } from "../model/context-window.js";
|
|
8
|
+
import { createModelGatewayFromConfig } from "../model/provider-factory.js";
|
|
9
|
+
import { cliHelpText, parseCliReplCommandArgs, parseReplCommand, helpText } from "./commands.js";
|
|
36
10
|
import { writeSessionMarkdownExport } from "../session/session-export.js";
|
|
37
11
|
import { readClipboard } from "./clipboard.js";
|
|
38
12
|
import { formatTipLine, initialTipIndex, tipAt } from "../tips.js";
|
|
39
13
|
import { openDirectory } from "../open-directory.js";
|
|
40
14
|
import { runWebServer } from "../web/index.js";
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
15
|
+
import { requireSkillName } from "../skills/skill-tool.js";
|
|
16
|
+
import { createRuntime, syncImageGenerationTool } from "./runtime.js";
|
|
17
|
+
import { attachmentsForText, buildPromptPayload, shouldFoldClipboardText } from "./prompt-payload.js";
|
|
18
|
+
import { formatReplData, formatToolFinishedWithoutResult } from "./tool-rendering.js";
|
|
19
|
+
import { applyLoginFormToProcessEnv, createLoginFormState, cycleLoginFieldOption, deleteLoginFieldCharacter, insertLoginFieldText, LOGIN_FIELD_DEFINITIONS, loginFormForProvider, moveLoginFieldSelection, moveLoginProviderSelection, parseLoginProvider, saveLoginFormToEnv, validateLoginForm } from "./login.js";
|
|
20
|
+
import { applyEnvUpdatesToProcess, writeEnvUpdates } from "./env-file.js";
|
|
21
|
+
import { formatResume, movePagedPage, movePagedSelection, moveSessionsPage, moveSessionsSelection, pagedAbsoluteIndex, SecretsBrowser, secretsBrowserViewHeight, sessionAbsoluteIndex, SessionsBrowser, sessionsBrowserViewHeight, SkillsBrowser, skillsBrowserViewHeight } from "./browser.js";
|
|
22
|
+
import { BackgroundTaskStatusLine, backgroundTaskStatusRenderRows, ForegroundExecDetachHintLine, isActivePhase, reduceStatus, StatusBar, SubagentLivePanel, subagentLivePanelRenderRows } from "./status-panel.js";
|
|
23
|
+
import { selectedSlashCommandCompletion, slashCommandCompletions, slashCompletionSelectableCount, slashCompletionViewHeight, SLASH_COMPLETION_PAGE_SIZE } from "./slash-completion.js";
|
|
24
|
+
import { PasteStatusLine, PromptLine, promptPrefix, promptTextView, QueuedInputLine } from "./prompt-view.js";
|
|
25
|
+
import { LoginFormView, loginFormViewHeight } from "./login-view.js";
|
|
26
|
+
import { assistantText, lineNeedsDynamicRender, lineRenderContentWidth, MessageBlock, MessageList, renderMessage, renderToolResultMessage, systemLine, thinkingLine, thinkingText } from "./message-rendering.js";
|
|
27
|
+
import { disableTerminalFocusReporting, disableTerminalMouseReporting, enableTerminalFocusReporting, enableTerminalMouseReporting, isPasteShortcut, isRightClickPasteSequence, isTerminalFocusInSequence, isTerminalFocusOutSequence, mouseScrollDirection, playReadySound, setTerminalTitle, useTerminalSize } from "./terminal.js";
|
|
45
28
|
const e = React.createElement;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!handle)
|
|
71
|
-
return { ok: false, message: "No foreground exec command is currently running" };
|
|
72
|
-
return handle.detach();
|
|
73
|
-
}
|
|
74
|
-
notify() {
|
|
75
|
-
for (const listener of this.subscribers)
|
|
76
|
-
listener();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
class SessionUsageTracker {
|
|
80
|
-
totals = emptyUsageTotals();
|
|
81
|
-
lastUsage;
|
|
82
|
-
add(usage) {
|
|
83
|
-
if (usage === this.lastUsage)
|
|
84
|
-
return;
|
|
85
|
-
this.lastUsage = usage;
|
|
86
|
-
const inputTokens = usageTokenValue(usage.inputTokens);
|
|
87
|
-
const outputTokens = usageTokenValue(usage.outputTokens);
|
|
88
|
-
const reportedTotalTokens = usageTokenValue(usage.totalTokens);
|
|
89
|
-
const computedTotalTokens = reportedTotalTokens ?? sumUsageTokens(inputTokens, outputTokens);
|
|
90
|
-
const reasoningTokens = usageTokenValue(usage.reasoningTokens);
|
|
91
|
-
const cachedTokens = usageTokenValue(usage.cachedTokens);
|
|
92
|
-
if (inputTokens === undefined &&
|
|
93
|
-
outputTokens === undefined &&
|
|
94
|
-
computedTotalTokens === undefined &&
|
|
95
|
-
reasoningTokens === undefined &&
|
|
96
|
-
cachedTokens === undefined)
|
|
97
|
-
return;
|
|
98
|
-
this.totals = {
|
|
99
|
-
inputTokens: this.totals.inputTokens + (inputTokens ?? 0),
|
|
100
|
-
outputTokens: this.totals.outputTokens + (outputTokens ?? 0),
|
|
101
|
-
totalTokens: this.totals.totalTokens + (computedTotalTokens ?? 0),
|
|
102
|
-
reasoningTokens: this.totals.reasoningTokens + (reasoningTokens ?? 0),
|
|
103
|
-
cachedTokens: this.totals.cachedTokens + (cachedTokens ?? 0),
|
|
104
|
-
requests: this.totals.requests + 1,
|
|
105
|
-
computedTotalTokens: this.totals.computedTotalTokens || (reportedTotalTokens === undefined && computedTotalTokens !== undefined),
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
reset() {
|
|
109
|
-
this.totals = emptyUsageTotals();
|
|
110
|
-
this.lastUsage = undefined;
|
|
111
|
-
}
|
|
112
|
-
snapshot() {
|
|
113
|
-
return { ...this.totals };
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
function emptyUsageTotals() {
|
|
117
|
-
return {
|
|
118
|
-
inputTokens: 0,
|
|
119
|
-
outputTokens: 0,
|
|
120
|
-
totalTokens: 0,
|
|
121
|
-
reasoningTokens: 0,
|
|
122
|
-
cachedTokens: 0,
|
|
123
|
-
requests: 0,
|
|
124
|
-
computedTotalTokens: false,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
function usageTokenValue(value) {
|
|
128
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
129
|
-
}
|
|
130
|
-
function sumUsageTokens(left, right) {
|
|
131
|
-
if (left === undefined && right === undefined)
|
|
132
|
-
return undefined;
|
|
133
|
-
return (left ?? 0) + (right ?? 0);
|
|
134
|
-
}
|
|
29
|
+
const SESSIONS_DEFAULT_PAGE_SIZE = 10;
|
|
30
|
+
const TERMINAL_TITLE_WORKING_PREFIX = "*";
|
|
31
|
+
const TERMINAL_TITLE_READY_PREFIX = "✓";
|
|
32
|
+
const REPL_ANIMATION_INTERVAL_MS = 420;
|
|
33
|
+
const TOOL_RESULT_REPLACEMENT_DELAY_MS = 2000;
|
|
34
|
+
const SUBAGENT_ACTIVITY_UPDATE_DEBOUNCE_MS = 1000;
|
|
35
|
+
const SUBAGENT_COMPLETED_LINGER_MS = 8000;
|
|
36
|
+
const PASTE_STATUS_DISPLAY_MS = 2500;
|
|
37
|
+
const STATUS_BAR_RENDER_ROWS = 1;
|
|
38
|
+
const FOREGROUND_EXEC_DETACH_HINT_RENDER_ROWS = 1;
|
|
39
|
+
const FOREGROUND_EXEC_DETACH_HINT_DELAY_MS = 2000;
|
|
40
|
+
const QUEUED_INPUT_RENDER_ROWS = 1;
|
|
41
|
+
const EMPTY_CTRL_C_EXIT_PLACEHOLDER = "Press Ctrl+C again to exit";
|
|
42
|
+
const MIN_LIVE_VIEWPORT_LINES = 1;
|
|
43
|
+
const FULLSCREEN_RENDER_GUARD_ROWS = 3;
|
|
44
|
+
const MESSAGE_BLOCK_SPACING_LINES = 1;
|
|
45
|
+
const SUMMARY_BLOCK = {
|
|
46
|
+
maxLines: 6,
|
|
47
|
+
detailIndent: " ",
|
|
48
|
+
};
|
|
49
|
+
const THINKING_COLOR = "#a855f7";
|
|
50
|
+
const THINKING_MARKER = "~";
|
|
51
|
+
const THINKING_SUMMARY_MAX_LINES = 1000;
|
|
52
|
+
const EXPANDED_SUMMARY_MAX_LINES = 1000;
|
|
135
53
|
async function main(argv = process.argv.slice(2)) {
|
|
136
54
|
const webArgs = parseWebCliArgs(argv);
|
|
137
55
|
if (webArgs) {
|
|
@@ -171,174 +89,6 @@ function binaryName() {
|
|
|
171
89
|
const name = parsed.name || "neo";
|
|
172
90
|
return name === "index" ? "neo" : name;
|
|
173
91
|
}
|
|
174
|
-
class SkillCatalogContextManager {
|
|
175
|
-
catalog;
|
|
176
|
-
base;
|
|
177
|
-
constructor(catalog, base = new DefaultContextManager()) {
|
|
178
|
-
this.catalog = catalog;
|
|
179
|
-
this.base = base;
|
|
180
|
-
}
|
|
181
|
-
async build(input) {
|
|
182
|
-
const runtimeContext = await this.base.build(input);
|
|
183
|
-
const skillSection = await buildSkillCatalogPromptSection(this.catalog);
|
|
184
|
-
if (!skillSection)
|
|
185
|
-
return runtimeContext;
|
|
186
|
-
const promptSections = [...runtimeContext.promptSections, skillSection];
|
|
187
|
-
return {
|
|
188
|
-
...runtimeContext,
|
|
189
|
-
promptSections,
|
|
190
|
-
systemPrompt: buildEffectiveSystemPrompt(promptSections, input),
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
async function buildSkillCatalogPromptSection(catalog) {
|
|
195
|
-
const skills = await catalog.list();
|
|
196
|
-
if (skills.length === 0)
|
|
197
|
-
return undefined;
|
|
198
|
-
const visible = skills.slice(0, 80);
|
|
199
|
-
const lines = visible.map((skill) => {
|
|
200
|
-
const tags = skill.tags?.length ? `; tags=${skill.tags.join(",")}` : "";
|
|
201
|
-
const tools = skill.allowedTools?.length ? `; allowedTools=${skill.allowedTools.join(",")}` : "";
|
|
202
|
-
return `- ${skill.name}: ${skill.description} (execution=${skill.execution}${tags}${tools})`;
|
|
203
|
-
});
|
|
204
|
-
if (skills.length > visible.length)
|
|
205
|
-
lines.push(`- ... ${skills.length - visible.length} more skills available; use skill_list for the full catalog.`);
|
|
206
|
-
return {
|
|
207
|
-
name: "Available Skills",
|
|
208
|
-
cacheStable: false,
|
|
209
|
-
content: [
|
|
210
|
-
"Reusable skills are available through the `skill` tool and the `/skill` REPL command.",
|
|
211
|
-
"When the user's task matches a skill name, description, tags, or domain capability, proactively call the `skill` tool before doing the work directly.",
|
|
212
|
-
"Do not wait for the user to explicitly say 'use skill'. Use skill_list/skill_read if you need to inspect details.",
|
|
213
|
-
"Available skill catalog:",
|
|
214
|
-
...lines,
|
|
215
|
-
].join("\n"),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
function createTaskNotificationSource(taskStore) {
|
|
219
|
-
return {
|
|
220
|
-
collectUnnotifiedCompletions() {
|
|
221
|
-
return taskStore.collectUnnotifiedCompletions().map((task) => ({
|
|
222
|
-
taskId: task.taskId,
|
|
223
|
-
agentId: task.agentId,
|
|
224
|
-
status: task.status,
|
|
225
|
-
type: task.type,
|
|
226
|
-
content: task.result?.content ?? task.error ?? "",
|
|
227
|
-
}));
|
|
228
|
-
},
|
|
229
|
-
markNotified(taskId) {
|
|
230
|
-
taskStore.markNotified(taskId);
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
async function createRuntime() {
|
|
235
|
-
const envLoad = loadDefaultDotEnvFiles({ override: true });
|
|
236
|
-
const modelConfig = readModelProviderConfig(process.env);
|
|
237
|
-
const communicationLogger = new CommunicationLogger();
|
|
238
|
-
const modelGateway = new LoggingModelGateway(createModelGatewayFromProcessEnv(process.env), communicationLogger);
|
|
239
|
-
const taskStore = new TaskStore();
|
|
240
|
-
const agentActivityStore = new AgentActivityStore();
|
|
241
|
-
const foregroundExecDetach = new ReplForegroundExecDetachRegistry();
|
|
242
|
-
const secretStore = await SecretStore.open();
|
|
243
|
-
const secretRedactions = new InMemorySecretRedactionRegistry();
|
|
244
|
-
const tools = new ToolRegistry();
|
|
245
|
-
const skillWorkspaceRoot = path.resolve(process.cwd(), ".neo", "skills");
|
|
246
|
-
const skills = new FileSystemSkillCatalog({
|
|
247
|
-
roots: [
|
|
248
|
-
{ root: skillWorkspaceRoot, kind: "workspace" },
|
|
249
|
-
{ root: path.resolve(getNeoctlHome(), "skills"), kind: "user" },
|
|
250
|
-
],
|
|
251
|
-
createRoot: skillWorkspaceRoot,
|
|
252
|
-
});
|
|
253
|
-
tools.register(editTool);
|
|
254
|
-
tools.register(writeTool);
|
|
255
|
-
tools.register(createExecTool({ taskStore, foregroundDetachRegistry: foregroundExecDetach }));
|
|
256
|
-
tools.register(listDirectoryTool);
|
|
257
|
-
tools.register(readFileTool);
|
|
258
|
-
tools.register(grepTool);
|
|
259
|
-
tools.register(searchTool);
|
|
260
|
-
tools.register(createLoadImageTool());
|
|
261
|
-
if (modelConfig?.provider === "openai")
|
|
262
|
-
tools.register(createOpenAIImageGenerationTool());
|
|
263
|
-
tools.register(planTool);
|
|
264
|
-
for (const tool of createSecretTools())
|
|
265
|
-
tools.register(tool);
|
|
266
|
-
tools.register(createSkillTool(skills));
|
|
267
|
-
for (const tool of createSkillManagementTools(skills, { requireApproval: true, allowDelete: false }))
|
|
268
|
-
tools.register(tool);
|
|
269
|
-
const agentRuntime = { modelGateway, tools, taskStore, agentActivityStore };
|
|
270
|
-
tools.register(createAgentTool(agentRuntime));
|
|
271
|
-
const resumeHandler = async (taskId, directive) => {
|
|
272
|
-
const dummyContext = {
|
|
273
|
-
agentId: "main",
|
|
274
|
-
tools,
|
|
275
|
-
appState: new (await import("../app/app-state.js")).InMemoryAppState("main"),
|
|
276
|
-
secrets: secretStore,
|
|
277
|
-
secretRedactions,
|
|
278
|
-
emit: () => undefined,
|
|
279
|
-
};
|
|
280
|
-
return resumeAgentTask(taskId, directive, agentRuntime, taskStore, dummyContext);
|
|
281
|
-
};
|
|
282
|
-
for (const tool of createTaskTools(taskStore, resumeHandler))
|
|
283
|
-
tools.register(tool);
|
|
284
|
-
const taskNotificationSource = createTaskNotificationSource(taskStore);
|
|
285
|
-
const engine = new QueryEngine({
|
|
286
|
-
agentId: "main",
|
|
287
|
-
model: modelConfig?.model,
|
|
288
|
-
fallbackModel: modelConfig?.fallbackModel,
|
|
289
|
-
reasoning: modelConfig?.defaultReasoning,
|
|
290
|
-
modelGateway,
|
|
291
|
-
tools,
|
|
292
|
-
contextManager: new SkillCatalogContextManager(skills),
|
|
293
|
-
canUseTool: createSkillAwareCanUseTool(skills),
|
|
294
|
-
secrets: secretStore,
|
|
295
|
-
secretRedactions,
|
|
296
|
-
taskNotificationSource,
|
|
297
|
-
commands: replCommandDefinitions.map((command) => command.usage),
|
|
298
|
-
session: {
|
|
299
|
-
enabled: process.env.AGENT_SESSION_TRANSCRIPT !== "0",
|
|
300
|
-
sessionId: process.env.AGENT_SESSION_ID,
|
|
301
|
-
rootDir: process.env.AGENT_SESSION_DIR,
|
|
302
|
-
resume: parseResumeFlag(process.env.AGENT_SESSION_RESUME),
|
|
303
|
-
toolResultThresholdChars: process.env.AGENT_TOOL_RESULT_THRESHOLD_CHARS
|
|
304
|
-
? Number(process.env.AGENT_TOOL_RESULT_THRESHOLD_CHARS)
|
|
305
|
-
: undefined,
|
|
306
|
-
},
|
|
307
|
-
});
|
|
308
|
-
await engine.initialize();
|
|
309
|
-
const initialMetrics = await engine.contextMetrics();
|
|
310
|
-
return {
|
|
311
|
-
engine,
|
|
312
|
-
communicationLogger,
|
|
313
|
-
modelGateway,
|
|
314
|
-
agentRuntime,
|
|
315
|
-
usage: new SessionUsageTracker(),
|
|
316
|
-
taskStore,
|
|
317
|
-
agentActivityStore,
|
|
318
|
-
foregroundExecDetach,
|
|
319
|
-
tools,
|
|
320
|
-
skills,
|
|
321
|
-
secretStore,
|
|
322
|
-
skillWorkspaceRoot,
|
|
323
|
-
initialMetrics,
|
|
324
|
-
defaultReasoning: modelConfig?.defaultReasoning,
|
|
325
|
-
envPath: process.env.NEO_ENV_FILE?.trim() ? path.resolve(process.env.NEO_ENV_FILE.trim()) : envLoad.userDotEnvPath,
|
|
326
|
-
envNotice: envLoad.createdUserDotEnv ? formatCreatedEnvNotice(envLoad.userDotEnvPath) : undefined,
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
function syncImageGenerationTool(runtime, provider) {
|
|
330
|
-
runtime.tools.unregister("image2");
|
|
331
|
-
if (provider === "openai")
|
|
332
|
-
runtime.tools.register(createOpenAIImageGenerationTool());
|
|
333
|
-
}
|
|
334
|
-
function formatCreatedEnvNotice(path) {
|
|
335
|
-
return `Created default config file: ${path}\nSet MODEL_PROVIDER and the matching provider section (OPENAI_API_KEY or ANTHROPIC_API_KEY), then restart neo.`;
|
|
336
|
-
}
|
|
337
|
-
function parseResumeFlag(value) {
|
|
338
|
-
if (!value)
|
|
339
|
-
return false;
|
|
340
|
-
return ["1", "true", "yes", "latest"].includes(value.toLowerCase());
|
|
341
|
-
}
|
|
342
92
|
function activeBackgroundTasks(runtime) {
|
|
343
93
|
return runtime.taskStore.list().filter((task) => !runtime.taskStore.isTerminal(task));
|
|
344
94
|
}
|
|
@@ -387,132 +137,8 @@ function initialStatus(runtime, metrics = runtime.initialMetrics) {
|
|
|
387
137
|
async function resetStatus(runtime) {
|
|
388
138
|
return initialStatus(runtime, await runtime.engine.contextMetrics());
|
|
389
139
|
}
|
|
390
|
-
function setTerminalTitle(title, prefix = TERMINAL_TITLE_WORKING_PREFIX) {
|
|
391
|
-
if (!stdout.isTTY)
|
|
392
|
-
return;
|
|
393
|
-
const safeTitle = title.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim();
|
|
394
|
-
const decoratedTitle = `${prefix}${safeTitle || "neo"}`.slice(0, 120);
|
|
395
|
-
stdout.write(`\u001b]0;${decoratedTitle}\u0007`);
|
|
396
|
-
}
|
|
397
|
-
function playReadySound() {
|
|
398
|
-
if (!stdout.isTTY)
|
|
399
|
-
return;
|
|
400
|
-
stdout.write("\u0007");
|
|
401
|
-
}
|
|
402
|
-
function enableTerminalFocusReporting() {
|
|
403
|
-
if (!stdout.isTTY)
|
|
404
|
-
return;
|
|
405
|
-
stdout.write("\u001b[?1004h");
|
|
406
|
-
}
|
|
407
|
-
function enableTerminalMouseReporting() {
|
|
408
|
-
if (!stdout.isTTY || !stdin.isTTY)
|
|
409
|
-
return;
|
|
410
|
-
// Only enable SGR extended coordinates; no tracking mode (?1000h etc.)
|
|
411
|
-
// is activated so the terminal keeps handling scroll-wheel natively.
|
|
412
|
-
// Right-click paste is handled via Ctrl+V / Cmd+V instead.
|
|
413
|
-
stdout.write("\u001b[?1006h");
|
|
414
|
-
}
|
|
415
|
-
function disableTerminalFocusReporting() {
|
|
416
|
-
if (!stdout.isTTY)
|
|
417
|
-
return;
|
|
418
|
-
stdout.write("\u001b[?1004l");
|
|
419
|
-
}
|
|
420
|
-
function disableTerminalMouseReporting() {
|
|
421
|
-
if (!stdout.isTTY)
|
|
422
|
-
return;
|
|
423
|
-
stdout.write("\u001b[?1006l");
|
|
424
|
-
}
|
|
425
|
-
function isTerminalFocusInSequence(value) {
|
|
426
|
-
return value === "\u001b[I";
|
|
427
|
-
}
|
|
428
|
-
function isTerminalFocusOutSequence(value) {
|
|
429
|
-
return value === "\u001b[O";
|
|
430
|
-
}
|
|
431
140
|
function sessionTerminalTitle(snapshot) {
|
|
432
|
-
return snapshot?.title?.trim() || "neo";
|
|
433
|
-
}
|
|
434
|
-
function isPasteShortcut(value, key) {
|
|
435
|
-
return (key.ctrl === true && value === "v") || (key.meta === true && value === "v") || value === "\u0016" || value === "\u001bv";
|
|
436
|
-
}
|
|
437
|
-
function isRightClickPasteSequence(value) {
|
|
438
|
-
const match = /^\u001b\[<(\d+);\d+;\d+M$/u.exec(value);
|
|
439
|
-
if (!match)
|
|
440
|
-
return false;
|
|
441
|
-
const button = Number(match[1]);
|
|
442
|
-
return button % 4 === 2;
|
|
443
|
-
}
|
|
444
|
-
function mouseScrollDirection(value) {
|
|
445
|
-
const match = /^\u001b\[<(\d+);\d+;\d+[Mm]$/u.exec(value);
|
|
446
|
-
if (!match)
|
|
447
|
-
return undefined;
|
|
448
|
-
const button = Number(match[1]);
|
|
449
|
-
if (button === 64)
|
|
450
|
-
return "up";
|
|
451
|
-
if (button === 65)
|
|
452
|
-
return "down";
|
|
453
|
-
return undefined;
|
|
454
|
-
}
|
|
455
|
-
function shouldFoldClipboardText(text) {
|
|
456
|
-
return text.length >= LONG_CLIPBOARD_TEXT_THRESHOLD;
|
|
457
|
-
}
|
|
458
|
-
function attachmentsForText(text, attachments) {
|
|
459
|
-
return attachments.filter((attachment) => text.includes(attachment.label));
|
|
460
|
-
}
|
|
461
|
-
function buildPromptPayload(displayText, attachments) {
|
|
462
|
-
const activeAttachments = attachmentsForText(displayText, attachments);
|
|
463
|
-
if (activeAttachments.length === 0)
|
|
464
|
-
return { text: displayText };
|
|
465
|
-
const blocks = [];
|
|
466
|
-
let cursor = 0;
|
|
467
|
-
while (cursor < displayText.length) {
|
|
468
|
-
const next = nextAttachmentOccurrence(displayText, activeAttachments, cursor);
|
|
469
|
-
if (!next) {
|
|
470
|
-
pushTextBlock(blocks, displayText.slice(cursor));
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
pushTextBlock(blocks, displayText.slice(cursor, next.index));
|
|
474
|
-
if (next.attachment.kind === "text" && next.attachment.text !== undefined) {
|
|
475
|
-
pushTextBlock(blocks, next.attachment.text);
|
|
476
|
-
}
|
|
477
|
-
else if (next.attachment.kind === "image" && next.attachment.image) {
|
|
478
|
-
blocks.push({ type: "image", mimeType: next.attachment.image.mimeType, data: next.attachment.image.data, label: next.attachment.label });
|
|
479
|
-
}
|
|
480
|
-
cursor = next.index + next.attachment.label.length;
|
|
481
|
-
}
|
|
482
|
-
const text = blocks
|
|
483
|
-
.map((block) => {
|
|
484
|
-
if (block.type === "text")
|
|
485
|
-
return block.text;
|
|
486
|
-
if (block.type === "image")
|
|
487
|
-
return block.label ?? "[image]";
|
|
488
|
-
return "";
|
|
489
|
-
})
|
|
490
|
-
.join("");
|
|
491
|
-
return { text, blocks };
|
|
492
|
-
}
|
|
493
|
-
function nextAttachmentOccurrence(text, attachments, start) {
|
|
494
|
-
let best;
|
|
495
|
-
for (const attachment of attachments) {
|
|
496
|
-
const index = text.indexOf(attachment.label, start);
|
|
497
|
-
if (index === -1)
|
|
498
|
-
continue;
|
|
499
|
-
if (!best || index < best.index)
|
|
500
|
-
best = { index, attachment };
|
|
501
|
-
}
|
|
502
|
-
return best;
|
|
503
|
-
}
|
|
504
|
-
function pushTextBlock(blocks, text) {
|
|
505
|
-
if (!text)
|
|
506
|
-
return;
|
|
507
|
-
const previous = blocks[blocks.length - 1];
|
|
508
|
-
if (previous?.type === "text") {
|
|
509
|
-
previous.text += text;
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
blocks.push({ type: "text", text });
|
|
513
|
-
}
|
|
514
|
-
function escapeRegExp(value) {
|
|
515
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
141
|
+
return snapshot?.title?.trim() || snapshot?.sessionId || "neo";
|
|
516
142
|
}
|
|
517
143
|
function InkRepl({ runtime, initialCommandLine }) {
|
|
518
144
|
const app = useApp();
|
|
@@ -520,10 +146,13 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
520
146
|
const assistantLineId = useRef(undefined);
|
|
521
147
|
const thinkingLineId = useRef(undefined);
|
|
522
148
|
const finalizedThinkingLineId = useRef(undefined);
|
|
149
|
+
const assistantDeltaBuffer = useRef("");
|
|
150
|
+
const thinkingDeltaBuffer = useRef("");
|
|
523
151
|
const activeAbortController = useRef(undefined);
|
|
524
152
|
const interruptArmed = useRef(false);
|
|
525
153
|
const history = useRef([]);
|
|
526
154
|
const toolLineIds = useRef(new Map());
|
|
155
|
+
const renderedToolResultIds = useRef(new Set());
|
|
527
156
|
const pendingToolResultTimers = useRef(new Map());
|
|
528
157
|
const [lines, setLines] = useState(() => initialLines(runtime, lineId));
|
|
529
158
|
const [input, setInput] = useState("");
|
|
@@ -852,7 +481,10 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
852
481
|
assistantLineId.current = undefined;
|
|
853
482
|
thinkingLineId.current = undefined;
|
|
854
483
|
finalizedThinkingLineId.current = undefined;
|
|
484
|
+
assistantDeltaBuffer.current = "";
|
|
485
|
+
thinkingDeltaBuffer.current = "";
|
|
855
486
|
toolLineIds.current.clear();
|
|
487
|
+
renderedToolResultIds.current.clear();
|
|
856
488
|
clearPendingToolResultTimers();
|
|
857
489
|
};
|
|
858
490
|
const resumeSnapshot = (snapshot, metrics) => {
|
|
@@ -925,7 +557,18 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
925
557
|
finalizeToolLine(id);
|
|
926
558
|
toolLineIds.current.clear();
|
|
927
559
|
};
|
|
560
|
+
const flushBufferedModelOutput = () => {
|
|
561
|
+
const thinkingText = thinkingDeltaBuffer.current;
|
|
562
|
+
const assistantText = assistantDeltaBuffer.current;
|
|
563
|
+
thinkingDeltaBuffer.current = "";
|
|
564
|
+
assistantDeltaBuffer.current = "";
|
|
565
|
+
if (thinkingText)
|
|
566
|
+
append(thinkingLine(thinkingText));
|
|
567
|
+
if (assistantText)
|
|
568
|
+
append({ kind: "assistant", text: assistantText });
|
|
569
|
+
};
|
|
928
570
|
const finalizeForegroundView = () => {
|
|
571
|
+
flushBufferedModelOutput();
|
|
929
572
|
finalizeLiveLine(assistantLineId.current);
|
|
930
573
|
finalizeThinkingLine();
|
|
931
574
|
finalizeActiveToolLines();
|
|
@@ -933,6 +576,14 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
933
576
|
finalizedThinkingLineId.current = undefined;
|
|
934
577
|
};
|
|
935
578
|
const handleEvent = (event) => {
|
|
579
|
+
if (event.type === "assistant.delta") {
|
|
580
|
+
assistantDeltaBuffer.current += event.text;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (event.type === "thinking.delta") {
|
|
584
|
+
thinkingDeltaBuffer.current += event.text;
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
936
587
|
setStatus((current) => reduceStatus(current, event));
|
|
937
588
|
if (event.type === "usage")
|
|
938
589
|
runtime.usage.add(event.usage);
|
|
@@ -940,46 +591,18 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
940
591
|
return;
|
|
941
592
|
if (event.type === "context.metrics" || event.type === "usage" || event.type === "tool_call.delta")
|
|
942
593
|
return;
|
|
943
|
-
if (event.type === "assistant.delta") {
|
|
944
|
-
finalizeThinkingLine();
|
|
945
|
-
const id = assistantLineId.current ?? append({ kind: "assistant", text: "", live: true });
|
|
946
|
-
assistantLineId.current = id;
|
|
947
|
-
updateLine(id, (text) => text + event.text);
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
if (event.type === "thinking.delta") {
|
|
951
|
-
const id = thinkingLineId.current ?? finalizedThinkingLineId.current ?? append(thinkingLine("", true));
|
|
952
|
-
thinkingLineId.current = id;
|
|
953
|
-
finalizedThinkingLineId.current = undefined;
|
|
954
|
-
updateLine(id, (text) => text + event.text);
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
594
|
if (event.type === "message") {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
if (
|
|
962
|
-
|
|
963
|
-
finalizeLiveLine(assistantLineId.current);
|
|
964
|
-
assistantLineId.current = undefined;
|
|
965
|
-
replacedStreamingContent = true;
|
|
966
|
-
}
|
|
595
|
+
if (event.message.role === "assistant") {
|
|
596
|
+
if (assistantText(event.message) !== undefined)
|
|
597
|
+
assistantDeltaBuffer.current = "";
|
|
598
|
+
if (thinkingText(event.message) !== undefined)
|
|
599
|
+
thinkingDeltaBuffer.current = "";
|
|
967
600
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
const text = thinkingText(event.message);
|
|
971
|
-
if (text !== undefined) {
|
|
972
|
-
replaceLineText(existingThinkingLineId, text);
|
|
973
|
-
finalizeLiveLine(existingThinkingLineId);
|
|
974
|
-
thinkingLineId.current = undefined;
|
|
975
|
-
finalizedThinkingLineId.current = undefined;
|
|
976
|
-
replacedStreamingContent = true;
|
|
977
|
-
}
|
|
601
|
+
else {
|
|
602
|
+
flushBufferedModelOutput();
|
|
978
603
|
}
|
|
979
|
-
if (replacedStreamingContent)
|
|
980
|
-
return;
|
|
981
604
|
if (event.message.role === "tool_result") {
|
|
982
|
-
renderToolResultMessage(event.message, append,
|
|
605
|
+
renderToolResultMessage(event.message, append, renderedToolResultIds.current);
|
|
983
606
|
return;
|
|
984
607
|
}
|
|
985
608
|
if (event.message.role !== "assistant") {
|
|
@@ -987,7 +610,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
987
610
|
finalizeThinkingLine();
|
|
988
611
|
assistantLineId.current = undefined;
|
|
989
612
|
}
|
|
990
|
-
const rendered = renderMessage(event.message, append
|
|
613
|
+
const rendered = renderMessage(event.message, append);
|
|
991
614
|
if (rendered && event.message.role === "assistant") {
|
|
992
615
|
finalizeLiveLine(assistantLineId.current);
|
|
993
616
|
finalizeThinkingLine();
|
|
@@ -996,22 +619,21 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
996
619
|
return;
|
|
997
620
|
}
|
|
998
621
|
if (event.type === "tool.started") {
|
|
622
|
+
flushBufferedModelOutput();
|
|
999
623
|
finalizeLiveLine(assistantLineId.current);
|
|
1000
624
|
finalizeThinkingLine();
|
|
1001
|
-
const id = append({ ...formatToolUse(event.toolUse), live: true });
|
|
1002
|
-
toolLineIds.current.set(event.toolUse.id, id);
|
|
1003
625
|
return;
|
|
1004
626
|
}
|
|
1005
627
|
if (event.type === "tool.finished") {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
replaceLine(id, formatToolFinishedWithoutResult(event.toolUse, event.ok));
|
|
628
|
+
if (!renderedToolResultIds.current.has(event.toolUse.id)) {
|
|
629
|
+
append(formatToolFinishedWithoutResult(event.toolUse, event.ok));
|
|
1009
630
|
}
|
|
1010
631
|
return;
|
|
1011
632
|
}
|
|
1012
633
|
if (event.type === "retrying")
|
|
1013
634
|
return;
|
|
1014
635
|
if (event.type === "terminal") {
|
|
636
|
+
flushBufferedModelOutput();
|
|
1015
637
|
finalizeLiveLine(assistantLineId.current);
|
|
1016
638
|
finalizeThinkingLine();
|
|
1017
639
|
finalizeActiveToolLines();
|
|
@@ -1240,7 +862,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1240
862
|
setSkillsBrowser(undefined);
|
|
1241
863
|
setSecretsBrowser(undefined);
|
|
1242
864
|
setLoginFormState(createLoginFormState(runtime.envPath));
|
|
1243
|
-
append(systemLine("Opening provider login. Use
|
|
865
|
+
append(systemLine("Opening provider login. Use 鈫?鈫?to choose, Enter to continue/save, Esc to cancel."));
|
|
1244
866
|
return;
|
|
1245
867
|
}
|
|
1246
868
|
if (command.type === "log") {
|
|
@@ -1352,7 +974,10 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1352
974
|
assistantLineId.current = undefined;
|
|
1353
975
|
thinkingLineId.current = undefined;
|
|
1354
976
|
finalizedThinkingLineId.current = undefined;
|
|
977
|
+
assistantDeltaBuffer.current = "";
|
|
978
|
+
thinkingDeltaBuffer.current = "";
|
|
1355
979
|
toolLineIds.current.clear();
|
|
980
|
+
renderedToolResultIds.current.clear();
|
|
1356
981
|
clearPendingToolResultTimers();
|
|
1357
982
|
setStatus(initialStatus(runtime));
|
|
1358
983
|
setSessionsBrowser(undefined);
|
|
@@ -1372,8 +997,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1372
997
|
const inputLockedByQueue = busy && queuedInput !== undefined;
|
|
1373
998
|
const prompt = promptPrefix(busy);
|
|
1374
999
|
const currentTip = tipAt(tipIndex);
|
|
1375
|
-
const
|
|
1376
|
-
const activePlaceholder = input.length === 0 && !compactLiveLayout ? promptPlaceholder ?? currentTip.placeholder : undefined;
|
|
1000
|
+
const activePlaceholder = input.length === 0 ? promptPlaceholder ?? currentTip.placeholder : undefined;
|
|
1377
1001
|
const promptDisplayText = input;
|
|
1378
1002
|
const promptDisplayCursor = cursor;
|
|
1379
1003
|
const promptLayoutText = activePlaceholder ? ` ${activePlaceholder}` : promptDisplayText;
|
|
@@ -1387,10 +1011,10 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1387
1011
|
slashCompletionIndexRef.current = selectedSlashCompletionIndex;
|
|
1388
1012
|
}
|
|
1389
1013
|
const promptHeight = promptTextView(promptLayoutText, promptLayoutCursor, width, prompt).length + slashCompletionViewHeight(slashCompletions) + (queuedInput !== undefined ? QUEUED_INPUT_RENDER_ROWS : 0) + (pasteStatus ? 1 : 0);
|
|
1390
|
-
const firstDynamicLineIndex = lines.findIndex((line) => lineNeedsDynamicRender(line,
|
|
1014
|
+
const firstDynamicLineIndex = lines.findIndex((line) => lineNeedsDynamicRender(line, lineRenderContentWidth(line, width)));
|
|
1391
1015
|
const staticLines = firstDynamicLineIndex === -1 ? lines : lines.slice(0, firstDynamicLineIndex);
|
|
1392
1016
|
const dynamicLines = firstDynamicLineIndex === -1 ? [] : lines.slice(firstDynamicLineIndex);
|
|
1393
|
-
const subagentRows = subagentLivePanelRenderRows(agentActivities, terminalSize.rows
|
|
1017
|
+
const subagentRows = subagentLivePanelRenderRows(agentActivities, terminalSize.rows);
|
|
1394
1018
|
const nonAgentBackgroundTasks = backgroundTasks.filter((task) => task.type !== "agent");
|
|
1395
1019
|
const statusRenderRows = STATUS_BAR_RENDER_ROWS + (showForegroundExecDetachHint && foregroundExecDetachHandle ? FOREGROUND_EXEC_DETACH_HINT_RENDER_ROWS : 0) + subagentRows + backgroundTaskStatusRenderRows(subagentRows > 0 ? nonAgentBackgroundTasks.length : backgroundTasks.length);
|
|
1396
1020
|
const managementBrowserHeight = sessionsBrowser
|
|
@@ -1401,8 +1025,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1401
1025
|
? secretsBrowserViewHeight(secretsBrowser)
|
|
1402
1026
|
: 0;
|
|
1403
1027
|
const loginFormHeight = loginForm ? loginFormViewHeight(loginForm) : 0;
|
|
1404
|
-
const
|
|
1405
|
-
const visibleDynamicLines = dynamicLines.length > maxDynamicBlocks ? dynamicLines.slice(-maxDynamicBlocks) : dynamicLines;
|
|
1028
|
+
const visibleDynamicLines = dynamicLines;
|
|
1406
1029
|
const visibleDynamicMarginOverhead = visibleDynamicLines.reduce((sum, _, i) => {
|
|
1407
1030
|
const blockIndex = staticLines.length + i;
|
|
1408
1031
|
return sum + (blockIndex > 0 ? MESSAGE_BLOCK_SPACING_LINES : 0);
|
|
@@ -1754,958 +1377,66 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1754
1377
|
return;
|
|
1755
1378
|
}
|
|
1756
1379
|
});
|
|
1757
|
-
return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: visibleDynamicLines, width, liveMaxLines: liveViewportLines, lineIndexOffset: staticLines.length, onMarkdownRenderComplete: markLineRendered }), sessionsBrowser ? e(SessionsBrowser, { state: sessionsBrowser, width }) : null, skillsBrowser ? e(SkillsBrowser, { state: skillsBrowser, width }) : null, secretsBrowser ? e(SecretsBrowser, { state: secretsBrowser, width }) : null, loginForm ? e(LoginFormView, { state: loginForm, width }) : null, e(StatusBar, { status, animationTick, width }), showForegroundExecDetachHint && foregroundExecDetachHandle ? e(ForegroundExecDetachHintLine, { handle: foregroundExecDetachHandle, width }) : null, agentActivities.length > 0 ? e(SubagentLivePanel, { activities: agentActivities, width, terminalRows: terminalSize.rows, compact:
|
|
1758
|
-
}
|
|
1759
|
-
const MessageList = React.memo(function MessageList({ lines, width, liveMaxLines, lineIndexOffset = 0, onMarkdownRenderComplete }) {
|
|
1760
|
-
const contentWidth = messageContentWidth(width);
|
|
1761
|
-
const toolWidth = toolContentWidth(width);
|
|
1762
|
-
return e(Box, { flexDirection: "column" }, ...lines.map((line, index) => e(MessageBlock, {
|
|
1763
|
-
key: line.id,
|
|
1764
|
-
line,
|
|
1765
|
-
width,
|
|
1766
|
-
blockIndex: lineIndexOffset + index,
|
|
1767
|
-
contentWidth,
|
|
1768
|
-
toolWidth,
|
|
1769
|
-
liveMaxLines,
|
|
1770
|
-
onMarkdownRenderComplete,
|
|
1771
|
-
})));
|
|
1772
|
-
});
|
|
1773
|
-
function MessageBlock({ line, width, blockIndex, contentWidth, toolWidth, liveMaxLines, onMarkdownRenderComplete }) {
|
|
1774
|
-
return e(Box, { flexDirection: "column", marginTop: blockIndex > 0 ? MESSAGE_BLOCK_SPACING_LINES : 0 }, e(MessageLine, { line, width, contentWidth, toolWidth, liveMaxLines, onMarkdownRenderComplete }));
|
|
1380
|
+
return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: visibleDynamicLines, width, liveMaxLines: liveViewportLines, lineIndexOffset: staticLines.length, onMarkdownRenderComplete: markLineRendered }), sessionsBrowser ? e(SessionsBrowser, { state: sessionsBrowser, width }) : null, skillsBrowser ? e(SkillsBrowser, { state: skillsBrowser, width }) : null, secretsBrowser ? e(SecretsBrowser, { state: secretsBrowser, width }) : null, loginForm ? e(LoginFormView, { state: loginForm, width }) : null, e(StatusBar, { status, animationTick, width }), showForegroundExecDetachHint && foregroundExecDetachHandle ? e(ForegroundExecDetachHintLine, { handle: foregroundExecDetachHandle, width }) : null, agentActivities.length > 0 ? e(SubagentLivePanel, { activities: agentActivities, width, terminalRows: terminalSize.rows, compact: false, animationTick }) : null, agentActivities.length === 0 && backgroundTasks.length > 0 ? e(BackgroundTaskStatusLine, { tasks: backgroundTasks, width }) : null, agentActivities.length > 0 && nonAgentBackgroundTasks.length > 0 ? e(BackgroundTaskStatusLine, { tasks: nonAgentBackgroundTasks, width }) : null, pasteStatus ? e(PasteStatusLine, { text: pasteStatus, width }) : null, queuedInput !== undefined ? e(QueuedInputLine, { text: queuedInput, width }) : null, e(PromptLine, { text: promptDisplayText, cursor: promptDisplayCursor, busy, locked: inputLockedByQueue, placeholder: input.length === 0 && promptPlaceholder !== undefined, ghostText: activePlaceholder, width, prompt, slashCompletions, selectedSlashCompletionIndex, attachments }));
|
|
1775
1381
|
}
|
|
1776
|
-
function
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
const useRoleMarker = !titleProvidesToolMarker(line);
|
|
1784
|
-
const lineWidth = useRoleMarker ? contentWidth : toolWidth;
|
|
1785
|
-
const clipPendingMarkdown = !line.live && onMarkdownRenderComplete !== undefined && lineNeedsDynamicRender(line, lineWidth);
|
|
1786
|
-
const display = displayWindowForLine(line, lineWidth, line.live || clipPendingMarkdown ? liveMaxLines : undefined);
|
|
1787
|
-
const contentNodes = [];
|
|
1788
|
-
if (line.title)
|
|
1789
|
-
contentNodes.push(renderBlockTitle(line));
|
|
1790
|
-
if (line.bodyTitle)
|
|
1791
|
-
contentNodes.push(e(Text, { key: `body-title-${line.id}`, bold: true }, line.bodyTitle));
|
|
1792
|
-
contentNodes.push(...renderDisplayText(line, lineWidth, display.maxLines, display.skipTop, onMarkdownRenderComplete));
|
|
1793
|
-
return e(Box, { flexDirection: "row" }, useRoleMarker ? e(Text, { color: markerColorForKind(line.kind) }, messageRoleMarker(line.kind)) : null, e(Box, { flexDirection: "column", width: lineWidth }, ...contentNodes));
|
|
1794
|
-
}
|
|
1795
|
-
function displayWindowForLine(line, width, maxLines) {
|
|
1796
|
-
if (maxLines === undefined)
|
|
1797
|
-
return { skipTop: 0 };
|
|
1798
|
-
const safeMaxLines = Math.max(1, maxLines);
|
|
1799
|
-
const lineCount = estimateRenderedLineCount(line, width);
|
|
1800
|
-
return {
|
|
1801
|
-
maxLines: safeMaxLines,
|
|
1802
|
-
skipTop: Math.max(0, lineCount - safeMaxLines),
|
|
1382
|
+
async function handleSecretCommand(command, runtime) {
|
|
1383
|
+
const usage = "Usage: /secret <list|get|set|request|delete|rename|info> ...";
|
|
1384
|
+
const action = command.action ?? "list";
|
|
1385
|
+
const requireKey = () => {
|
|
1386
|
+
if (!command.key)
|
|
1387
|
+
throw new Error(usage);
|
|
1388
|
+
return command.key;
|
|
1803
1389
|
};
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
}
|
|
1812
|
-
function lineNeedsDynamicRender(line, width) {
|
|
1813
|
-
if (line.live || line.pendingReplacement)
|
|
1814
|
-
return true;
|
|
1815
|
-
if (line.previewStyle === "summary" || line.format === "ansi")
|
|
1816
|
-
return false;
|
|
1817
|
-
return line.renderedKey !== markdownRenderKey(line.text, line.kind, width);
|
|
1818
|
-
}
|
|
1819
|
-
function renderDisplayText(line, width, maxLines, skipTop = 0, onMarkdownRenderComplete) {
|
|
1820
|
-
if (line.previewStyle === "summary")
|
|
1821
|
-
return renderSummaryBlock(line, width, maxLines, skipTop);
|
|
1822
|
-
if (line.format === "ansi")
|
|
1823
|
-
return renderAnsiBlock(line.text, width, maxLines, skipTop);
|
|
1824
|
-
const shouldAsyncRenderMarkdown = !line.live && onMarkdownRenderComplete !== undefined;
|
|
1825
|
-
return [e(MarkdownText, {
|
|
1826
|
-
key: `markdown-${line.id}`,
|
|
1827
|
-
text: line.text,
|
|
1828
|
-
kind: line.kind,
|
|
1829
|
-
width,
|
|
1830
|
-
maxLines,
|
|
1831
|
-
skipLines: skipTop,
|
|
1832
|
-
asyncRender: shouldAsyncRenderMarkdown,
|
|
1833
|
-
onRenderComplete: shouldAsyncRenderMarkdown ? (renderKey) => onMarkdownRenderComplete(line.id, renderKey) : undefined,
|
|
1834
|
-
})];
|
|
1835
|
-
}
|
|
1836
|
-
function renderSummaryLines(line, width) {
|
|
1837
|
-
const content = line.text;
|
|
1838
|
-
const detailWidth = Math.max(10, width - SUMMARY_BLOCK.detailIndent.length);
|
|
1839
|
-
const title = summaryTitle(line);
|
|
1840
|
-
const rawLines = content.replace(/\r\n/g, "\n").split("\n");
|
|
1841
|
-
const wrapped = rawLines.flatMap((rawLine, index) => {
|
|
1842
|
-
const lineWidth = index === 0 && !title ? width : detailWidth;
|
|
1843
|
-
return wrapAnsi(rawLine, Math.max(10, lineWidth), { hard: true, trim: false }).split("\n");
|
|
1844
|
-
});
|
|
1845
|
-
const maxLines = line.summaryMaxLines ?? SUMMARY_BLOCK.maxLines;
|
|
1846
|
-
const preview = [title, ...wrapped].filter((value) => stripAnsi(value).length > 0).slice(0, maxLines);
|
|
1847
|
-
if (wrapped.length + (title ? 1 : 0) > maxLines && preview.length > 0) {
|
|
1848
|
-
preview[preview.length - 1] = truncateAnsi(preview[preview.length - 1], Math.max(1, detailWidth - 1)) + "…";
|
|
1849
|
-
}
|
|
1850
|
-
return preview.length ? preview : [""];
|
|
1851
|
-
}
|
|
1852
|
-
function summaryTitle(line) {
|
|
1853
|
-
if (summaryUsesRoleMarker(line))
|
|
1854
|
-
return "";
|
|
1855
|
-
const title = line.title ?? titleForKind(line.kind);
|
|
1856
|
-
if (!line.titleStatus)
|
|
1857
|
-
return title;
|
|
1858
|
-
return `${title} ${titleStatusMarker(line.titleStatus)}`;
|
|
1859
|
-
}
|
|
1860
|
-
function summaryUsesRoleMarker(line) {
|
|
1861
|
-
return line.previewStyle === "summary" && (line.kind === "system" || line.kind === "meta");
|
|
1862
|
-
}
|
|
1863
|
-
function titleProvidesToolMarker(line) {
|
|
1864
|
-
return line.kind === "tool" && !!line.title && (line.title.startsWith("◇ ") || line.title.startsWith("◆ "));
|
|
1865
|
-
}
|
|
1866
|
-
function titleStatusMarker(status) {
|
|
1867
|
-
return status === "success" ? "✓" : "✗";
|
|
1868
|
-
}
|
|
1869
|
-
function titleStatusColor(status) {
|
|
1870
|
-
return status === "success" ? "green" : "red";
|
|
1871
|
-
}
|
|
1872
|
-
function renderBlockTitle(line) {
|
|
1873
|
-
const title = line.title ?? titleForKind(line.kind);
|
|
1874
|
-
if (!line.titleStatus)
|
|
1875
|
-
return e(Text, { key: `title-${line.id}`, color: colorForKind(line.kind), bold: true }, title);
|
|
1876
|
-
return e(Text, { key: `title-${line.id}`, color: colorForKind(line.kind), bold: true }, `${title} `, e(Text, { color: titleStatusColor(line.titleStatus), bold: true }, titleStatusMarker(line.titleStatus)));
|
|
1877
|
-
}
|
|
1878
|
-
function renderSummaryBlock(line, width, maxLines, skipTop = 0) {
|
|
1879
|
-
const allPreviewLines = renderSummaryLines(line, width);
|
|
1880
|
-
const preview = clipStrings(allPreviewLines, maxLines, skipTop);
|
|
1881
|
-
return preview.map((previewLine, index) => {
|
|
1882
|
-
const sourceIndex = skipTop + index;
|
|
1883
|
-
const detail = sourceIndex > 0;
|
|
1884
|
-
const text = detail ? `${SUMMARY_BLOCK.detailIndent}${previewLine}` : previewLine;
|
|
1885
|
-
if (!detail && line.titleStatus) {
|
|
1886
|
-
const marker = titleStatusMarker(line.titleStatus);
|
|
1887
|
-
const markerSuffix = ` ${marker}`;
|
|
1888
|
-
const titleText = text.endsWith(markerSuffix) ? text.slice(0, -marker.length) : `${text} `;
|
|
1889
|
-
return e(Text, {
|
|
1890
|
-
key: `summary-${line.id}-${index}`,
|
|
1891
|
-
color: colorForKind(line.kind),
|
|
1892
|
-
bold: true,
|
|
1893
|
-
}, titleText, e(Text, { color: titleStatusColor(line.titleStatus), bold: true }, marker));
|
|
1894
|
-
}
|
|
1895
|
-
if (line.format === "ansi") {
|
|
1896
|
-
const baseStyle = detail
|
|
1897
|
-
? { color: "gray", dimColor: true }
|
|
1898
|
-
: { color: colorForKind(line.kind), bold: true };
|
|
1899
|
-
return e(Text, { key: `summary-${line.id}-${index}` }, ...renderAnsiInline(text, baseStyle));
|
|
1900
|
-
}
|
|
1901
|
-
return e(Text, {
|
|
1902
|
-
key: `summary-${line.id}-${index}`,
|
|
1903
|
-
color: detail ? "gray" : colorForKind(line.kind),
|
|
1904
|
-
dimColor: detail,
|
|
1905
|
-
bold: !detail,
|
|
1906
|
-
}, text);
|
|
1907
|
-
});
|
|
1908
|
-
}
|
|
1909
|
-
function renderAnsiBlock(text, width, maxLines, skipTop = 0) {
|
|
1910
|
-
const lines = clipStrings(wrapAnsi(text, Math.max(10, width), { hard: true, trim: false }).split("\n"), maxLines, skipTop);
|
|
1911
|
-
return lines.map((line, index) => e(Text, { key: `ansi-${index}` }, ...renderAnsiInline(line)));
|
|
1912
|
-
}
|
|
1913
|
-
function clipStrings(lines, maxLines, skipTop = 0) {
|
|
1914
|
-
const start = Math.max(0, skipTop);
|
|
1915
|
-
if (maxLines === undefined)
|
|
1916
|
-
return lines.slice(start);
|
|
1917
|
-
if (maxLines <= 0)
|
|
1918
|
-
return [];
|
|
1919
|
-
return lines.slice(start, start + maxLines);
|
|
1920
|
-
}
|
|
1921
|
-
function renderAnsiInline(text, baseStyle = {}) {
|
|
1922
|
-
const nodes = [];
|
|
1923
|
-
const pattern = /\x1b\[([0-9;]*)m/g;
|
|
1924
|
-
let lastIndex = 0;
|
|
1925
|
-
let style = { ...baseStyle };
|
|
1926
|
-
let match;
|
|
1927
|
-
while ((match = pattern.exec(text)) !== null) {
|
|
1928
|
-
if (match.index > lastIndex) {
|
|
1929
|
-
nodes.push(e(Text, { key: `ansi-${nodes.length}`, ...style }, text.slice(lastIndex, match.index)));
|
|
1930
|
-
}
|
|
1931
|
-
style = nextAnsiStyle(style, match[1], baseStyle);
|
|
1932
|
-
lastIndex = match.index + match[0].length;
|
|
1933
|
-
}
|
|
1934
|
-
if (lastIndex < text.length)
|
|
1935
|
-
nodes.push(e(Text, { key: `ansi-${nodes.length}`, ...style }, text.slice(lastIndex)));
|
|
1936
|
-
return nodes.length ? nodes : [e(Text, { key: "ansi-empty", ...baseStyle }, "")];
|
|
1937
|
-
}
|
|
1938
|
-
function nextAnsiStyle(current, rawCodes, baseStyle = {}) {
|
|
1939
|
-
const codes = rawCodes ? rawCodes.split(";").filter(Boolean).map((code) => Number(code)) : [0];
|
|
1940
|
-
let next = { ...current };
|
|
1941
|
-
for (let index = 0; index < codes.length; index += 1) {
|
|
1942
|
-
const code = codes[index] ?? 0;
|
|
1943
|
-
if (code === 0)
|
|
1944
|
-
next = { ...baseStyle };
|
|
1945
|
-
else if (code === 1)
|
|
1946
|
-
next.bold = true;
|
|
1947
|
-
else if (code === 2)
|
|
1948
|
-
next.dimColor = true;
|
|
1949
|
-
else if (code === 3)
|
|
1950
|
-
next.italic = true;
|
|
1951
|
-
else if (code === 4)
|
|
1952
|
-
next.underline = true;
|
|
1953
|
-
else if (code === 22) {
|
|
1954
|
-
next.bold = undefined;
|
|
1955
|
-
next.dimColor = undefined;
|
|
1956
|
-
}
|
|
1957
|
-
else if (code === 23)
|
|
1958
|
-
next.italic = undefined;
|
|
1959
|
-
else if (code === 24)
|
|
1960
|
-
next.underline = undefined;
|
|
1961
|
-
else if (code === 39)
|
|
1962
|
-
next.color = undefined;
|
|
1963
|
-
else if (code === 49)
|
|
1964
|
-
next.backgroundColor = undefined;
|
|
1965
|
-
else if (code >= 30 && code <= 37)
|
|
1966
|
-
next.color = ANSI_COLORS[code - 30];
|
|
1967
|
-
else if (code >= 90 && code <= 97)
|
|
1968
|
-
next.color = ANSI_BRIGHT_COLORS[code - 90];
|
|
1969
|
-
else if (code >= 40 && code <= 47)
|
|
1970
|
-
next.backgroundColor = ANSI_COLORS[code - 40];
|
|
1971
|
-
else if (code >= 100 && code <= 107)
|
|
1972
|
-
next.backgroundColor = ANSI_BRIGHT_COLORS[code - 100];
|
|
1973
|
-
else if (code === 38 || code === 48) {
|
|
1974
|
-
const isForeground = code === 38;
|
|
1975
|
-
const mode = codes[index + 1];
|
|
1976
|
-
if (mode === 5) {
|
|
1977
|
-
const color = xtermColor(codes[index + 2]);
|
|
1978
|
-
if (isForeground)
|
|
1979
|
-
next.color = color;
|
|
1980
|
-
else
|
|
1981
|
-
next.backgroundColor = color;
|
|
1982
|
-
index += 2;
|
|
1983
|
-
}
|
|
1984
|
-
else if (mode === 2) {
|
|
1985
|
-
const color = rgbColor(codes[index + 2], codes[index + 3], codes[index + 4]);
|
|
1986
|
-
if (isForeground)
|
|
1987
|
-
next.color = color;
|
|
1988
|
-
else
|
|
1989
|
-
next.backgroundColor = color;
|
|
1990
|
-
index += 4;
|
|
1390
|
+
if (action === "list") {
|
|
1391
|
+
const entries = await runtime.secretStore.list();
|
|
1392
|
+
if (entries.length === 0)
|
|
1393
|
+
return systemLine("No secrets stored.");
|
|
1394
|
+
const lines = await Promise.all(entries.map(async (entry) => {
|
|
1395
|
+
if (command.show) {
|
|
1396
|
+
const value = entry.status === "set" ? await runtime.secretStore.getPlaintext(entry.key) : "";
|
|
1397
|
+
return `${entry.key} = ${value}`;
|
|
1991
1398
|
}
|
|
1992
|
-
|
|
1399
|
+
const reason = entry.requestReason ? ` reason=${JSON.stringify(entry.requestReason)}` : "";
|
|
1400
|
+
return `${entry.key}\t${entry.status}\tlength=${entry.length}${reason}`;
|
|
1401
|
+
}));
|
|
1402
|
+
return systemLine(lines.join("\n"), EXPANDED_SUMMARY_MAX_LINES);
|
|
1993
1403
|
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
const
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
return
|
|
2001
|
-
if (value < 8)
|
|
2002
|
-
return ANSI_COLORS[value];
|
|
2003
|
-
if (value < 16)
|
|
2004
|
-
return ANSI_BRIGHT_COLORS[value - 8];
|
|
2005
|
-
return undefined;
|
|
2006
|
-
}
|
|
2007
|
-
function rgbColor(red, green, blue) {
|
|
2008
|
-
if ([red, green, blue].some((value) => value === undefined || Number.isNaN(value)))
|
|
2009
|
-
return undefined;
|
|
2010
|
-
return `#${[red, green, blue].map((value) => Math.max(0, Math.min(255, value ?? 0)).toString(16).padStart(2, "0")).join("")}`;
|
|
2011
|
-
}
|
|
2012
|
-
function hasAnsi(text) {
|
|
2013
|
-
return /\x1b\[[0-9;]*m/.test(text);
|
|
2014
|
-
}
|
|
2015
|
-
function useAnimatedNumber(target) {
|
|
2016
|
-
const [display, setDisplay] = useState(target);
|
|
2017
|
-
const displayRef = useRef(target);
|
|
2018
|
-
useEffect(() => {
|
|
2019
|
-
if (target === undefined) {
|
|
2020
|
-
displayRef.current = undefined;
|
|
2021
|
-
setDisplay(undefined);
|
|
2022
|
-
return undefined;
|
|
2023
|
-
}
|
|
2024
|
-
const current = displayRef.current;
|
|
2025
|
-
if (current === undefined || current === target) {
|
|
2026
|
-
displayRef.current = target;
|
|
2027
|
-
setDisplay(target);
|
|
2028
|
-
return undefined;
|
|
2029
|
-
}
|
|
2030
|
-
const from = current;
|
|
2031
|
-
const delta = target - from;
|
|
2032
|
-
const startedAt = Date.now();
|
|
2033
|
-
const durationMs = animatedNumberDurationMs(Math.abs(delta));
|
|
2034
|
-
const interval = setInterval(() => {
|
|
2035
|
-
const progress = Math.min(1, (Date.now() - startedAt) / durationMs);
|
|
2036
|
-
const eased = easeOutCubic(progress);
|
|
2037
|
-
const next = from + delta * eased;
|
|
2038
|
-
displayRef.current = progress >= 1 ? target : next;
|
|
2039
|
-
setDisplay(displayRef.current);
|
|
2040
|
-
if (progress >= 1)
|
|
2041
|
-
clearInterval(interval);
|
|
2042
|
-
}, ANIMATED_NUMBER_INTERVAL_MS);
|
|
2043
|
-
return () => clearInterval(interval);
|
|
2044
|
-
}, [target]);
|
|
2045
|
-
return display;
|
|
2046
|
-
}
|
|
2047
|
-
function useMinimumDisplayValue(target, minDurationMs) {
|
|
2048
|
-
const [display, setDisplay] = useState(target);
|
|
2049
|
-
const displayRef = useRef(target);
|
|
2050
|
-
const displayedAtRef = useRef(Date.now());
|
|
2051
|
-
const pendingRef = useRef(undefined);
|
|
2052
|
-
const timerRef = useRef(undefined);
|
|
2053
|
-
useEffect(() => {
|
|
2054
|
-
if (timerRef.current) {
|
|
2055
|
-
clearTimeout(timerRef.current);
|
|
2056
|
-
timerRef.current = undefined;
|
|
2057
|
-
}
|
|
2058
|
-
if (Object.is(target, displayRef.current)) {
|
|
2059
|
-
pendingRef.current = undefined;
|
|
2060
|
-
return undefined;
|
|
2061
|
-
}
|
|
2062
|
-
const applyPending = () => {
|
|
2063
|
-
const next = pendingRef.current;
|
|
2064
|
-
if (next === undefined || Object.is(next, displayRef.current)) {
|
|
2065
|
-
pendingRef.current = undefined;
|
|
2066
|
-
return;
|
|
2067
|
-
}
|
|
2068
|
-
displayRef.current = next;
|
|
2069
|
-
displayedAtRef.current = Date.now();
|
|
2070
|
-
pendingRef.current = undefined;
|
|
2071
|
-
timerRef.current = undefined;
|
|
2072
|
-
setDisplay(next);
|
|
2073
|
-
};
|
|
2074
|
-
pendingRef.current = target;
|
|
2075
|
-
const elapsedMs = Date.now() - displayedAtRef.current;
|
|
2076
|
-
const remainingMs = minDurationMs - elapsedMs;
|
|
2077
|
-
if (remainingMs <= 0) {
|
|
2078
|
-
applyPending();
|
|
2079
|
-
return undefined;
|
|
2080
|
-
}
|
|
2081
|
-
timerRef.current = setTimeout(applyPending, remainingMs);
|
|
2082
|
-
return () => {
|
|
2083
|
-
if (timerRef.current) {
|
|
2084
|
-
clearTimeout(timerRef.current);
|
|
2085
|
-
timerRef.current = undefined;
|
|
2086
|
-
}
|
|
2087
|
-
};
|
|
2088
|
-
}, [target, minDurationMs]);
|
|
2089
|
-
return display;
|
|
2090
|
-
}
|
|
2091
|
-
function animatedNumberDurationMs(delta) {
|
|
2092
|
-
if (!Number.isFinite(delta) || delta <= 0)
|
|
2093
|
-
return ANIMATED_NUMBER_MIN_DURATION_MS;
|
|
2094
|
-
const scaled = ANIMATED_NUMBER_MIN_DURATION_MS + Math.log10(delta + 1) * ANIMATED_NUMBER_DURATION_SCALE_MS;
|
|
2095
|
-
return Math.min(ANIMATED_NUMBER_MAX_DURATION_MS, Math.max(ANIMATED_NUMBER_MIN_DURATION_MS, scaled));
|
|
2096
|
-
}
|
|
2097
|
-
function easeOutCubic(progress) {
|
|
2098
|
-
const clamped = Math.max(0, Math.min(1, progress));
|
|
2099
|
-
return 1 - Math.pow(1 - clamped, 3);
|
|
2100
|
-
}
|
|
2101
|
-
function StatusBar({ status, animationTick, width: terminalWidth }) {
|
|
2102
|
-
const width = statusBarWidth(terminalWidth);
|
|
2103
|
-
const inputTokens = useAnimatedNumber(statusInputTokens(status));
|
|
2104
|
-
const outputTokens = useAnimatedNumber(statusOutputTokens(status));
|
|
2105
|
-
const displayPhase = useMinimumDisplayValue(status.phase, STATUS_PHASE_MIN_DISPLAY_MS);
|
|
2106
|
-
const segments = fitStatusSegments(renderCompactStatusSegments(status, animationTick, width, inputTokens, outputTokens, displayPhase), width);
|
|
2107
|
-
return e(Box, { width, height: 1, overflow: "hidden" }, ...segments.map((segment, index) => e(Text, { key: index, color: segment.color ?? "gray", bold: segment.bold ?? false }, segment.text)));
|
|
2108
|
-
}
|
|
2109
|
-
function backgroundTaskStatusRenderRows(taskCount) {
|
|
2110
|
-
if (taskCount <= 0)
|
|
2111
|
-
return 0;
|
|
2112
|
-
return 1 + Math.min(taskCount, 2);
|
|
2113
|
-
}
|
|
2114
|
-
function ForegroundExecDetachHintLine({ handle, width: terminalWidth }) {
|
|
2115
|
-
const width = statusBarWidth(terminalWidth);
|
|
2116
|
-
const label = handle.description?.trim() || handle.command;
|
|
2117
|
-
const text = `↳ exec still running · Ctrl+B to detach · ${truncateMiddle(label, Math.max(12, width - 38))}`;
|
|
2118
|
-
return e(Text, { color: "yellow" }, fitToWidth(text, width));
|
|
2119
|
-
}
|
|
2120
|
-
function SubagentLivePanel({ activities, width: terminalWidth, terminalRows, compact, animationTick }) {
|
|
2121
|
-
const width = statusBarWidth(terminalWidth);
|
|
2122
|
-
const rows = subagentLivePanelRenderRows(activities, terminalRows, compact);
|
|
2123
|
-
if (rows <= 0)
|
|
2124
|
-
return null;
|
|
2125
|
-
const sorted = sortAgentActivitiesForPanel(activities);
|
|
2126
|
-
const selected = sorted[0];
|
|
2127
|
-
if (!selected)
|
|
2128
|
-
return null;
|
|
2129
|
-
const activeCount = activities.filter((activity) => activity.status === "running" || activity.status === "pending").length;
|
|
2130
|
-
const header = `◇ subagents: ${activeCount} active${activities.length > activeCount ? ` · ${activities.length - activeCount} recent` : ""}`;
|
|
2131
|
-
if (rows <= 1) {
|
|
2132
|
-
return e(Text, { color: "yellow" }, fitToWidth(`${header} · ${compactAgentSummary(selected, width - header.length - 3)}`, width));
|
|
1404
|
+
if (action === "get") {
|
|
1405
|
+
const key = requireKey();
|
|
1406
|
+
const info = await runtime.secretStore.info(key);
|
|
1407
|
+
if (!info)
|
|
1408
|
+
return systemLine(`Secret "${key}" does not exist.`);
|
|
1409
|
+
const value = await runtime.secretStore.getPlaintext(key);
|
|
1410
|
+
return systemLine(info.status === "empty" ? `Secret "${key}" is empty.` : value, EXPANDED_SUMMARY_MAX_LINES);
|
|
2133
1411
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
key
|
|
2137
|
-
|
|
2138
|
-
}, fitToWidth(line.text, width))));
|
|
2139
|
-
}
|
|
2140
|
-
const SUBAGENT_DETAIL_ROWS = 3;
|
|
2141
|
-
function subagentLivePanelRenderRows(activities, terminalRows, compact = false) {
|
|
2142
|
-
if (activities.length === 0)
|
|
2143
|
-
return 0;
|
|
2144
|
-
if (compact || terminalRows < 22 || activities.length > 1)
|
|
2145
|
-
return 1;
|
|
2146
|
-
return 1 + SUBAGENT_DETAIL_ROWS;
|
|
2147
|
-
}
|
|
2148
|
-
function sortAgentActivitiesForPanel(activities) {
|
|
2149
|
-
const rank = (status) => {
|
|
2150
|
-
if (status === "running")
|
|
2151
|
-
return 0;
|
|
2152
|
-
if (status === "pending")
|
|
2153
|
-
return 1;
|
|
2154
|
-
if (status === "failed" || status === "killed")
|
|
2155
|
-
return 2;
|
|
2156
|
-
return 3;
|
|
2157
|
-
};
|
|
2158
|
-
return [...activities].sort((left, right) => rank(left.status) - rank(right.status) || right.updatedAt.localeCompare(left.updatedAt));
|
|
2159
|
-
}
|
|
2160
|
-
function buildSubagentDetailLines(selected, sorted, animationTick) {
|
|
2161
|
-
const spinner = selected.status === "running" ? spinnerFrame(animationTick) : statusGlyph(selected.status);
|
|
2162
|
-
const elapsed = formatElapsed(Date.now() - new Date(selected.startedAt).getTime());
|
|
2163
|
-
const headerLine = `${spinner} ${selected.description || selected.agentId} · ${elapsed}`;
|
|
2164
|
-
const currentLine = selected.currentTool
|
|
2165
|
-
? `→ ${selected.currentTool.name}${selected.currentTool.inputPreview ? ` · ${selected.currentTool.inputPreview}` : ""}`
|
|
2166
|
-
: selected.error
|
|
2167
|
-
? `✖ ${selected.error}`
|
|
2168
|
-
: selected.resultPreview
|
|
2169
|
-
? `✓ ${selected.resultPreview}`
|
|
2170
|
-
: selected.lastText
|
|
2171
|
-
? `• ${selected.lastText}`
|
|
2172
|
-
: `• ${selected.prompt}`;
|
|
2173
|
-
const recent = selected.timeline.slice(-2).map((entry) => `${timelinePrefix(entry)} ${formatTimelineEntry(entry, 240)}`);
|
|
2174
|
-
const otherRunning = sorted
|
|
2175
|
-
.filter((activity) => activity.agentId !== selected.agentId && (activity.status === "running" || activity.status === "pending"))
|
|
2176
|
-
.slice(0, 2)
|
|
2177
|
-
.map((activity) => compactAgentSummary(activity, 180));
|
|
2178
|
-
const tail = [...recent, ...otherRunning.map((summary) => `· ${summary}`)].find((line) => line.trim()) ?? `tools:${selected.totalToolUseCount}`;
|
|
2179
|
-
return [
|
|
2180
|
-
{ text: headerLine, color: statusColor(selected.status) },
|
|
2181
|
-
{ text: currentLine, color: selected.error ? "red" : selected.currentTool ? "#d4b04c" : "yellow" },
|
|
2182
|
-
{ text: tail, color: "gray" },
|
|
2183
|
-
];
|
|
2184
|
-
}
|
|
2185
|
-
function compactAgentSummary(activity, maxLength) {
|
|
2186
|
-
const current = activity.currentTool
|
|
2187
|
-
? `${activity.currentTool.name}${activity.currentTool.inputPreview ? ` ${activity.currentTool.inputPreview}` : ""}`
|
|
2188
|
-
: activity.lastText ?? activity.resultPreview ?? activity.error ?? activity.prompt;
|
|
2189
|
-
const elapsed = formatElapsed(Date.now() - new Date(activity.startedAt).getTime());
|
|
2190
|
-
return truncateMiddle(`${activity.description || activity.agentId} · ${elapsed} · tools:${activity.totalToolUseCount} · ${current.replace(/\s+/g, " ")}`, Math.max(8, maxLength));
|
|
2191
|
-
}
|
|
2192
|
-
function formatTimelineEntry(entry, maxLength) {
|
|
2193
|
-
const detail = entry.detail ? ` · ${entry.detail.replace(/\s+/g, " ")}` : "";
|
|
2194
|
-
return truncateMiddle(`${entry.title}${detail}`, Math.max(8, maxLength));
|
|
2195
|
-
}
|
|
2196
|
-
function timelinePrefix(entry) {
|
|
2197
|
-
if (entry.kind === "tool_start")
|
|
2198
|
-
return "→";
|
|
2199
|
-
if (entry.kind === "tool_result")
|
|
2200
|
-
return entry.status === "failed" ? "✖" : "←";
|
|
2201
|
-
if (entry.kind === "thinking")
|
|
2202
|
-
return "◆";
|
|
2203
|
-
if (entry.kind === "error")
|
|
2204
|
-
return "✖";
|
|
2205
|
-
if (entry.kind === "status")
|
|
2206
|
-
return "•";
|
|
2207
|
-
return "assistant:";
|
|
2208
|
-
}
|
|
2209
|
-
function timelineColor(entry) {
|
|
2210
|
-
if (entry.status === "failed" || entry.kind === "error")
|
|
2211
|
-
return "red";
|
|
2212
|
-
if (entry.kind === "tool_start" || entry.kind === "tool_result")
|
|
2213
|
-
return "#d4b04c";
|
|
2214
|
-
if (entry.kind === "thinking")
|
|
2215
|
-
return THINKING_COLOR;
|
|
2216
|
-
if (entry.kind === "status")
|
|
2217
|
-
return "gray";
|
|
2218
|
-
return "green";
|
|
2219
|
-
}
|
|
2220
|
-
function statusGlyph(status) {
|
|
2221
|
-
if (status === "completed")
|
|
2222
|
-
return "✓";
|
|
2223
|
-
if (status === "failed")
|
|
2224
|
-
return "✖";
|
|
2225
|
-
if (status === "killed")
|
|
2226
|
-
return "■";
|
|
2227
|
-
if (status === "pending")
|
|
2228
|
-
return "…";
|
|
2229
|
-
return "●";
|
|
2230
|
-
}
|
|
2231
|
-
function statusColor(status) {
|
|
2232
|
-
if (status === "completed")
|
|
2233
|
-
return "green";
|
|
2234
|
-
if (status === "failed" || status === "killed")
|
|
2235
|
-
return "red";
|
|
2236
|
-
if (status === "pending")
|
|
2237
|
-
return "gray";
|
|
2238
|
-
return "yellow";
|
|
2239
|
-
}
|
|
2240
|
-
function spinnerFrame(tick) {
|
|
2241
|
-
return ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"][tick % 10] ?? "●";
|
|
2242
|
-
}
|
|
2243
|
-
function BackgroundTaskStatusLine({ tasks, width: terminalWidth }) {
|
|
2244
|
-
const width = statusBarWidth(terminalWidth);
|
|
2245
|
-
const summary = `◇ background tools: ${tasks.length} task${tasks.length === 1 ? "" : "s"}`;
|
|
2246
|
-
const detailTasks = tasks.slice(0, 2);
|
|
2247
|
-
return e(Box, { flexDirection: "column", width, overflow: "hidden" }, e(Text, { color: "yellow" }, fitToWidth(summary, width)), ...detailTasks.map((task, index) => e(Text, { key: `bg-task-${task.taskId}-${index}`, color: "yellow" }, fitToWidth(` ${task.type}:${truncateMiddle(task.description || task.agentId || task.taskId, Math.max(12, width - 30))} · ${task.status} · ${formatElapsed(Date.now() - new Date(task.createdAt).getTime())}`, width))));
|
|
2248
|
-
}
|
|
2249
|
-
function formatElapsed(ms) {
|
|
2250
|
-
const seconds = Math.max(0, Math.floor(ms / 1000));
|
|
2251
|
-
if (seconds < 60)
|
|
2252
|
-
return `${seconds}s`;
|
|
2253
|
-
const minutes = Math.floor(seconds / 60);
|
|
2254
|
-
const remainder = seconds % 60;
|
|
2255
|
-
if (minutes < 60)
|
|
2256
|
-
return `${minutes}m${remainder.toString().padStart(2, "0")}s`;
|
|
2257
|
-
const hours = Math.floor(minutes / 60);
|
|
2258
|
-
return `${hours}h${(minutes % 60).toString().padStart(2, "0")}m`;
|
|
2259
|
-
}
|
|
2260
|
-
function renderCompactStatusSegments(status, animationTick, width, inputTokens, outputTokens, displayPhase = status.phase) {
|
|
2261
|
-
const phase = displayPhase;
|
|
2262
|
-
const now = Date.now();
|
|
2263
|
-
const phaseText = phaseLabelForStatus(phase);
|
|
2264
|
-
const inputValue = compactNumber(inputTokens);
|
|
2265
|
-
const outputValue = compactNumber(outputTokens);
|
|
2266
|
-
const context = renderContextParts(status.metrics);
|
|
2267
|
-
const fixedText = [
|
|
2268
|
-
phaseText,
|
|
2269
|
-
context.percent,
|
|
2270
|
-
`↑ ${inputValue}`,
|
|
2271
|
-
`↓ ${outputValue}`,
|
|
2272
|
-
].join(STATUS_SEPARATOR);
|
|
2273
|
-
const modelBudget = Math.max(4, width - fixedText.length - STATUS_SEPARATOR.length);
|
|
2274
|
-
const model = truncateMiddle(status.metrics?.model ?? "model?", Math.min(width >= 120 ? 26 : width >= 90 ? 20 : 14, modelBudget));
|
|
2275
|
-
const retryPending = retryCooldownActive(status, now);
|
|
2276
|
-
const outputPulseColor = tokenArrowColor(status.outputTokenUpdatedAt, now, "cyan");
|
|
2277
|
-
const outputPending = modelOutputPending(status, now);
|
|
2278
|
-
const tokenInputColor = retryPending ? "red" : tokenArrowColor(status.inputTokenUpdatedAt, now, "green");
|
|
2279
|
-
const tokenOutputColor = outputPulseColor;
|
|
2280
|
-
const outputLabelColor = outputPending && !slowBlinkVisible(animationTick) ? "gray" : tokenOutputColor;
|
|
2281
|
-
const segments = [
|
|
2282
|
-
...renderPhaseStatusSegments(phaseText, phase, animationTick),
|
|
2283
|
-
statusDividerSegment(),
|
|
2284
|
-
{ text: model },
|
|
2285
|
-
statusDividerSegment(),
|
|
2286
|
-
{ text: context.percent, color: contextColor(status.metrics) },
|
|
2287
|
-
statusDividerSegment(),
|
|
2288
|
-
statusLabelSegment("↑", tokenInputColor),
|
|
2289
|
-
{ text: ` ${inputValue}` },
|
|
2290
|
-
statusDividerSegment(),
|
|
2291
|
-
statusLabelSegment("↓", outputLabelColor),
|
|
2292
|
-
{ text: ` ${outputValue}` },
|
|
2293
|
-
];
|
|
2294
|
-
return segments;
|
|
2295
|
-
}
|
|
2296
|
-
function fitStatusSegments(segments, width) {
|
|
2297
|
-
const fitted = [];
|
|
2298
|
-
let remaining = width;
|
|
2299
|
-
for (const segment of segments) {
|
|
2300
|
-
if (remaining <= 0)
|
|
2301
|
-
break;
|
|
2302
|
-
const textWidth = stripAnsi(segment.text).length;
|
|
2303
|
-
if (textWidth <= remaining) {
|
|
2304
|
-
fitted.push(segment);
|
|
2305
|
-
remaining -= textWidth;
|
|
2306
|
-
continue;
|
|
2307
|
-
}
|
|
2308
|
-
const text = fitToWidth(segment.text, remaining);
|
|
2309
|
-
if (text.length > 0)
|
|
2310
|
-
fitted.push({ ...segment, text });
|
|
2311
|
-
remaining = 0;
|
|
1412
|
+
if (action === "set") {
|
|
1413
|
+
const key = requireKey();
|
|
1414
|
+
const meta = await runtime.secretStore.setPlaintext(key, command.value ?? "");
|
|
1415
|
+
return systemLine(`Secret "${meta.key}" saved, status=${meta.status}, length=${meta.length}.`);
|
|
2312
1416
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
const
|
|
2316
|
-
|
|
2317
|
-
const MODEL_REASONING_CONTROL_CHOICES = ["default", "off"];
|
|
2318
|
-
const SKILL_COMMAND_ACTIONS = [
|
|
2319
|
-
{ name: "list", description: "Open the skill management browser", aliases: ["ls"] },
|
|
2320
|
-
{ name: "import", description: "Import by linking a skill directory" },
|
|
2321
|
-
{ name: "delete", description: "Delete a workspace skill link/directory", aliases: ["remove", "rm"] },
|
|
2322
|
-
];
|
|
2323
|
-
const SECRET_COMMAND_ACTIONS = [
|
|
2324
|
-
{ name: "list", description: "List secret keys/status/length; add --show to print values" },
|
|
2325
|
-
{ name: "get", description: "Print one secret value in the REPL" },
|
|
2326
|
-
{ name: "set", description: "Set a plaintext secret value" },
|
|
2327
|
-
{ name: "request", description: "Create an empty placeholder secret", aliases: ["empty"] },
|
|
2328
|
-
{ name: "info", description: "Show one secret's metadata" },
|
|
2329
|
-
{ name: "rename", description: "Rename a secret key", aliases: ["mv"] },
|
|
2330
|
-
{ name: "delete", description: "Delete a secret", aliases: ["remove", "rm"] },
|
|
2331
|
-
];
|
|
2332
|
-
function slashCommandCompletions(text, cursor, skills = [], secrets = []) {
|
|
2333
|
-
const safeCursor = Math.max(0, Math.min(cursor, text.length));
|
|
2334
|
-
const prefix = text.slice(0, safeCursor);
|
|
2335
|
-
if (!prefix.startsWith("/") || /\r|\n/.test(prefix))
|
|
2336
|
-
return [];
|
|
2337
|
-
if (/^\s/.test(prefix) || text.slice(0, 1) !== "/")
|
|
2338
|
-
return [];
|
|
2339
|
-
const suffix = text.slice(safeCursor);
|
|
2340
|
-
if (/\S/.test(suffix))
|
|
2341
|
-
return [];
|
|
2342
|
-
if (prefix.startsWith("/model") && (prefix.length === "/model".length || prefix["/model".length] === " ")) {
|
|
2343
|
-
return modelCommandCompletions(prefix);
|
|
1417
|
+
if (action === "request" || action === "empty") {
|
|
1418
|
+
const key = requireKey();
|
|
1419
|
+
const meta = await runtime.secretStore.requestEmpty(key, { reason: command.reason, requestedBy: "user" });
|
|
1420
|
+
return systemLine(`Secret "${meta.key}" is ${meta.status}. Fill it with: /secret set ${meta.key} <value>`);
|
|
2344
1421
|
}
|
|
2345
|
-
if (
|
|
2346
|
-
|
|
1422
|
+
if (action === "delete") {
|
|
1423
|
+
const key = requireKey();
|
|
1424
|
+
const deleted = await runtime.secretStore.delete(key);
|
|
1425
|
+
return systemLine(deleted ? `Secret "${key}" deleted.` : `Secret "${key}" did not exist.`);
|
|
2347
1426
|
}
|
|
2348
|
-
if (
|
|
2349
|
-
|
|
1427
|
+
if (action === "rename") {
|
|
1428
|
+
const key = requireKey();
|
|
1429
|
+
if (!command.newKey)
|
|
1430
|
+
throw new Error("Usage: /secret rename <old-key> <new-key>");
|
|
1431
|
+
const meta = await runtime.secretStore.rename(key, command.newKey);
|
|
1432
|
+
return systemLine(`Secret renamed to "${meta.key}".`);
|
|
2350
1433
|
}
|
|
2351
|
-
if (
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
.flatMap((command) => [command.name, ...(command.aliases ?? [])].map((name) => ({ value: name, insertText: name, description: command.description, arguments: command.arguments, kind: "command" })))
|
|
2356
|
-
.filter((command) => command.value.toLowerCase().startsWith(normalizedPrefix));
|
|
2357
|
-
}
|
|
2358
|
-
function skillCommandCompletions(prefix, skills) {
|
|
2359
|
-
const hasTrailingSpace = /\s$/.test(prefix);
|
|
2360
|
-
const tokens = prefix.trim().split(/\s+/).filter(Boolean);
|
|
2361
|
-
const argumentTokens = tokens.slice(1);
|
|
2362
|
-
if (!hasTrailingSpace && argumentTokens.length === 0 && !"/skill".startsWith(prefix.toLowerCase()))
|
|
2363
|
-
return [];
|
|
2364
|
-
if (argumentTokens.length === 0)
|
|
2365
|
-
return skillActionCompletions("");
|
|
2366
|
-
const [first = "", second = ""] = argumentTokens;
|
|
2367
|
-
if (first === "list" || first === "ls" || first === "import")
|
|
2368
|
-
return [];
|
|
2369
|
-
if (first === "delete" || first === "remove" || first === "rm") {
|
|
2370
|
-
if (argumentTokens.length > 1 && hasTrailingSpace)
|
|
2371
|
-
return [];
|
|
2372
|
-
return skillNameCompletions(skills, hasTrailingSpace ? "" : second, "delete");
|
|
1434
|
+
if (action === "info") {
|
|
1435
|
+
const key = requireKey();
|
|
1436
|
+
const info = await runtime.secretStore.info(key);
|
|
1437
|
+
return systemLine(info ? formatReplData(info, 4000) : `Secret "${key}" does not exist.`, EXPANDED_SUMMARY_MAX_LINES);
|
|
2373
1438
|
}
|
|
2374
|
-
|
|
2375
|
-
return [];
|
|
2376
|
-
return skillActionCompletions(first);
|
|
2377
|
-
}
|
|
2378
|
-
function skillActionCompletions(current) {
|
|
2379
|
-
return SKILL_COMMAND_ACTIONS
|
|
2380
|
-
.flatMap((action) => [action.name, ...("aliases" in action ? action.aliases ?? [] : [])].map((name) => ({ name, description: action.description })))
|
|
2381
|
-
.filter((action) => action.name.startsWith(current.toLowerCase()))
|
|
2382
|
-
.map((action) => ({
|
|
2383
|
-
value: action.name,
|
|
2384
|
-
insertText: action.name === "list" || action.name === "ls" ? `/skill ${action.name}` : `/skill ${action.name} `,
|
|
2385
|
-
description: action.description,
|
|
2386
|
-
arguments: "optional",
|
|
2387
|
-
kind: "skill-action",
|
|
2388
|
-
}));
|
|
2389
|
-
}
|
|
2390
|
-
function skillNameCompletions(skills, current, action) {
|
|
2391
|
-
return skills
|
|
2392
|
-
.filter((skill) => skill.name.toLowerCase().includes(current.toLowerCase()))
|
|
2393
|
-
.map((skill) => ({
|
|
2394
|
-
value: skill.name,
|
|
2395
|
-
insertText: action === "delete" ? `/skill delete ${skill.name}` : `/skill ${skill.name}`,
|
|
2396
|
-
description: formatSkillCompletionDescription(skill),
|
|
2397
|
-
arguments: "optional",
|
|
2398
|
-
kind: "skill",
|
|
2399
|
-
}));
|
|
2400
|
-
}
|
|
2401
|
-
function formatSkillCompletionDescription(skill) {
|
|
2402
|
-
const tags = skill.tags?.length ? ` · ${skill.tags.join(",")}` : "";
|
|
2403
|
-
return `${skill.description}${skill.execution ? ` · ${skill.execution}` : ""}${tags}`;
|
|
2404
|
-
}
|
|
2405
|
-
function secretCommandCompletions(prefix, secrets) {
|
|
2406
|
-
const hasTrailingSpace = /\s$/.test(prefix);
|
|
2407
|
-
const tokens = prefix.trim().split(/\s+/).filter(Boolean);
|
|
2408
|
-
const argumentTokens = tokens.slice(1);
|
|
2409
|
-
if (!hasTrailingSpace && argumentTokens.length === 0 && !"/secret".startsWith(prefix.toLowerCase()))
|
|
2410
|
-
return [];
|
|
2411
|
-
if (argumentTokens.length === 0)
|
|
2412
|
-
return secretActionCompletions("");
|
|
2413
|
-
const [action = "", key = "", newKey = ""] = argumentTokens;
|
|
2414
|
-
const normalizedAction = secretCanonicalAction(action);
|
|
2415
|
-
if (!normalizedAction) {
|
|
2416
|
-
if (argumentTokens.length > 1 || hasTrailingSpace)
|
|
2417
|
-
return [];
|
|
2418
|
-
return secretActionCompletions(action);
|
|
2419
|
-
}
|
|
2420
|
-
if (normalizedAction === "list") {
|
|
2421
|
-
if (argumentTokens.length === 1 && hasTrailingSpace)
|
|
2422
|
-
return [{ value: "--show", insertText: "/secret list --show", description: "Print plaintext values in the REPL", arguments: "optional", kind: "secret-action" }];
|
|
2423
|
-
if (argumentTokens.length === 2 && !hasTrailingSpace)
|
|
2424
|
-
return "--show".startsWith(key) ? [{ value: "--show", insertText: "/secret list --show", description: "Print plaintext values in the REPL", arguments: "optional", kind: "secret-action" }] : [];
|
|
2425
|
-
return [];
|
|
2426
|
-
}
|
|
2427
|
-
if (normalizedAction === "set" || normalizedAction === "request") {
|
|
2428
|
-
if (argumentTokens.length <= 1 && hasTrailingSpace)
|
|
2429
|
-
return [];
|
|
2430
|
-
return [];
|
|
2431
|
-
}
|
|
2432
|
-
if (normalizedAction === "rename") {
|
|
2433
|
-
if (argumentTokens.length <= 1)
|
|
2434
|
-
return hasTrailingSpace ? secretKeyCompletions(secrets, "", normalizedAction) : [];
|
|
2435
|
-
if (argumentTokens.length === 2 && !hasTrailingSpace)
|
|
2436
|
-
return secretKeyCompletions(secrets, key, normalizedAction);
|
|
2437
|
-
if (argumentTokens.length === 2 && hasTrailingSpace)
|
|
2438
|
-
return [];
|
|
2439
|
-
if (argumentTokens.length === 3 && !hasTrailingSpace && newKey)
|
|
2440
|
-
return [];
|
|
2441
|
-
return [];
|
|
2442
|
-
}
|
|
2443
|
-
if (normalizedAction === "get" || normalizedAction === "info" || normalizedAction === "delete") {
|
|
2444
|
-
if (argumentTokens.length <= 1)
|
|
2445
|
-
return hasTrailingSpace ? secretKeyCompletions(secrets, "", normalizedAction) : [];
|
|
2446
|
-
if (argumentTokens.length === 2 && !hasTrailingSpace)
|
|
2447
|
-
return secretKeyCompletions(secrets, key, normalizedAction);
|
|
2448
|
-
return [];
|
|
2449
|
-
}
|
|
2450
|
-
return [];
|
|
2451
|
-
}
|
|
2452
|
-
function secretCanonicalAction(action) {
|
|
2453
|
-
const lower = action.toLowerCase();
|
|
2454
|
-
if (lower === "ls")
|
|
2455
|
-
return "list";
|
|
2456
|
-
if (lower === "show")
|
|
2457
|
-
return "get";
|
|
2458
|
-
if (lower === "empty")
|
|
2459
|
-
return "request";
|
|
2460
|
-
if (lower === "mv")
|
|
2461
|
-
return "rename";
|
|
2462
|
-
if (lower === "remove" || lower === "rm")
|
|
2463
|
-
return "delete";
|
|
2464
|
-
return ["list", "get", "set", "request", "info", "rename", "delete"].includes(lower) ? lower : undefined;
|
|
2465
|
-
}
|
|
2466
|
-
function secretActionCompletions(current) {
|
|
2467
|
-
return SECRET_COMMAND_ACTIONS
|
|
2468
|
-
.flatMap((action) => [action.name, ...("aliases" in action ? action.aliases ?? [] : [])].map((name) => ({ name, description: action.description })))
|
|
2469
|
-
.filter((action) => action.name.startsWith(current.toLowerCase()))
|
|
2470
|
-
.map((action) => ({
|
|
2471
|
-
value: action.name,
|
|
2472
|
-
insertText: `/secret ${action.name} `,
|
|
2473
|
-
description: action.description,
|
|
2474
|
-
arguments: "optional",
|
|
2475
|
-
kind: "secret-action",
|
|
2476
|
-
}));
|
|
2477
|
-
}
|
|
2478
|
-
function secretKeyCompletions(secrets, current, action) {
|
|
2479
|
-
return secrets
|
|
2480
|
-
.filter((secret) => secret.key.toLowerCase().includes(current.toLowerCase()))
|
|
2481
|
-
.map((secret) => ({
|
|
2482
|
-
value: secret.key,
|
|
2483
|
-
insertText: `/secret ${action} ${secret.key}${action === "rename" ? " " : ""}`,
|
|
2484
|
-
description: `${secret.status} · length=${secret.length}${secret.requestReason ? ` · ${secret.requestReason}` : ""}`,
|
|
2485
|
-
arguments: "optional",
|
|
2486
|
-
kind: "secret-key",
|
|
2487
|
-
}));
|
|
2488
|
-
}
|
|
2489
|
-
function modelCommandCompletions(prefix) {
|
|
2490
|
-
const hasTrailingSpace = /\s$/.test(prefix);
|
|
2491
|
-
const tokens = prefix.trim().split(/\s+/).filter(Boolean);
|
|
2492
|
-
const argumentTokens = tokens.slice(1);
|
|
2493
|
-
if (!hasTrailingSpace && argumentTokens.length === 0 && !"/model".startsWith(prefix.toLowerCase()))
|
|
2494
|
-
return [];
|
|
2495
|
-
if (argumentTokens.length >= 2 && !hasTrailingSpace) {
|
|
2496
|
-
const current = argumentTokens[1] ?? "";
|
|
2497
|
-
return reasoningCompletions(argumentTokens[0] ?? "", current);
|
|
2498
|
-
}
|
|
2499
|
-
if (argumentTokens.length >= 2)
|
|
2500
|
-
return [];
|
|
2501
|
-
if (argumentTokens.length === 1 && hasTrailingSpace) {
|
|
2502
|
-
const first = argumentTokens[0] ?? "";
|
|
2503
|
-
return isModelReasoningArgument(first) ? [] : reasoningCompletions(first, "");
|
|
2504
|
-
}
|
|
2505
|
-
const current = argumentTokens[0] ?? "";
|
|
2506
|
-
const modelCompletions = availableModelIds()
|
|
2507
|
-
.filter((modelId) => modelId.toLowerCase().includes(current.toLowerCase()))
|
|
2508
|
-
.map((modelId) => modelCompletion(modelId));
|
|
2509
|
-
const reasoning = reasoningChoicesForModel(undefined)
|
|
2510
|
-
.filter((choice) => choice.startsWith(current.toLowerCase()))
|
|
2511
|
-
.map((choice) => reasoningCompletion("", choice));
|
|
2512
|
-
return [...modelCompletions, ...reasoning];
|
|
2513
|
-
}
|
|
2514
|
-
function modelCompletion(modelId) {
|
|
2515
|
-
const window = resolveContextWindowTokens(modelId);
|
|
2516
|
-
const metadata = window.model;
|
|
2517
|
-
const efforts = reasoningEffortsForModel(modelId);
|
|
2518
|
-
const details = [
|
|
2519
|
-
metadata?.provider,
|
|
2520
|
-
metadata?.reasoning ? (efforts?.length ? `reasoning: ${efforts.join("/")}` : "reasoning") : undefined,
|
|
2521
|
-
metadata?.imageInput ? "vision" : undefined,
|
|
2522
|
-
window.tokens ? `${formatCompactNumber(window.tokens)} ctx` : undefined,
|
|
2523
|
-
].filter(Boolean).join(" · ");
|
|
2524
|
-
return {
|
|
2525
|
-
value: modelId,
|
|
2526
|
-
insertText: `/model ${modelId}`,
|
|
2527
|
-
description: details || "model id",
|
|
2528
|
-
arguments: "optional",
|
|
2529
|
-
kind: "model",
|
|
2530
|
-
};
|
|
2531
|
-
}
|
|
2532
|
-
function reasoningCompletions(modelId, current) {
|
|
2533
|
-
return reasoningChoicesForModel(modelId || undefined)
|
|
2534
|
-
.filter((choice) => choice.startsWith(current.toLowerCase()))
|
|
2535
|
-
.map((choice) => reasoningCompletion(modelId, choice));
|
|
2536
|
-
}
|
|
2537
|
-
function reasoningChoicesForModel(modelId) {
|
|
2538
|
-
if (!modelId)
|
|
2539
|
-
return [...MODEL_REASONING_EFFORTS, ...MODEL_REASONING_CONTROL_CHOICES];
|
|
2540
|
-
const efforts = reasoningEffortsForModel(modelId);
|
|
2541
|
-
if (!efforts)
|
|
2542
|
-
return MODEL_REASONING_CONTROL_CHOICES;
|
|
2543
|
-
return [...efforts, ...MODEL_REASONING_CONTROL_CHOICES];
|
|
2544
|
-
}
|
|
2545
|
-
function reasoningCompletion(modelId, choice) {
|
|
2546
|
-
return {
|
|
2547
|
-
value: choice,
|
|
2548
|
-
insertText: modelId ? `/model ${modelId} ${choice}` : `/model ${choice}`,
|
|
2549
|
-
description: reasoningDescription(choice),
|
|
2550
|
-
arguments: "optional",
|
|
2551
|
-
kind: "reasoning",
|
|
2552
|
-
};
|
|
2553
|
-
}
|
|
2554
|
-
function availableModelIds() {
|
|
2555
|
-
const ids = loadModelCatalog().models.flatMap((model) => model.modelIds.length ? model.modelIds : [model.id]);
|
|
2556
|
-
return [...new Set(ids)].sort((left, right) => left.localeCompare(right));
|
|
2557
|
-
}
|
|
2558
|
-
function slashCompletionPageCount(completions) {
|
|
2559
|
-
return Math.max(1, Math.ceil(completions.length / SLASH_COMPLETION_PAGE_SIZE));
|
|
2560
|
-
}
|
|
2561
|
-
function slashCompletionPageStart(selectedIndex, completions) {
|
|
2562
|
-
const page = Math.floor(Math.max(0, selectedIndex) / SLASH_COMPLETION_PAGE_SIZE);
|
|
2563
|
-
return Math.min(page * SLASH_COMPLETION_PAGE_SIZE, Math.max(0, (slashCompletionPageCount(completions) - 1) * SLASH_COMPLETION_PAGE_SIZE));
|
|
2564
|
-
}
|
|
2565
|
-
function visibleSlashCompletions(completions, selectedIndex) {
|
|
2566
|
-
const start = slashCompletionPageStart(selectedIndex, completions);
|
|
2567
|
-
return completions.slice(start, start + SLASH_COMPLETION_PAGE_SIZE);
|
|
2568
|
-
}
|
|
2569
|
-
function slashCompletionViewHeight(completions) {
|
|
2570
|
-
if (completions.length === 0)
|
|
2571
|
-
return 0;
|
|
2572
|
-
return Math.min(completions.length, SLASH_COMPLETION_PAGE_SIZE) + 2;
|
|
2573
|
-
}
|
|
2574
|
-
function slashCompletionSelectableCount(text, cursor, skills = [], secrets = []) {
|
|
2575
|
-
return slashCommandCompletions(text, cursor, skills, secrets).length;
|
|
2576
|
-
}
|
|
2577
|
-
function selectedSlashCommandCompletion(text, cursor, selectedIndex, skills = [], secrets = []) {
|
|
2578
|
-
const completions = slashCommandCompletions(text, cursor, skills, secrets);
|
|
2579
|
-
if (completions.length === 0)
|
|
2580
|
-
return undefined;
|
|
2581
|
-
return completions[Math.max(0, Math.min(selectedIndex, completions.length - 1))];
|
|
2582
|
-
}
|
|
2583
|
-
function PromptLine({ text, cursor, busy, locked, placeholder = false, ghostText, width, prompt, slashCompletions, selectedSlashCompletionIndex, attachments }) {
|
|
2584
|
-
const displayText = text.length === 0 && ghostText ? ` ${ghostText}` : text;
|
|
2585
|
-
const displayCursor = text.length === 0 && ghostText ? 0 : cursor;
|
|
2586
|
-
const visualLines = promptTextView(displayText, displayCursor, width, prompt);
|
|
2587
|
-
const inputColor = placeholder ? "gray" : (!locked && isValidReplCommandLine(text) ? "cyan" : undefined);
|
|
2588
|
-
return e(Box, { flexDirection: "column" }, ...visualLines.map((line, index) => {
|
|
2589
|
-
const isGhostLine = text.length === 0 && ghostText !== undefined;
|
|
2590
|
-
const afterColor = isGhostLine ? "gray" : inputColor;
|
|
2591
|
-
return e(Box, { key: `prompt-${index}`, height: 1, overflow: "hidden" }, e(Text, { color: locked ? "gray" : "cyan" }, index === 0 ? prompt : " ".repeat(prompt.length)), ...renderPromptPart(line.before, inputColor, attachments, `prompt-${index}-before`), e(Text, { key: `prompt-${index}-cursor`, inverse: true, color: inputColor }, line.selected), ...renderPromptPart(line.after, afterColor, attachments, `prompt-${index}-after`));
|
|
2592
|
-
}), ...SlashCompletionLines({ completions: slashCompletions, width, prompt, selectedIndex: selectedSlashCompletionIndex }));
|
|
2593
|
-
}
|
|
2594
|
-
function PasteStatusLine({ text, width: terminalWidth }) {
|
|
2595
|
-
const width = statusBarWidth(terminalWidth);
|
|
2596
|
-
return e(Box, { width, height: 1, overflow: "hidden" }, e(Text, { color: "yellow" }, fitToWidth(text, width)));
|
|
2597
|
-
}
|
|
2598
|
-
function QueuedInputLine({ text, width: terminalWidth }) {
|
|
2599
|
-
const width = statusBarWidth(terminalWidth);
|
|
2600
|
-
const preview = fitToWidth(`pending next: ${text.replace(/\s+/g, " ").trim()} (Esc to edit)`, width);
|
|
2601
|
-
return e(Box, { width, height: 1, overflow: "hidden" }, e(Text, { color: "yellow" }, preview));
|
|
2602
|
-
}
|
|
2603
|
-
function renderPromptPart(text, color, attachments, keyPrefix) {
|
|
2604
|
-
if (!text)
|
|
2605
|
-
return [];
|
|
2606
|
-
const activeLabels = attachments.map((attachment) => attachment.label).filter((label) => text.includes(label));
|
|
2607
|
-
if (activeLabels.length === 0)
|
|
2608
|
-
return [e(Text, { key: `${keyPrefix}-plain`, color }, text)];
|
|
2609
|
-
const pattern = new RegExp(activeLabels.map(escapeRegExp).join("|"), "g");
|
|
2610
|
-
const nodes = [];
|
|
2611
|
-
let lastIndex = 0;
|
|
2612
|
-
let match;
|
|
2613
|
-
while ((match = pattern.exec(text)) !== null) {
|
|
2614
|
-
if (match.index > lastIndex)
|
|
2615
|
-
nodes.push(e(Text, { key: `${keyPrefix}-plain-${nodes.length}`, color }, text.slice(lastIndex, match.index)));
|
|
2616
|
-
nodes.push(e(Text, { key: `${keyPrefix}-tag-${nodes.length}`, color: "black", backgroundColor: "cyan", bold: true }, match[0]));
|
|
2617
|
-
lastIndex = match.index + match[0].length;
|
|
2618
|
-
}
|
|
2619
|
-
if (lastIndex < text.length)
|
|
2620
|
-
nodes.push(e(Text, { key: `${keyPrefix}-plain-${nodes.length}`, color }, text.slice(lastIndex)));
|
|
2621
|
-
return nodes;
|
|
2622
|
-
}
|
|
2623
|
-
function SlashCompletionLines({ completions, width, prompt, selectedIndex }) {
|
|
2624
|
-
if (completions.length === 0)
|
|
2625
|
-
return [];
|
|
2626
|
-
const pageStart = slashCompletionPageStart(selectedIndex, completions);
|
|
2627
|
-
const visibleCompletions = visibleSlashCompletions(completions, selectedIndex);
|
|
2628
|
-
const safeSelectedIndex = Math.max(0, Math.min(selectedIndex - pageStart, visibleCompletions.length - 1));
|
|
2629
|
-
const contentWidth = Math.max(20, width - prompt.length);
|
|
2630
|
-
const nameWidth = Math.min(32, Math.max(...visibleCompletions.map((completion) => completion.value.length)));
|
|
2631
|
-
const pageCount = slashCompletionPageCount(completions);
|
|
2632
|
-
const pageIndex = Math.floor(pageStart / SLASH_COMPLETION_PAGE_SIZE) + 1;
|
|
2633
|
-
const footer = pageCount > 1 ? "↑/↓ select · ←/→ page · Tab complete" : "↑/↓ select · Tab complete";
|
|
2634
|
-
const rows = visibleCompletions.map((completion, index) => {
|
|
2635
|
-
const selected = index === safeSelectedIndex;
|
|
2636
|
-
const numberPrefix = `${pageStart + index + 1}.`.padStart(String(completions.length).length + 1);
|
|
2637
|
-
const descriptionWidth = Math.max(0, contentWidth - numberPrefix.length - nameWidth - 4);
|
|
2638
|
-
const description = fitToWidth(completion.description, descriptionWidth);
|
|
2639
|
-
return e(Text, { key: `slash-completion-${completion.kind}-${completion.insertText}`, color: "white" }, e(Text, {
|
|
2640
|
-
color: selected ? "black" : "white",
|
|
2641
|
-
backgroundColor: selected ? "cyan" : undefined,
|
|
2642
|
-
}, numberPrefix), e(Text, { color: "gray" }, " "), e(Text, { color: completion.kind === "reasoning" ? "magenta" : "cyan" }, completion.value.padEnd(nameWidth)), e(Text, { color: "gray" }, " "), e(Text, { color: selected ? "white" : "gray" }, description));
|
|
2643
|
-
});
|
|
2644
|
-
const title = pageCount > 1 ? `Completions (${completions.length}) page ${pageIndex}/${pageCount}` : `Completions (${completions.length})`;
|
|
2645
|
-
return [
|
|
2646
|
-
e(Text, { key: "slash-completion-header", color: "cyan", bold: true }, fitToWidth(title, contentWidth)),
|
|
2647
|
-
...rows,
|
|
2648
|
-
e(Text, { key: "slash-completion-footer", color: "gray" }, fitToWidth(footer, contentWidth)),
|
|
2649
|
-
].map((line, index) => e(Box, { key: `slash-completion-line-${index}`, height: 1, overflow: "hidden" }, e(Text, { color: "gray" }, " ".repeat(prompt.length)), line));
|
|
2650
|
-
}
|
|
2651
|
-
async function handleSecretCommand(command, runtime) {
|
|
2652
|
-
const usage = "Usage: /secret <list|get|set|request|delete|rename|info> ...";
|
|
2653
|
-
const action = command.action ?? "list";
|
|
2654
|
-
const requireKey = () => {
|
|
2655
|
-
if (!command.key)
|
|
2656
|
-
throw new Error(usage);
|
|
2657
|
-
return command.key;
|
|
2658
|
-
};
|
|
2659
|
-
if (action === "list") {
|
|
2660
|
-
const entries = await runtime.secretStore.list();
|
|
2661
|
-
if (entries.length === 0)
|
|
2662
|
-
return systemLine("No secrets stored.");
|
|
2663
|
-
const lines = await Promise.all(entries.map(async (entry) => {
|
|
2664
|
-
if (command.show) {
|
|
2665
|
-
const value = entry.status === "set" ? await runtime.secretStore.getPlaintext(entry.key) : "";
|
|
2666
|
-
return `${entry.key} = ${value}`;
|
|
2667
|
-
}
|
|
2668
|
-
const reason = entry.requestReason ? ` reason=${JSON.stringify(entry.requestReason)}` : "";
|
|
2669
|
-
return `${entry.key}\t${entry.status}\tlength=${entry.length}${reason}`;
|
|
2670
|
-
}));
|
|
2671
|
-
return systemLine(lines.join("\n"), EXPANDED_SUMMARY_MAX_LINES);
|
|
2672
|
-
}
|
|
2673
|
-
if (action === "get") {
|
|
2674
|
-
const key = requireKey();
|
|
2675
|
-
const info = await runtime.secretStore.info(key);
|
|
2676
|
-
if (!info)
|
|
2677
|
-
return systemLine(`Secret "${key}" does not exist.`);
|
|
2678
|
-
const value = await runtime.secretStore.getPlaintext(key);
|
|
2679
|
-
return systemLine(info.status === "empty" ? `Secret "${key}" is empty.` : value, EXPANDED_SUMMARY_MAX_LINES);
|
|
2680
|
-
}
|
|
2681
|
-
if (action === "set") {
|
|
2682
|
-
const key = requireKey();
|
|
2683
|
-
const meta = await runtime.secretStore.setPlaintext(key, command.value ?? "");
|
|
2684
|
-
return systemLine(`Secret "${meta.key}" saved, status=${meta.status}, length=${meta.length}.`);
|
|
2685
|
-
}
|
|
2686
|
-
if (action === "request" || action === "empty") {
|
|
2687
|
-
const key = requireKey();
|
|
2688
|
-
const meta = await runtime.secretStore.requestEmpty(key, { reason: command.reason, requestedBy: "user" });
|
|
2689
|
-
return systemLine(`Secret "${meta.key}" is ${meta.status}. Fill it with: /secret set ${meta.key} <value>`);
|
|
2690
|
-
}
|
|
2691
|
-
if (action === "delete") {
|
|
2692
|
-
const key = requireKey();
|
|
2693
|
-
const deleted = await runtime.secretStore.delete(key);
|
|
2694
|
-
return systemLine(deleted ? `Secret "${key}" deleted.` : `Secret "${key}" did not exist.`);
|
|
2695
|
-
}
|
|
2696
|
-
if (action === "rename") {
|
|
2697
|
-
const key = requireKey();
|
|
2698
|
-
if (!command.newKey)
|
|
2699
|
-
throw new Error("Usage: /secret rename <oldKey> <newKey>");
|
|
2700
|
-
const meta = await runtime.secretStore.rename(key, command.newKey);
|
|
2701
|
-
return systemLine(`Secret renamed to "${meta.key}".`);
|
|
2702
|
-
}
|
|
2703
|
-
if (action === "info") {
|
|
2704
|
-
const key = requireKey();
|
|
2705
|
-
const info = await runtime.secretStore.info(key);
|
|
2706
|
-
return systemLine(info ? formatReplData(info, 4000) : `Secret "${key}" does not exist.`, EXPANDED_SUMMARY_MAX_LINES);
|
|
2707
|
-
}
|
|
2708
|
-
return systemLine(usage);
|
|
1439
|
+
return systemLine(usage);
|
|
2709
1440
|
}
|
|
2710
1441
|
async function handleSkillCommand(command, runtime) {
|
|
2711
1442
|
if (command.action === "import")
|
|
@@ -2918,20 +1649,6 @@ function envValueForReasoning(reasoning) {
|
|
|
2918
1649
|
return "off";
|
|
2919
1650
|
return reasoning?.effort;
|
|
2920
1651
|
}
|
|
2921
|
-
async function writeEnvUpdates(envPath, updates, removeKeys = []) {
|
|
2922
|
-
await fs.mkdir(path.dirname(envPath), { recursive: true });
|
|
2923
|
-
const existing = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
2924
|
-
const next = updateEnvContent(existing, updates, removeKeys);
|
|
2925
|
-
await fs.writeFile(envPath, next, "utf8");
|
|
2926
|
-
}
|
|
2927
|
-
function applyEnvUpdatesToProcess(updates) {
|
|
2928
|
-
for (const [key, value] of Object.entries(updates)) {
|
|
2929
|
-
if (value === undefined)
|
|
2930
|
-
delete process.env[key];
|
|
2931
|
-
else
|
|
2932
|
-
process.env[key] = value;
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
2935
1652
|
function validateModelReasoningArgument(modelId, reasoning) {
|
|
2936
1653
|
if (!reasoning || reasoning === "default" || reasoning === "off")
|
|
2937
1654
|
return undefined;
|
|
@@ -2990,172 +1707,6 @@ async function handleLogCommand(command, runtime, append) {
|
|
|
2990
1707
|
runtime.communicationLogger.setDirectory(command.path);
|
|
2991
1708
|
append(systemLine(`model communication logs: ${path.resolve(command.path)}`));
|
|
2992
1709
|
}
|
|
2993
|
-
function renderMessage(message, append, activeAssistantId, options = {}) {
|
|
2994
|
-
if (message.metadata?.syntheticToolUse === true)
|
|
2995
|
-
return false;
|
|
2996
|
-
if (message.role === "progress" || message.isMeta)
|
|
2997
|
-
return false;
|
|
2998
|
-
if (message.role === "assistant" && activeAssistantId !== undefined && message.blocks.some((block) => block.type === "text")) {
|
|
2999
|
-
return true;
|
|
3000
|
-
}
|
|
3001
|
-
let rendered = false;
|
|
3002
|
-
for (const block of message.blocks) {
|
|
3003
|
-
if (block.type === "text") {
|
|
3004
|
-
const kind = kindForRole(message.role);
|
|
3005
|
-
if (kind === "meta")
|
|
3006
|
-
continue;
|
|
3007
|
-
if (kind === "system")
|
|
3008
|
-
append({ kind, title: titleForRole(message.role), text: block.text, previewStyle: "summary" });
|
|
3009
|
-
else
|
|
3010
|
-
append({ kind, text: block.text });
|
|
3011
|
-
rendered = true;
|
|
3012
|
-
}
|
|
3013
|
-
if (block.type === "image") {
|
|
3014
|
-
const kind = kindForRole(message.role);
|
|
3015
|
-
if (kind === "meta")
|
|
3016
|
-
continue;
|
|
3017
|
-
append({ kind, text: block.label ?? `[image ${block.mimeType}]` });
|
|
3018
|
-
rendered = true;
|
|
3019
|
-
}
|
|
3020
|
-
if (block.type === "thinking") {
|
|
3021
|
-
if (options.includeThinkingBlocks === false)
|
|
3022
|
-
continue;
|
|
3023
|
-
append(thinkingLine(block.text));
|
|
3024
|
-
rendered = true;
|
|
3025
|
-
}
|
|
3026
|
-
if (block.type === "tool_use" && options.includeToolUseBlocks) {
|
|
3027
|
-
append({ ...formatToolUse(block), live: false });
|
|
3028
|
-
rendered = true;
|
|
3029
|
-
}
|
|
3030
|
-
if (block.type === "tool_result") {
|
|
3031
|
-
append(formatToolResultLine(block.name, block.output, block.ok));
|
|
3032
|
-
rendered = true;
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
return rendered;
|
|
3036
|
-
}
|
|
3037
|
-
function renderToolResultMessage(message, append, replaceLine, activeToolLineIds, scheduleReplacement) {
|
|
3038
|
-
let rendered = false;
|
|
3039
|
-
for (const block of message.blocks) {
|
|
3040
|
-
if (block.type !== "tool_result")
|
|
3041
|
-
continue;
|
|
3042
|
-
const line = formatToolResultLine(block.name, block.output, block.ok);
|
|
3043
|
-
const id = activeToolLineIds.get(block.toolUseId);
|
|
3044
|
-
if (id === undefined) {
|
|
3045
|
-
append(line);
|
|
3046
|
-
}
|
|
3047
|
-
else {
|
|
3048
|
-
replaceLine(id, {
|
|
3049
|
-
kind: line.kind,
|
|
3050
|
-
title: toolTitle(block.name, "finished"),
|
|
3051
|
-
titleStatus: block.ok ? "success" : "failure",
|
|
3052
|
-
live: true,
|
|
3053
|
-
pendingReplacement: true,
|
|
3054
|
-
});
|
|
3055
|
-
activeToolLineIds.delete(block.toolUseId);
|
|
3056
|
-
scheduleReplacement(block.toolUseId, id, line);
|
|
3057
|
-
}
|
|
3058
|
-
rendered = true;
|
|
3059
|
-
}
|
|
3060
|
-
return rendered;
|
|
3061
|
-
}
|
|
3062
|
-
function assistantText(message) {
|
|
3063
|
-
const text = message.blocks
|
|
3064
|
-
.filter((block) => block.type === "text")
|
|
3065
|
-
.map((block) => block.text)
|
|
3066
|
-
.join("");
|
|
3067
|
-
return text.length > 0 ? text : undefined;
|
|
3068
|
-
}
|
|
3069
|
-
function thinkingText(message) {
|
|
3070
|
-
const text = message.blocks
|
|
3071
|
-
.filter((block) => block.type === "thinking")
|
|
3072
|
-
.map((block) => block.text)
|
|
3073
|
-
.join("");
|
|
3074
|
-
return text.length > 0 ? text : undefined;
|
|
3075
|
-
}
|
|
3076
|
-
function reduceStatus(status, event) {
|
|
3077
|
-
if (event.type === "state") {
|
|
3078
|
-
return {
|
|
3079
|
-
...status,
|
|
3080
|
-
phase: event.phase,
|
|
3081
|
-
detail: event.detail,
|
|
3082
|
-
usage: event.phase === "preparing" ? undefined : status.usage,
|
|
3083
|
-
streamedOutputTokens: event.phase === "preparing" ? 0 : status.streamedOutputTokens,
|
|
3084
|
-
inputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.inputTokenUpdatedAt,
|
|
3085
|
-
outputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.outputTokenUpdatedAt,
|
|
3086
|
-
retryCooldownUntil: event.phase === "preparing" ? undefined : status.retryCooldownUntil,
|
|
3087
|
-
activityTick: status.activityTick + 1,
|
|
3088
|
-
};
|
|
3089
|
-
}
|
|
3090
|
-
if (event.type === "context.metrics") {
|
|
3091
|
-
return {
|
|
3092
|
-
...status,
|
|
3093
|
-
metrics: event.metrics,
|
|
3094
|
-
inputTokenUpdatedAt: event.metrics.estimatedInputTokens !== status.metrics?.estimatedInputTokens ? Date.now() : status.inputTokenUpdatedAt,
|
|
3095
|
-
activityTick: status.activityTick + 1,
|
|
3096
|
-
};
|
|
3097
|
-
}
|
|
3098
|
-
if (event.type === "usage") {
|
|
3099
|
-
return {
|
|
3100
|
-
...status,
|
|
3101
|
-
usage: event.usage,
|
|
3102
|
-
inputTokenUpdatedAt: event.usage.inputTokens !== undefined ? Date.now() : status.inputTokenUpdatedAt,
|
|
3103
|
-
outputTokenUpdatedAt: event.usage.outputTokens !== undefined ? Date.now() : status.outputTokenUpdatedAt,
|
|
3104
|
-
activityTick: status.activityTick + 1,
|
|
3105
|
-
};
|
|
3106
|
-
}
|
|
3107
|
-
if (event.type === "assistant.delta") {
|
|
3108
|
-
return {
|
|
3109
|
-
...status,
|
|
3110
|
-
phase: "calling_model",
|
|
3111
|
-
streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.text),
|
|
3112
|
-
outputTokenUpdatedAt: Date.now(),
|
|
3113
|
-
activityTick: status.activityTick + 1,
|
|
3114
|
-
};
|
|
3115
|
-
}
|
|
3116
|
-
if (event.type === "thinking.delta") {
|
|
3117
|
-
return {
|
|
3118
|
-
...status,
|
|
3119
|
-
phase: "thinking",
|
|
3120
|
-
streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.text),
|
|
3121
|
-
outputTokenUpdatedAt: Date.now(),
|
|
3122
|
-
activityTick: status.activityTick + 1,
|
|
3123
|
-
};
|
|
3124
|
-
}
|
|
3125
|
-
if (event.type === "tool_call.delta") {
|
|
3126
|
-
return {
|
|
3127
|
-
...status,
|
|
3128
|
-
phase: "calling_model",
|
|
3129
|
-
streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.argumentsDelta),
|
|
3130
|
-
outputTokenUpdatedAt: Date.now(),
|
|
3131
|
-
activityTick: status.activityTick + 1,
|
|
3132
|
-
};
|
|
3133
|
-
}
|
|
3134
|
-
if (event.type === "retrying") {
|
|
3135
|
-
return {
|
|
3136
|
-
...status,
|
|
3137
|
-
phase: "calling_model",
|
|
3138
|
-
detail: `retrying in ${(event.delayMs / 1000).toFixed(1)}s`,
|
|
3139
|
-
retryCooldownUntil: Date.now() + event.delayMs,
|
|
3140
|
-
activityTick: status.activityTick + 1,
|
|
3141
|
-
};
|
|
3142
|
-
}
|
|
3143
|
-
if (event.type === "terminal") {
|
|
3144
|
-
return {
|
|
3145
|
-
...status,
|
|
3146
|
-
phase: "stopped",
|
|
3147
|
-
detail: event.reason,
|
|
3148
|
-
inputTokenUpdatedAt: undefined,
|
|
3149
|
-
outputTokenUpdatedAt: undefined,
|
|
3150
|
-
retryCooldownUntil: undefined,
|
|
3151
|
-
activityTick: status.activityTick + 1,
|
|
3152
|
-
};
|
|
3153
|
-
}
|
|
3154
|
-
if (event.type === "message" || event.type === "tool.started" || event.type === "tool.finished" || event.type === "error") {
|
|
3155
|
-
return { ...status, activityTick: status.activityTick + 1 };
|
|
3156
|
-
}
|
|
3157
|
-
return status;
|
|
3158
|
-
}
|
|
3159
1710
|
async function handleSessionsCommand(runtime, runningSessionIds, setBrowser, append) {
|
|
3160
1711
|
const sessions = await runtime.engine.listSessions(Number.POSITIVE_INFINITY);
|
|
3161
1712
|
if (sessions.length === 0) {
|
|
@@ -3271,165 +1822,6 @@ function restoredHistoryLines(runtime) {
|
|
|
3271
1822
|
}
|
|
3272
1823
|
return lines;
|
|
3273
1824
|
}
|
|
3274
|
-
const LOGIN_PROVIDERS = ["openai", "anthropic"];
|
|
3275
|
-
const SHARED_LOGIN_FIELDS = [
|
|
3276
|
-
{ key: "reasoningEffort", label: "Reasoning effort", envKey: "MODEL_REASONING_EFFORT", scope: "shared", options: ["", "off", "none", "minimal", "low", "medium", "high", "xhigh", "max"] },
|
|
3277
|
-
{ key: "reasoningSummary", label: "Reasoning summary", envKey: "MODEL_REASONING_SUMMARY", scope: "shared", options: ["", "auto", "concise", "detailed"] },
|
|
3278
|
-
{ key: "maxOutputTokens", label: "Max output tokens", envKey: "MODEL_MAX_OUTPUT_TOKENS", scope: "shared", placeholder: "800" },
|
|
3279
|
-
{ key: "timeoutMs", label: "Timeout ms", envKey: "MODEL_TIMEOUT_MS", scope: "shared", placeholder: "120000" },
|
|
3280
|
-
{ key: "streamIdleTimeoutMs", label: "Stream idle timeout ms", envKey: "MODEL_STREAM_IDLE_TIMEOUT_MS", scope: "shared", placeholder: "120000" },
|
|
3281
|
-
{ key: "maxRetries", label: "Max retries", envKey: "MODEL_MAX_RETRIES", scope: "shared", placeholder: "2" },
|
|
3282
|
-
];
|
|
3283
|
-
const LOGIN_FIELD_DEFINITIONS = {
|
|
3284
|
-
openai: [
|
|
3285
|
-
{ key: "apiKey", label: "API key", envKey: "OPENAI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
|
|
3286
|
-
{ key: "baseUrl", label: "Base URL", envKey: "OPENAI_BASE_URL", scope: "provider", placeholder: "https://api.openai.com" },
|
|
3287
|
-
{ key: "model", label: "Model", envKey: "OPENAI_MODEL", scope: "provider", required: true, placeholder: "gpt-5.5" },
|
|
3288
|
-
{ key: "fallbackModel", label: "Fallback model", envKey: "OPENAI_FALLBACK_MODEL", scope: "provider" },
|
|
3289
|
-
{ key: "endpoint", label: "Endpoint", envKey: "OPENAI_ENDPOINT", scope: "provider", placeholder: "auto", options: ["auto", "responses", "chat"] },
|
|
3290
|
-
...SHARED_LOGIN_FIELDS,
|
|
3291
|
-
],
|
|
3292
|
-
anthropic: [
|
|
3293
|
-
{ key: "apiKey", label: "API key", envKey: "ANTHROPIC_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-ant-..." },
|
|
3294
|
-
{ key: "baseUrl", label: "Base URL", envKey: "ANTHROPIC_BASE_URL", scope: "provider", placeholder: "https://api.anthropic.com" },
|
|
3295
|
-
{ key: "model", label: "Model", envKey: "ANTHROPIC_MODEL", scope: "provider", required: true, placeholder: "claude-sonnet-4-6" },
|
|
3296
|
-
{ key: "fallbackModel", label: "Fallback model", envKey: "ANTHROPIC_FALLBACK_MODEL", scope: "provider" },
|
|
3297
|
-
{ key: "version", label: "Anthropic version", envKey: "ANTHROPIC_VERSION", scope: "provider", placeholder: "2023-06-01" },
|
|
3298
|
-
...SHARED_LOGIN_FIELDS,
|
|
3299
|
-
],
|
|
3300
|
-
};
|
|
3301
|
-
const DEPRECATED_MODEL_ENV_KEYS = [
|
|
3302
|
-
"MODEL_API_KEY",
|
|
3303
|
-
"MODEL_BASE_URL",
|
|
3304
|
-
"MODEL_ID",
|
|
3305
|
-
"MODEL_FALLBACK_ID",
|
|
3306
|
-
"MODEL_ENDPOINT",
|
|
3307
|
-
"OPENAI_PROVIDER",
|
|
3308
|
-
"OPENAI_REASONING_EFFORT",
|
|
3309
|
-
"OPENAI_REASONING_SUMMARY",
|
|
3310
|
-
"OPENAI_MAX_OUTPUT_TOKENS",
|
|
3311
|
-
"OPENAI_TIMEOUT_MS",
|
|
3312
|
-
"OPENAI_STREAM_IDLE_TIMEOUT_MS",
|
|
3313
|
-
"OPENAI_MAX_RETRIES",
|
|
3314
|
-
"ANTHROPIC_REASONING_EFFORT",
|
|
3315
|
-
"ANTHROPIC_REASONING_SUMMARY",
|
|
3316
|
-
"ANTHROPIC_MAX_OUTPUT_TOKENS",
|
|
3317
|
-
"ANTHROPIC_TIMEOUT_MS",
|
|
3318
|
-
"ANTHROPIC_STREAM_IDLE_TIMEOUT_MS",
|
|
3319
|
-
"ANTHROPIC_MAX_RETRIES",
|
|
3320
|
-
];
|
|
3321
|
-
function pagedPageCount(state) {
|
|
3322
|
-
return Math.max(1, Math.ceil(state.items.length / state.pageSize));
|
|
3323
|
-
}
|
|
3324
|
-
function pagedPageItems(state) {
|
|
3325
|
-
const start = state.pageIndex * state.pageSize;
|
|
3326
|
-
return state.items.slice(start, start + state.pageSize);
|
|
3327
|
-
}
|
|
3328
|
-
function pagedAbsoluteIndex(state) {
|
|
3329
|
-
return state.pageIndex * state.pageSize + state.selectedIndex;
|
|
3330
|
-
}
|
|
3331
|
-
function movePagedSelection(state, delta) {
|
|
3332
|
-
const pageLength = pagedPageItems(state).length;
|
|
3333
|
-
if (pageLength <= 0)
|
|
3334
|
-
return state;
|
|
3335
|
-
const selectedIndex = (state.selectedIndex + delta + pageLength) % pageLength;
|
|
3336
|
-
return { ...state, selectedIndex };
|
|
3337
|
-
}
|
|
3338
|
-
function movePagedPage(state, delta) {
|
|
3339
|
-
const pageCount = pagedPageCount(state);
|
|
3340
|
-
if (pageCount <= 1)
|
|
3341
|
-
return state;
|
|
3342
|
-
const pageIndex = (state.pageIndex + delta + pageCount) % pageCount;
|
|
3343
|
-
const pageLength = state.items.slice(pageIndex * state.pageSize, pageIndex * state.pageSize + state.pageSize).length;
|
|
3344
|
-
return { ...state, pageIndex, selectedIndex: Math.min(state.selectedIndex, Math.max(0, pageLength - 1)) };
|
|
3345
|
-
}
|
|
3346
|
-
function sessionsPageItems(state) {
|
|
3347
|
-
return pagedPageItems(state);
|
|
3348
|
-
}
|
|
3349
|
-
function sessionAbsoluteIndex(state) {
|
|
3350
|
-
return pagedAbsoluteIndex(state);
|
|
3351
|
-
}
|
|
3352
|
-
function moveSessionsSelection(state, delta) {
|
|
3353
|
-
return movePagedSelection(state, delta);
|
|
3354
|
-
}
|
|
3355
|
-
function moveSessionsPage(state, delta) {
|
|
3356
|
-
return movePagedPage(state, delta);
|
|
3357
|
-
}
|
|
3358
|
-
function sessionsBrowserViewHeight(state) {
|
|
3359
|
-
return sessionsPageItems(state).length + 3;
|
|
3360
|
-
}
|
|
3361
|
-
function skillsBrowserViewHeight(state) {
|
|
3362
|
-
return pagedPageItems(state).length + 3;
|
|
3363
|
-
}
|
|
3364
|
-
function secretsBrowserViewHeight(state) {
|
|
3365
|
-
return pagedPageItems(state).length + 3;
|
|
3366
|
-
}
|
|
3367
|
-
function SessionsBrowser({ state, width }) {
|
|
3368
|
-
const pageCount = pagedPageCount(state);
|
|
3369
|
-
const pageItems = sessionsPageItems(state);
|
|
3370
|
-
const showPagination = pageCount > 1;
|
|
3371
|
-
const contentWidth = Math.max(20, width);
|
|
3372
|
-
const header = showPagination
|
|
3373
|
-
? `Saved sessions (${state.sessions.length}) · page ${state.pageIndex + 1}/${pageCount}`
|
|
3374
|
-
: `Saved sessions (${state.sessions.length})`;
|
|
3375
|
-
const footer = showPagination
|
|
3376
|
-
? "↑/↓ select · ←/→ page · Enter resume · d/Delete remove · Esc close"
|
|
3377
|
-
: "↑/↓ select · Enter resume · d/Delete remove · Esc close";
|
|
3378
|
-
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(header, contentWidth)), ...pageItems.map((session, index) => {
|
|
3379
|
-
const selected = index === state.selectedIndex;
|
|
3380
|
-
const absoluteIndex = state.pageIndex * state.pageSize + index;
|
|
3381
|
-
const row = formatSessionBrowserRow(session, absoluteIndex, contentWidth, state.runningSessionIds.includes(session.sessionId));
|
|
3382
|
-
return e(Text, { key: session.sessionId, color: "white" }, e(Text, {
|
|
3383
|
-
color: selected ? "black" : "white",
|
|
3384
|
-
backgroundColor: selected ? "cyan" : undefined,
|
|
3385
|
-
}, row.numberPrefix), row.rest);
|
|
3386
|
-
}), e(Text, { color: "gray" }, fitToWidth(footer, contentWidth)));
|
|
3387
|
-
}
|
|
3388
|
-
function SkillsBrowser({ state, width }) {
|
|
3389
|
-
const pageCount = pagedPageCount(state);
|
|
3390
|
-
const pageItems = pagedPageItems(state);
|
|
3391
|
-
const showPagination = pageCount > 1;
|
|
3392
|
-
const contentWidth = Math.max(20, width);
|
|
3393
|
-
const header = showPagination
|
|
3394
|
-
? `Skills (${state.skills.length}) · page ${state.pageIndex + 1}/${pageCount}`
|
|
3395
|
-
: `Skills (${state.skills.length})`;
|
|
3396
|
-
const footer = showPagination
|
|
3397
|
-
? "↑/↓ select · ←/→ page · Enter details · i invoke · a import · d/Delete remove · Esc close"
|
|
3398
|
-
: "↑/↓ select · Enter details · i invoke · a import · d/Delete remove · Esc close";
|
|
3399
|
-
const nameWidth = Math.min(28, Math.max(...pageItems.map((skill) => skill.name.length)));
|
|
3400
|
-
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(header, contentWidth)), ...pageItems.map((skill, index) => {
|
|
3401
|
-
const selected = index === state.selectedIndex;
|
|
3402
|
-
const absoluteIndex = state.pageIndex * state.pageSize + index;
|
|
3403
|
-
const prefix = `${absoluteIndex + 1}.`.padStart(String(state.skills.length).length + 1);
|
|
3404
|
-
const tags = skill.tags?.length ? ` [${skill.tags.join(",")}]` : "";
|
|
3405
|
-
const execution = skill.execution ? ` (${skill.execution})` : "";
|
|
3406
|
-
const restWidth = Math.max(0, contentWidth - prefix.length - nameWidth - 4);
|
|
3407
|
-
const rest = fitToWidth(`${skill.description}${execution}${tags}`, restWidth);
|
|
3408
|
-
return e(Text, { key: skill.name, color: "white" }, e(Text, { color: selected ? "black" : "white", backgroundColor: selected ? "cyan" : undefined }, prefix), e(Text, { color: "gray" }, " "), e(Text, { color: "cyan" }, fitToWidth(skill.name, nameWidth).padEnd(nameWidth)), e(Text, { color: "gray" }, " "), e(Text, { color: selected ? "white" : "gray" }, rest));
|
|
3409
|
-
}), e(Text, { color: "gray" }, fitToWidth(footer, contentWidth)));
|
|
3410
|
-
}
|
|
3411
|
-
function SecretsBrowser({ state, width }) {
|
|
3412
|
-
const pageCount = pagedPageCount(state);
|
|
3413
|
-
const pageItems = pagedPageItems(state);
|
|
3414
|
-
const showPagination = pageCount > 1;
|
|
3415
|
-
const contentWidth = Math.max(20, width);
|
|
3416
|
-
const header = showPagination
|
|
3417
|
-
? `Secrets (${state.secrets.length}) · page ${state.pageIndex + 1}/${pageCount}`
|
|
3418
|
-
: `Secrets (${state.secrets.length})`;
|
|
3419
|
-
const footer = showPagination
|
|
3420
|
-
? "↑/↓ select · ←/→ page · Enter info · s set · r rename · a add · e empty · d/Delete remove · Esc close"
|
|
3421
|
-
: "↑/↓ select · Enter info · s set · r rename · a add · e empty · d/Delete remove · Esc close";
|
|
3422
|
-
const keyWidth = Math.min(32, Math.max(...pageItems.map((secret) => secret.key.length)));
|
|
3423
|
-
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(header, contentWidth)), ...pageItems.map((secret, index) => {
|
|
3424
|
-
const selected = index === state.selectedIndex;
|
|
3425
|
-
const absoluteIndex = state.pageIndex * state.pageSize + index;
|
|
3426
|
-
const prefix = `${absoluteIndex + 1}.`.padStart(String(state.secrets.length).length + 1);
|
|
3427
|
-
const reason = secret.requestReason ? ` reason=${JSON.stringify(secret.requestReason)}` : "";
|
|
3428
|
-
const restWidth = Math.max(0, contentWidth - prefix.length - keyWidth - 4);
|
|
3429
|
-
const rest = fitToWidth(`${secret.status} · length=${secret.length}${reason}`, restWidth);
|
|
3430
|
-
return e(Text, { key: secret.key, color: "white" }, e(Text, { color: selected ? "black" : "white", backgroundColor: selected ? "cyan" : undefined }, prefix), e(Text, { color: "gray" }, " "), e(Text, { color: secret.status === "set" ? "green" : "yellow" }, fitToWidth(secret.key, keyWidth).padEnd(keyWidth)), e(Text, { color: "gray" }, " "), e(Text, { color: selected ? "white" : "gray" }, rest));
|
|
3431
|
-
}), e(Text, { color: "gray" }, fitToWidth(footer, contentWidth)));
|
|
3432
|
-
}
|
|
3433
1825
|
function handleLoginFormInput(value, key, state, setLoginFormState, runtime, append, setStatus) {
|
|
3434
1826
|
if (key.escape) {
|
|
3435
1827
|
if (state.step === "fields")
|
|
@@ -3493,37 +1885,6 @@ function handleLoginFormInput(value, key, state, setLoginFormState, runtime, app
|
|
|
3493
1885
|
setLoginFormState(insertLoginFieldText(state, field, value));
|
|
3494
1886
|
}
|
|
3495
1887
|
}
|
|
3496
|
-
function moveLoginProviderSelection(state, delta) {
|
|
3497
|
-
const selectedProviderIndex = (state.selectedProviderIndex + delta + state.providers.length) % state.providers.length;
|
|
3498
|
-
return { ...state, selectedProviderIndex, provider: state.providers[selectedProviderIndex] ?? state.provider };
|
|
3499
|
-
}
|
|
3500
|
-
function moveLoginFieldSelection(state, delta) {
|
|
3501
|
-
const fields = LOGIN_FIELD_DEFINITIONS[state.provider];
|
|
3502
|
-
const selectedFieldIndex = (state.selectedFieldIndex + delta + fields.length) % fields.length;
|
|
3503
|
-
const field = fields[selectedFieldIndex];
|
|
3504
|
-
return { ...state, selectedFieldIndex, cursor: field ? (state.values[field.key] ?? "").length : 0 };
|
|
3505
|
-
}
|
|
3506
|
-
function cycleLoginFieldOption(state, field) {
|
|
3507
|
-
const options = field.options ?? [];
|
|
3508
|
-
const current = state.values[field.key] ?? "";
|
|
3509
|
-
const index = options.indexOf(current);
|
|
3510
|
-
const next = options[(index + 1 + options.length) % options.length] ?? "";
|
|
3511
|
-
return { ...state, values: { ...state.values, [field.key]: next }, cursor: next.length };
|
|
3512
|
-
}
|
|
3513
|
-
function insertLoginFieldText(state, field, value) {
|
|
3514
|
-
const current = state.values[field.key] ?? "";
|
|
3515
|
-
const cursor = Math.max(0, Math.min(state.cursor, current.length));
|
|
3516
|
-
const next = `${current.slice(0, cursor)}${value}${current.slice(cursor)}`;
|
|
3517
|
-
return { ...state, values: { ...state.values, [field.key]: next }, cursor: cursor + value.length };
|
|
3518
|
-
}
|
|
3519
|
-
function deleteLoginFieldCharacter(state, field) {
|
|
3520
|
-
const current = state.values[field.key] ?? "";
|
|
3521
|
-
const cursor = Math.max(0, Math.min(state.cursor, current.length));
|
|
3522
|
-
if (cursor <= 0)
|
|
3523
|
-
return state;
|
|
3524
|
-
const next = `${current.slice(0, cursor - 1)}${current.slice(cursor)}`;
|
|
3525
|
-
return { ...state, values: { ...state.values, [field.key]: next }, cursor: cursor - 1 };
|
|
3526
|
-
}
|
|
3527
1888
|
async function submitLoginForm(state, runtime, append, setLoginFormState, setStatus) {
|
|
3528
1889
|
const validationError = validateLoginForm(state);
|
|
3529
1890
|
if (validationError) {
|
|
@@ -3560,217 +1921,6 @@ async function submitLoginForm(state, runtime, append, setLoginFormState, setSta
|
|
|
3560
1921
|
append({ kind: "error", text: `Login save failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
3561
1922
|
}
|
|
3562
1923
|
}
|
|
3563
|
-
function validateLoginForm(state) {
|
|
3564
|
-
for (const field of LOGIN_FIELD_DEFINITIONS[state.provider]) {
|
|
3565
|
-
const value = (state.values[field.key] ?? "").trim();
|
|
3566
|
-
if (field.required && !value)
|
|
3567
|
-
return `${field.label} is required.`;
|
|
3568
|
-
if (field.options?.length && value && !field.options.includes(value))
|
|
3569
|
-
return `${field.label} must be one of: ${field.options.filter(Boolean).join(", ")}`;
|
|
3570
|
-
}
|
|
3571
|
-
for (const fieldKey of ["maxOutputTokens", "timeoutMs", "streamIdleTimeoutMs", "maxRetries"]) {
|
|
3572
|
-
const value = state.values[fieldKey]?.trim();
|
|
3573
|
-
if (value && !Number.isFinite(Number(value)))
|
|
3574
|
-
return `${fieldKey} must be a number.`;
|
|
3575
|
-
}
|
|
3576
|
-
return undefined;
|
|
3577
|
-
}
|
|
3578
|
-
function createLoginFormState(envPath = getUserDotEnvPath()) {
|
|
3579
|
-
const env = parseEnvFileSafe(envPath);
|
|
3580
|
-
const currentProvider = parseLoginProvider(env.MODEL_PROVIDER ?? process.env.MODEL_PROVIDER) ?? guessLoginProvider(env);
|
|
3581
|
-
return loginFormForProvider(currentProvider, envPath, env);
|
|
3582
|
-
}
|
|
3583
|
-
function loginFormForProvider(provider, envPath, env = parseEnvFileSafe(envPath)) {
|
|
3584
|
-
const selectedProviderIndex = Math.max(0, LOGIN_PROVIDERS.indexOf(provider));
|
|
3585
|
-
return {
|
|
3586
|
-
step: "provider",
|
|
3587
|
-
providers: LOGIN_PROVIDERS,
|
|
3588
|
-
selectedProviderIndex,
|
|
3589
|
-
provider,
|
|
3590
|
-
selectedFieldIndex: 0,
|
|
3591
|
-
cursor: 0,
|
|
3592
|
-
values: loginValuesForProvider(provider, env),
|
|
3593
|
-
envPath,
|
|
3594
|
-
};
|
|
3595
|
-
}
|
|
3596
|
-
function loginValuesForProvider(provider, env) {
|
|
3597
|
-
const values = {};
|
|
3598
|
-
for (const field of LOGIN_FIELD_DEFINITIONS[provider]) {
|
|
3599
|
-
values[field.key] = env[field.envKey] ?? "";
|
|
3600
|
-
}
|
|
3601
|
-
if (!values.baseUrl)
|
|
3602
|
-
values.baseUrl = defaultBaseUrlForLoginProvider(provider);
|
|
3603
|
-
if (!values.model)
|
|
3604
|
-
values.model = defaultModelForLoginProvider(provider);
|
|
3605
|
-
if (provider === "openai" && !values.endpoint)
|
|
3606
|
-
values.endpoint = "auto";
|
|
3607
|
-
return values;
|
|
3608
|
-
}
|
|
3609
|
-
function parseLoginProvider(value) {
|
|
3610
|
-
if (value === "openai" || value === "anthropic")
|
|
3611
|
-
return value;
|
|
3612
|
-
return undefined;
|
|
3613
|
-
}
|
|
3614
|
-
function guessLoginProvider(env) {
|
|
3615
|
-
if (env.ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY)
|
|
3616
|
-
return "anthropic";
|
|
3617
|
-
return "openai";
|
|
3618
|
-
}
|
|
3619
|
-
function defaultBaseUrlForLoginProvider(provider) {
|
|
3620
|
-
if (provider === "anthropic")
|
|
3621
|
-
return "https://api.anthropic.com";
|
|
3622
|
-
return "https://api.openai.com";
|
|
3623
|
-
}
|
|
3624
|
-
function defaultModelForLoginProvider(provider) {
|
|
3625
|
-
if (provider === "anthropic")
|
|
3626
|
-
return "claude-sonnet-4-6";
|
|
3627
|
-
return "gpt-5.5";
|
|
3628
|
-
}
|
|
3629
|
-
function loginFormViewHeight(state) {
|
|
3630
|
-
return state.step === "provider" ? state.providers.length + 3 : LOGIN_FIELD_DEFINITIONS[state.provider].length + 4;
|
|
3631
|
-
}
|
|
3632
|
-
function LoginFormView({ state, width }) {
|
|
3633
|
-
const contentWidth = Math.max(30, width);
|
|
3634
|
-
if (state.step === "provider") {
|
|
3635
|
-
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(`Login: choose provider · saving to ${state.envPath}`, contentWidth)), ...state.providers.map((provider, index) => e(Text, { key: `provider-${provider}-${index}`, color: "white" }, e(Text, { color: index === state.selectedProviderIndex ? "black" : "white", backgroundColor: index === state.selectedProviderIndex ? "cyan" : undefined }, `${index + 1}.`.padStart(3)), e(Text, { color: "gray" }, " "), e(Text, { color: "cyan" }, provider))), e(Text, { color: "gray" }, fitToWidth("↑/↓ select · Enter edit config · Esc close", contentWidth)));
|
|
3636
|
-
}
|
|
3637
|
-
const fields = LOGIN_FIELD_DEFINITIONS[state.provider];
|
|
3638
|
-
const maxLabel = Math.max(...fields.map((field) => field.label.length));
|
|
3639
|
-
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(`Login: ${state.provider} · ${state.envPath}`, contentWidth)), ...fields.map((field, index) => {
|
|
3640
|
-
const selected = index === state.selectedFieldIndex;
|
|
3641
|
-
const rawValue = state.values[field.key] ?? "";
|
|
3642
|
-
const visibleValue = formatLoginFieldValue(field, rawValue, selected ? state.cursor : undefined);
|
|
3643
|
-
const placeholder = rawValue ? "" : (field.placeholder ? ` (${field.placeholder})` : "");
|
|
3644
|
-
return e(Text, { key: field.key, color: "white" }, e(Text, { color: selected ? "black" : "white", backgroundColor: selected ? "cyan" : undefined }, `${index + 1}.`.padStart(3)), e(Text, { color: field.required ? "yellow" : "gray" }, ` ${field.label.padEnd(maxLabel)} `), e(Text, { color: field.scope === "shared" ? "blue" : "gray" }, field.scope === "shared" ? "shared " : "provider "), e(Text, { color: rawValue ? "white" : "gray" }, fitToWidth(`${visibleValue}${placeholder}`, Math.max(8, contentWidth - maxLabel - 14))));
|
|
3645
|
-
}), e(Text, { color: "gray" }, fitToWidth("↑/↓ field · ←/→ cursor · type edit · Tab cycle choices · Enter save · Esc back/cancel", contentWidth)), e(Text, { color: "gray" }, fitToWidth("Provider fields save as OPENAI_* / ANTHROPIC_*; shared runtime fields save as MODEL_*.", contentWidth)));
|
|
3646
|
-
}
|
|
3647
|
-
function formatLoginFieldValue(field, value, cursor) {
|
|
3648
|
-
const display = field.secret && value ? "•".repeat(Math.min(value.length, 24)) : value;
|
|
3649
|
-
if (cursor === undefined)
|
|
3650
|
-
return display;
|
|
3651
|
-
const safeCursor = Math.max(0, Math.min(cursor, display.length));
|
|
3652
|
-
const selected = display[safeCursor] ?? " ";
|
|
3653
|
-
return `${display.slice(0, safeCursor)}█${selected === " " ? "" : display.slice(safeCursor + 1)}`;
|
|
3654
|
-
}
|
|
3655
|
-
function applyLoginFormToProcessEnv(state) {
|
|
3656
|
-
applyEnvUpdatesToProcess(envEntriesForLoginForm(state));
|
|
3657
|
-
for (const key of DEPRECATED_MODEL_ENV_KEYS)
|
|
3658
|
-
delete process.env[key];
|
|
3659
|
-
}
|
|
3660
|
-
async function saveLoginFormToEnv(state) {
|
|
3661
|
-
await writeEnvUpdates(state.envPath, envEntriesForLoginForm(state), DEPRECATED_MODEL_ENV_KEYS);
|
|
3662
|
-
}
|
|
3663
|
-
function envEntriesForLoginForm(state) {
|
|
3664
|
-
const entries = {
|
|
3665
|
-
MODEL_PROVIDER: state.provider,
|
|
3666
|
-
};
|
|
3667
|
-
for (const field of LOGIN_FIELD_DEFINITIONS[state.provider]) {
|
|
3668
|
-
const value = (state.values[field.key] ?? "").trim();
|
|
3669
|
-
entries[field.envKey] = value || undefined;
|
|
3670
|
-
}
|
|
3671
|
-
return entries;
|
|
3672
|
-
}
|
|
3673
|
-
function updateEnvContent(content, updates, removeKeys = []) {
|
|
3674
|
-
const keys = new Set(Object.keys(updates));
|
|
3675
|
-
const removals = new Set(removeKeys);
|
|
3676
|
-
const seen = new Set();
|
|
3677
|
-
const lines = content ? content.split(/\r?\n/) : [];
|
|
3678
|
-
const updatedLines = lines.map((line) => {
|
|
3679
|
-
const parsed = parseEnvLine(line);
|
|
3680
|
-
if (!parsed)
|
|
3681
|
-
return line;
|
|
3682
|
-
if (removals.has(parsed.key) && !keys.has(parsed.key))
|
|
3683
|
-
return undefined;
|
|
3684
|
-
if (!keys.has(parsed.key))
|
|
3685
|
-
return line;
|
|
3686
|
-
seen.add(parsed.key);
|
|
3687
|
-
const value = updates[parsed.key];
|
|
3688
|
-
if (value === undefined)
|
|
3689
|
-
return undefined;
|
|
3690
|
-
return `${parsed.key}=${quoteEnvValue(value)}`;
|
|
3691
|
-
}).filter((line) => line !== undefined);
|
|
3692
|
-
const missing = Object.entries(updates).filter((entry) => !seen.has(entry[0]) && entry[1] !== undefined);
|
|
3693
|
-
if (missing.length > 0) {
|
|
3694
|
-
const grouped = groupLoginEnvEntries(missing);
|
|
3695
|
-
appendEnvGroup(updatedLines, "# Neo active provider", grouped.active);
|
|
3696
|
-
appendEnvGroup(updatedLines, "# OpenAI provider settings", grouped.openai);
|
|
3697
|
-
appendEnvGroup(updatedLines, "# Anthropic provider settings", grouped.anthropic);
|
|
3698
|
-
appendEnvGroup(updatedLines, "# Shared model runtime settings", grouped.shared);
|
|
3699
|
-
}
|
|
3700
|
-
return `${updatedLines.join("\n").replace(/\n*$/u, "")}\n`;
|
|
3701
|
-
}
|
|
3702
|
-
function groupLoginEnvEntries(entries) {
|
|
3703
|
-
return {
|
|
3704
|
-
active: entries.filter(([key]) => key === "MODEL_PROVIDER"),
|
|
3705
|
-
openai: entries.filter(([key]) => key.startsWith("OPENAI_")),
|
|
3706
|
-
anthropic: entries.filter(([key]) => key.startsWith("ANTHROPIC_")),
|
|
3707
|
-
shared: entries.filter(([key]) => key.startsWith("MODEL_") && key !== "MODEL_PROVIDER"),
|
|
3708
|
-
};
|
|
3709
|
-
}
|
|
3710
|
-
function appendEnvGroup(lines, header, entries) {
|
|
3711
|
-
if (entries.length === 0)
|
|
3712
|
-
return;
|
|
3713
|
-
if (lines.length > 0 && lines[lines.length - 1]?.trim())
|
|
3714
|
-
lines.push("");
|
|
3715
|
-
lines.push(header);
|
|
3716
|
-
for (const [key, value] of entries)
|
|
3717
|
-
lines.push(`${key}=${quoteEnvValue(value)}`);
|
|
3718
|
-
}
|
|
3719
|
-
function parseEnvFileSafe(envPath) {
|
|
3720
|
-
if (!existsSync(envPath))
|
|
3721
|
-
return {};
|
|
3722
|
-
const env = {};
|
|
3723
|
-
for (const line of readFileSync(envPath, "utf8").split(/\r?\n/)) {
|
|
3724
|
-
const parsed = parseEnvLine(line);
|
|
3725
|
-
if (parsed)
|
|
3726
|
-
env[parsed.key] = stripEnvQuotes(parsed.value.trim());
|
|
3727
|
-
}
|
|
3728
|
-
return env;
|
|
3729
|
-
}
|
|
3730
|
-
function parseEnvLine(line) {
|
|
3731
|
-
const trimmed = line.trim();
|
|
3732
|
-
if (!trimmed || trimmed.startsWith("#"))
|
|
3733
|
-
return undefined;
|
|
3734
|
-
const separator = trimmed.indexOf("=");
|
|
3735
|
-
if (separator <= 0)
|
|
3736
|
-
return undefined;
|
|
3737
|
-
const key = trimmed.slice(0, separator).trim();
|
|
3738
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
3739
|
-
return undefined;
|
|
3740
|
-
return { key, value: trimmed.slice(separator + 1) };
|
|
3741
|
-
}
|
|
3742
|
-
function quoteEnvValue(value) {
|
|
3743
|
-
if (/^[A-Za-z0-9_./:@+-]*$/.test(value))
|
|
3744
|
-
return value;
|
|
3745
|
-
return JSON.stringify(value);
|
|
3746
|
-
}
|
|
3747
|
-
function stripEnvQuotes(value) {
|
|
3748
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
3749
|
-
return value.slice(1, -1);
|
|
3750
|
-
return value;
|
|
3751
|
-
}
|
|
3752
|
-
function formatSessionBrowserRow(session, absoluteIndex, width, running = false) {
|
|
3753
|
-
const numberPrefix = `${absoluteIndex + 1}.`.padStart(4);
|
|
3754
|
-
const title = session.title?.trim() || "(untitled)";
|
|
3755
|
-
const runningTag = running ? " · running" : "";
|
|
3756
|
-
const updated = session.updatedAt ? ` · ${formatSessionTimestamp(session.updatedAt)}` : "";
|
|
3757
|
-
const messages = ` · ${session.messages} messages`;
|
|
3758
|
-
const fixedParts = `${numberPrefix} ${runningTag}${updated}${messages}`;
|
|
3759
|
-
const idBudget = Math.max(12, Math.min(32, Math.floor(width * 0.28)));
|
|
3760
|
-
const id = truncateMiddle(session.sessionId, idBudget);
|
|
3761
|
-
const titleBudget = Math.max(8, width - fixedParts.length - id.length - 5);
|
|
3762
|
-
const row = fitToWidth(`${numberPrefix} ${truncateMiddle(title, titleBudget)} · ${id}${runningTag}${updated}${messages}`, width);
|
|
3763
|
-
return { numberPrefix, rest: row.slice(numberPrefix.length) };
|
|
3764
|
-
}
|
|
3765
|
-
function formatSessionTimestamp(value) {
|
|
3766
|
-
const date = new Date(value);
|
|
3767
|
-
if (Number.isNaN(date.getTime()))
|
|
3768
|
-
return value;
|
|
3769
|
-
return date.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z");
|
|
3770
|
-
}
|
|
3771
|
-
function formatResume(snapshot) {
|
|
3772
|
-
return `resumed session ${snapshot.sessionId}: ${snapshot.resumedMessages} messages from ${snapshot.transcriptPath}`;
|
|
3773
|
-
}
|
|
3774
1924
|
function formatUsageTotals(totals) {
|
|
3775
1925
|
if (totals.requests === 0)
|
|
3776
1926
|
return "No token usage recorded for this REPL session yet.";
|
|
@@ -3788,6 +1938,9 @@ function formatUsageTotals(totals) {
|
|
|
3788
1938
|
lines.push(` Cached input tokens: ${formatNumber(totals.cachedTokens)}`);
|
|
3789
1939
|
return lines.join("\n");
|
|
3790
1940
|
}
|
|
1941
|
+
function formatNumber(value) {
|
|
1942
|
+
return value === undefined ? "?" : new Intl.NumberFormat("en-US").format(Math.round(value));
|
|
1943
|
+
}
|
|
3791
1944
|
function formatManualCompaction(result) {
|
|
3792
1945
|
if (!result.changed)
|
|
3793
1946
|
return "No earlier context available to compact.";
|
|
@@ -3798,937 +1951,6 @@ function formatPureCompaction(result) {
|
|
|
3798
1951
|
return "No context available to purify.";
|
|
3799
1952
|
return `pure context compacted: ${result.messages.length} sanitized message(s) retained, ${formatNumber(result.charsFreed ?? result.tokensFreed ?? 0)} chars removed; raw command/log/code details omitted`;
|
|
3800
1953
|
}
|
|
3801
|
-
function colorForKind(kind) {
|
|
3802
|
-
if (kind === "user")
|
|
3803
|
-
return "cyan";
|
|
3804
|
-
if (kind === "assistant")
|
|
3805
|
-
return "green";
|
|
3806
|
-
if (kind === "thinking")
|
|
3807
|
-
return THINKING_COLOR;
|
|
3808
|
-
if (kind === "tool")
|
|
3809
|
-
return "#d4b04c";
|
|
3810
|
-
if (kind === "error")
|
|
3811
|
-
return "red";
|
|
3812
|
-
if (kind === "meta")
|
|
3813
|
-
return "gray";
|
|
3814
|
-
return "white";
|
|
3815
|
-
}
|
|
3816
|
-
function markerColorForKind(kind) {
|
|
3817
|
-
if (kind === "thinking")
|
|
3818
|
-
return THINKING_COLOR;
|
|
3819
|
-
return colorForKind(kind);
|
|
3820
|
-
}
|
|
3821
|
-
function messageRoleMarker(kind) {
|
|
3822
|
-
if (kind === "thinking")
|
|
3823
|
-
return `${THINKING_MARKER} `;
|
|
3824
|
-
return "● ";
|
|
3825
|
-
}
|
|
3826
|
-
function kindForRole(role) {
|
|
3827
|
-
if (role === "user")
|
|
3828
|
-
return "user";
|
|
3829
|
-
if (role === "assistant")
|
|
3830
|
-
return "assistant";
|
|
3831
|
-
if (role === "tool_result")
|
|
3832
|
-
return "tool";
|
|
3833
|
-
if (role === "progress")
|
|
3834
|
-
return "meta";
|
|
3835
|
-
if (role === "system")
|
|
3836
|
-
return "meta";
|
|
3837
|
-
return "system";
|
|
3838
|
-
}
|
|
3839
|
-
function titleForKind(kind) {
|
|
3840
|
-
if (kind === "thinking")
|
|
3841
|
-
return `${THINKING_MARKER} think`;
|
|
3842
|
-
if (kind === "tool")
|
|
3843
|
-
return "Tool";
|
|
3844
|
-
if (kind === "error")
|
|
3845
|
-
return "Error";
|
|
3846
|
-
if (kind === "meta")
|
|
3847
|
-
return "Meta";
|
|
3848
|
-
if (kind === "system")
|
|
3849
|
-
return "System";
|
|
3850
|
-
if (kind === "user")
|
|
3851
|
-
return "User";
|
|
3852
|
-
return "Assistant";
|
|
3853
|
-
}
|
|
3854
|
-
function titleForRole(role) {
|
|
3855
|
-
if (role === "progress")
|
|
3856
|
-
return "Meta";
|
|
3857
|
-
if (role === "system")
|
|
3858
|
-
return "System";
|
|
3859
|
-
if (role === "tool_result")
|
|
3860
|
-
return "Tool result";
|
|
3861
|
-
return titleForKind(kindForRole(role));
|
|
3862
|
-
}
|
|
3863
|
-
function systemLine(text, summaryMaxLines) {
|
|
3864
|
-
return {
|
|
3865
|
-
kind: "system",
|
|
3866
|
-
title: "System",
|
|
3867
|
-
text,
|
|
3868
|
-
previewStyle: "summary",
|
|
3869
|
-
summaryMaxLines,
|
|
3870
|
-
};
|
|
3871
|
-
}
|
|
3872
|
-
function thinkingLine(text, live = false) {
|
|
3873
|
-
return {
|
|
3874
|
-
kind: "thinking",
|
|
3875
|
-
title: titleForKind("thinking"),
|
|
3876
|
-
text,
|
|
3877
|
-
previewStyle: "summary",
|
|
3878
|
-
summaryMaxLines: THINKING_SUMMARY_MAX_LINES,
|
|
3879
|
-
live,
|
|
3880
|
-
};
|
|
3881
|
-
}
|
|
3882
|
-
function metaLine(text) {
|
|
3883
|
-
return {
|
|
3884
|
-
kind: "meta",
|
|
3885
|
-
title: "Meta",
|
|
3886
|
-
text,
|
|
3887
|
-
previewStyle: "summary",
|
|
3888
|
-
};
|
|
3889
|
-
}
|
|
3890
|
-
function formatToolUse(toolUse) {
|
|
3891
|
-
if (toolUse.name === "plan" && isPlanToolPayload(toolUse.input)) {
|
|
3892
|
-
return {
|
|
3893
|
-
kind: "tool",
|
|
3894
|
-
title: toolTitle(toolUse.name, "running"),
|
|
3895
|
-
bodyTitle: planToolBodyTitle(toolUse.input),
|
|
3896
|
-
text: formatPlanToolPayload(toolUse.input),
|
|
3897
|
-
};
|
|
3898
|
-
}
|
|
3899
|
-
const description = toolUse.name === "exec" ? execDescriptionFromInput(toolUse.input) : undefined;
|
|
3900
|
-
return {
|
|
3901
|
-
kind: "tool",
|
|
3902
|
-
title: toolTitle(toolUse.name, "running"),
|
|
3903
|
-
bodyTitle: description,
|
|
3904
|
-
text: formatJson(toolUse.input, 1200),
|
|
3905
|
-
previewStyle: "summary",
|
|
3906
|
-
};
|
|
3907
|
-
}
|
|
3908
|
-
function formatToolResultLine(toolName, output, ok) {
|
|
3909
|
-
const formatted = formatToolResult(toolName, output, ok);
|
|
3910
|
-
const line = {
|
|
3911
|
-
kind: ok ? "tool" : "error",
|
|
3912
|
-
title: toolTitle(toolName, "finished"),
|
|
3913
|
-
bodyTitle: formatted.bodyTitle,
|
|
3914
|
-
titleStatus: ok ? "success" : "failure",
|
|
3915
|
-
text: formatted.text,
|
|
3916
|
-
format: formatted.format,
|
|
3917
|
-
live: false,
|
|
3918
|
-
};
|
|
3919
|
-
if (formatted.summaryMaxLines !== undefined) {
|
|
3920
|
-
line.previewStyle = "summary";
|
|
3921
|
-
line.summaryMaxLines = formatted.summaryMaxLines;
|
|
3922
|
-
}
|
|
3923
|
-
else if (!formatted.full) {
|
|
3924
|
-
line.previewStyle = "summary";
|
|
3925
|
-
}
|
|
3926
|
-
return line;
|
|
3927
|
-
}
|
|
3928
|
-
function formatToolFinishedWithoutResult(toolUse, ok) {
|
|
3929
|
-
const inputText = formatJson(toolUse.input, 1200);
|
|
3930
|
-
const description = toolUse.name === "exec" ? execDescriptionFromInput(toolUse.input) : undefined;
|
|
3931
|
-
return {
|
|
3932
|
-
kind: ok ? "tool" : "error",
|
|
3933
|
-
title: toolTitle(toolUse.name, "finished"),
|
|
3934
|
-
bodyTitle: description,
|
|
3935
|
-
titleStatus: ok ? "success" : "failure",
|
|
3936
|
-
text: inputText ? `${ok ? "finished" : "failed"}\n${inputText}` : ok ? "finished" : "failed",
|
|
3937
|
-
previewStyle: "summary",
|
|
3938
|
-
live: true,
|
|
3939
|
-
pendingReplacement: true,
|
|
3940
|
-
};
|
|
3941
|
-
}
|
|
3942
|
-
function toolTitle(toolName, phase) {
|
|
3943
|
-
if (toolName === "plan")
|
|
3944
|
-
return `${phase === "running" ? "◇" : "◆"} plan`;
|
|
3945
|
-
return `${phase === "running" ? "◇" : "◆"} ${toolName}`;
|
|
3946
|
-
}
|
|
3947
|
-
function execDescriptionFromInput(input) {
|
|
3948
|
-
if (!isRecord(input))
|
|
3949
|
-
return undefined;
|
|
3950
|
-
const description = typeof input.description === "string" ? input.description.trim() : "";
|
|
3951
|
-
return description || undefined;
|
|
3952
|
-
}
|
|
3953
|
-
function isPlanToolPayload(value) {
|
|
3954
|
-
if (!isRecord(value) || !Array.isArray(value.items))
|
|
3955
|
-
return false;
|
|
3956
|
-
return value.items.every(isPlanItemLike);
|
|
3957
|
-
}
|
|
3958
|
-
function isPlanItemLike(item) {
|
|
3959
|
-
if (!isRecord(item))
|
|
3960
|
-
return false;
|
|
3961
|
-
if (typeof item.description !== "string")
|
|
3962
|
-
return false;
|
|
3963
|
-
if (item.status !== "pending" && item.status !== "in_progress" && item.status !== "completed")
|
|
3964
|
-
return false;
|
|
3965
|
-
if (item.subitems === undefined)
|
|
3966
|
-
return true;
|
|
3967
|
-
return Array.isArray(item.subitems) && item.subitems.every(isPlanItemLike);
|
|
3968
|
-
}
|
|
3969
|
-
function planToolBodyTitle(payload) {
|
|
3970
|
-
const title = payload.title?.trim();
|
|
3971
|
-
return title ? title : undefined;
|
|
3972
|
-
}
|
|
3973
|
-
function formatPlanToolPayload(payload) {
|
|
3974
|
-
const sections = [];
|
|
3975
|
-
if (payload.summary?.trim())
|
|
3976
|
-
sections.push(payload.summary.trim());
|
|
3977
|
-
if (payload.note?.trim())
|
|
3978
|
-
sections.push(payload.note.trim());
|
|
3979
|
-
sections.push(payload.items.flatMap((item) => formatPlanItem(item)).join("\n"));
|
|
3980
|
-
return sections.filter(Boolean).join("\n");
|
|
3981
|
-
}
|
|
3982
|
-
function formatPlanItem(item, depth = 0) {
|
|
3983
|
-
const indent = " ".repeat(Math.max(0, depth));
|
|
3984
|
-
const text = escapePlanMarkdown(item.description.trim());
|
|
3985
|
-
const marker = planItemMarker(item.status);
|
|
3986
|
-
const line = item.status === "completed"
|
|
3987
|
-
? `${indent}- ${marker} ~~${text}~~`
|
|
3988
|
-
: `${indent}- ${marker} ${text}`;
|
|
3989
|
-
const subitems = item.subitems?.flatMap((subitem) => formatPlanItem(subitem, depth + 1)) ?? [];
|
|
3990
|
-
return [line, ...subitems];
|
|
3991
|
-
}
|
|
3992
|
-
function planItemMarker(status) {
|
|
3993
|
-
if (status === "completed")
|
|
3994
|
-
return "✓";
|
|
3995
|
-
if (status === "in_progress")
|
|
3996
|
-
return "▶";
|
|
3997
|
-
return "○";
|
|
3998
|
-
}
|
|
3999
|
-
function escapePlanMarkdown(text) {
|
|
4000
|
-
return text.replace(/([\\`*_{}[\]()#+.!|>~-])/g, "\\$1");
|
|
4001
|
-
}
|
|
4002
|
-
function formatJson(value, maxLength) {
|
|
4003
|
-
return formatReplData(value, maxLength);
|
|
4004
|
-
}
|
|
4005
|
-
function formatReplData(value, maxLength) {
|
|
4006
|
-
return truncate(formatReplValue(value), maxLength);
|
|
4007
|
-
}
|
|
4008
|
-
function formatReplValue(value, indent = 0, seen = new WeakSet()) {
|
|
4009
|
-
if (typeof value === "string")
|
|
4010
|
-
return value;
|
|
4011
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint")
|
|
4012
|
-
return String(value);
|
|
4013
|
-
if (value === undefined)
|
|
4014
|
-
return "undefined";
|
|
4015
|
-
if (typeof value === "function")
|
|
4016
|
-
return `[Function${value.name ? `: ${value.name}` : ""}]`;
|
|
4017
|
-
if (typeof value === "symbol")
|
|
4018
|
-
return value.toString();
|
|
4019
|
-
if (value instanceof Date)
|
|
4020
|
-
return value.toISOString();
|
|
4021
|
-
if (value instanceof Error)
|
|
4022
|
-
return formatReplObject({ name: value.name, message: value.message, stack: value.stack }, indent, seen);
|
|
4023
|
-
if (Array.isArray(value))
|
|
4024
|
-
return formatReplArray(value, indent, seen);
|
|
4025
|
-
if (isRecord(value))
|
|
4026
|
-
return formatReplObject(value, indent, seen);
|
|
4027
|
-
return String(value);
|
|
4028
|
-
}
|
|
4029
|
-
function formatReplArray(value, indent, seen) {
|
|
4030
|
-
if (value.length === 0)
|
|
4031
|
-
return "[]";
|
|
4032
|
-
if (seen.has(value))
|
|
4033
|
-
return "[Circular]";
|
|
4034
|
-
seen.add(value);
|
|
4035
|
-
const pad = " ".repeat(indent);
|
|
4036
|
-
const childIndent = indent + 2;
|
|
4037
|
-
const lines = value.map((item) => {
|
|
4038
|
-
if (isReplScalar(item))
|
|
4039
|
-
return `${pad}- ${formatReplValue(item, childIndent, seen)}`;
|
|
4040
|
-
const formatted = formatReplValue(item, childIndent, seen);
|
|
4041
|
-
return `${pad}-\n${formatted}`;
|
|
4042
|
-
});
|
|
4043
|
-
seen.delete(value);
|
|
4044
|
-
return lines.join("\n");
|
|
4045
|
-
}
|
|
4046
|
-
function formatReplObject(value, indent, seen) {
|
|
4047
|
-
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined);
|
|
4048
|
-
if (entries.length === 0)
|
|
4049
|
-
return "{}";
|
|
4050
|
-
if (seen.has(value))
|
|
4051
|
-
return "[Circular]";
|
|
4052
|
-
seen.add(value);
|
|
4053
|
-
const pad = " ".repeat(indent);
|
|
4054
|
-
const childIndent = indent + 2;
|
|
4055
|
-
const lines = entries.map(([key, entryValue]) => {
|
|
4056
|
-
const label = `${pad}${key}:`;
|
|
4057
|
-
if (isReplScalar(entryValue))
|
|
4058
|
-
return `${label} ${formatReplValue(entryValue, childIndent, seen)}`;
|
|
4059
|
-
const formatted = formatReplValue(entryValue, childIndent, seen);
|
|
4060
|
-
if (formatted === "[]" || formatted === "{}" || formatted === "[Circular]")
|
|
4061
|
-
return `${label} ${formatted}`;
|
|
4062
|
-
return `${label}\n${formatted}`;
|
|
4063
|
-
});
|
|
4064
|
-
seen.delete(value);
|
|
4065
|
-
return lines.join("\n");
|
|
4066
|
-
}
|
|
4067
|
-
function isReplScalar(value) {
|
|
4068
|
-
return value === null || value === undefined || typeof value !== "object" || value instanceof Date;
|
|
4069
|
-
}
|
|
4070
|
-
function formatToolResult(toolName, output, ok) {
|
|
4071
|
-
if ((toolName === "edit" || toolName === "write") && isRecord(output) && isEditToolOutput(output)) {
|
|
4072
|
-
return { text: formatEditToolDiff(output, ok), format: "ansi", summaryMaxLines: EDIT_TOOL_SUMMARY_MAX_LINES };
|
|
4073
|
-
}
|
|
4074
|
-
if (isExecOutput(output)) {
|
|
4075
|
-
return { text: formatExecToolResult(output, ok), format: "ansi", summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
4076
|
-
}
|
|
4077
|
-
if (typeof output === "string" && hasAnsi(output)) {
|
|
4078
|
-
return { text: output, format: "ansi" };
|
|
4079
|
-
}
|
|
4080
|
-
if (toolName === "list" && isRecord(output)) {
|
|
4081
|
-
return { text: formatListToolResult(output, ok) };
|
|
4082
|
-
}
|
|
4083
|
-
if (toolName === "read" && isRecord(output)) {
|
|
4084
|
-
return { text: formatReadToolResult(output, ok) };
|
|
4085
|
-
}
|
|
4086
|
-
if (toolName === "grep" && isRecord(output)) {
|
|
4087
|
-
return { text: formatGrepToolResult(output, ok) };
|
|
4088
|
-
}
|
|
4089
|
-
if (toolName === "search" && isRecord(output)) {
|
|
4090
|
-
return { text: formatWebSearchToolResult(output, ok), summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
4091
|
-
}
|
|
4092
|
-
if (toolName === "image2" && isRecord(output)) {
|
|
4093
|
-
return { text: formatImageGenerationToolResult(output, ok), summaryMaxLines: 4 };
|
|
4094
|
-
}
|
|
4095
|
-
if (toolName === "plan" && isPlanToolPayload(output)) {
|
|
4096
|
-
return { text: formatPlanToolPayload(output), bodyTitle: planToolBodyTitle(output), full: true };
|
|
4097
|
-
}
|
|
4098
|
-
return { text: `${ok ? "ok" : "failed"}\n${formatJson(output, 6000)}`, summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
4099
|
-
}
|
|
4100
|
-
function isEditToolOutput(value) {
|
|
4101
|
-
return (typeof value.path === "string" &&
|
|
4102
|
-
typeof value.operation === "string" &&
|
|
4103
|
-
typeof value.replacements === "number" &&
|
|
4104
|
-
Array.isArray(value.patch) &&
|
|
4105
|
-
value.patch.every(isEditPatchHunk));
|
|
4106
|
-
}
|
|
4107
|
-
function isEditPatchHunk(value) {
|
|
4108
|
-
if (!isRecord(value))
|
|
4109
|
-
return false;
|
|
4110
|
-
return (typeof value.oldStart === "number" &&
|
|
4111
|
-
typeof value.oldLines === "number" &&
|
|
4112
|
-
typeof value.newStart === "number" &&
|
|
4113
|
-
typeof value.newLines === "number" &&
|
|
4114
|
-
Array.isArray(value.lines) &&
|
|
4115
|
-
value.lines.every((line) => typeof line === "string"));
|
|
4116
|
-
}
|
|
4117
|
-
function formatEditToolDiff(output, ok) {
|
|
4118
|
-
const lines = [
|
|
4119
|
-
dimAnsi(`${ok ? output.operation : "failed"} ${output.path}, ${output.replacements} replacement(s)`),
|
|
4120
|
-
`\x1b[2;31m--- ${output.path}\x1b[0m`,
|
|
4121
|
-
`\x1b[2;32m+++ ${output.path}\x1b[0m`,
|
|
4122
|
-
];
|
|
4123
|
-
for (const hunk of output.patch) {
|
|
4124
|
-
lines.push(colorizeDiffLine(`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`));
|
|
4125
|
-
lines.push(...formatEditPatchHunkLines(hunk));
|
|
4126
|
-
}
|
|
4127
|
-
if (output.patch.length === 0)
|
|
4128
|
-
lines.push(dimAnsi("no changes"));
|
|
4129
|
-
return lines.join("\n");
|
|
4130
|
-
}
|
|
4131
|
-
function formatEditPatchHunkLines(hunk) {
|
|
4132
|
-
const oldLineWidth = diffLineNumberWidth(hunk.oldStart, hunk.oldLines);
|
|
4133
|
-
const newLineWidth = diffLineNumberWidth(hunk.newStart, hunk.newLines);
|
|
4134
|
-
let oldLineNumber = hunk.oldStart;
|
|
4135
|
-
let newLineNumber = hunk.newStart;
|
|
4136
|
-
return hunk.lines.map((rawLine) => {
|
|
4137
|
-
const marker = diffLineMarker(rawLine);
|
|
4138
|
-
if (!marker)
|
|
4139
|
-
return rawLine;
|
|
4140
|
-
const showOldLineNumber = marker !== "+";
|
|
4141
|
-
const showNewLineNumber = marker !== "-";
|
|
4142
|
-
const oldLineLabel = showOldLineNumber ? String(oldLineNumber).padStart(oldLineWidth) : " ".repeat(oldLineWidth);
|
|
4143
|
-
const newLineLabel = showNewLineNumber ? String(newLineNumber).padStart(newLineWidth) : " ".repeat(newLineWidth);
|
|
4144
|
-
const line = `${oldLineLabel} ${newLineLabel} │ ${marker}${rawLine.slice(1)}`;
|
|
4145
|
-
if (showOldLineNumber)
|
|
4146
|
-
oldLineNumber += 1;
|
|
4147
|
-
if (showNewLineNumber)
|
|
4148
|
-
newLineNumber += 1;
|
|
4149
|
-
return colorizeDiffLine(line, marker);
|
|
4150
|
-
});
|
|
4151
|
-
}
|
|
4152
|
-
function diffLineNumberWidth(start, lineCount) {
|
|
4153
|
-
const end = lineCount > 0 ? start + lineCount - 1 : start;
|
|
4154
|
-
return Math.max(String(start).length, String(end).length, 2);
|
|
4155
|
-
}
|
|
4156
|
-
function diffLineMarker(line) {
|
|
4157
|
-
const marker = line[0];
|
|
4158
|
-
if (marker === "+" || marker === "-" || marker === " ")
|
|
4159
|
-
return marker;
|
|
4160
|
-
return undefined;
|
|
4161
|
-
}
|
|
4162
|
-
function colorizeDiffLine(line, marker) {
|
|
4163
|
-
if (marker === "+" || (!marker && line.startsWith("+")))
|
|
4164
|
-
return `\x1b[2;32m${line}\x1b[0m`;
|
|
4165
|
-
if (marker === "-" || (!marker && line.startsWith("-")))
|
|
4166
|
-
return `\x1b[2;31m${line}\x1b[0m`;
|
|
4167
|
-
if (line.startsWith("@@"))
|
|
4168
|
-
return `\x1b[2;36m${line}\x1b[0m`;
|
|
4169
|
-
return dimAnsi(line);
|
|
4170
|
-
}
|
|
4171
|
-
function dimAnsi(line) {
|
|
4172
|
-
return `\x1b[2m${line}\x1b[0m`;
|
|
4173
|
-
}
|
|
4174
|
-
function isExecOutput(value) {
|
|
4175
|
-
if (!value || typeof value !== "object")
|
|
4176
|
-
return false;
|
|
4177
|
-
const record = value;
|
|
4178
|
-
return (typeof record.command === "string" &&
|
|
4179
|
-
(typeof record.exitCode === "number" || record.exitCode === null) &&
|
|
4180
|
-
typeof record.timedOut === "boolean" &&
|
|
4181
|
-
typeof record.durationMs === "number" &&
|
|
4182
|
-
typeof record.stdout === "string" &&
|
|
4183
|
-
typeof record.stderr === "string");
|
|
4184
|
-
}
|
|
4185
|
-
function formatExecToolResult(output, ok) {
|
|
4186
|
-
const status = output.timedOut
|
|
4187
|
-
? "timed out"
|
|
4188
|
-
: output.exitCode === 0
|
|
4189
|
-
? "exit 0"
|
|
4190
|
-
: `exit ${output.exitCode ?? output.signal ?? "unknown"}`;
|
|
4191
|
-
const description = typeof output.description === "string" ? output.description.trim() : "";
|
|
4192
|
-
const lines = [
|
|
4193
|
-
"exec result",
|
|
4194
|
-
...(description ? [`purpose: ${description}`] : []),
|
|
4195
|
-
`status: ${status}`,
|
|
4196
|
-
`duration: ${output.durationMs}ms`,
|
|
4197
|
-
`command: ${output.command}`,
|
|
4198
|
-
];
|
|
4199
|
-
const stdout = output.stdout.replace(/\s+$/u, "");
|
|
4200
|
-
const stderr = output.stderr.replace(/\s+$/u, "");
|
|
4201
|
-
if (stdout)
|
|
4202
|
-
lines.push("stdout:", stdout);
|
|
4203
|
-
if (stderr)
|
|
4204
|
-
lines.push("stderr:", stderr);
|
|
4205
|
-
if (!stdout && !stderr)
|
|
4206
|
-
lines.push(ok ? "output: (none)" : "output: (not captured)");
|
|
4207
|
-
return lines.join("\n");
|
|
4208
|
-
}
|
|
4209
|
-
function isRecord(value) {
|
|
4210
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4211
|
-
}
|
|
4212
|
-
function formatImageGenerationToolResult(output, ok) {
|
|
4213
|
-
const error = typeof output.error === "string" ? output.error : undefined;
|
|
4214
|
-
const mode = output.mode === "edit" ? "edit" : "generate";
|
|
4215
|
-
if (!ok || error)
|
|
4216
|
-
return [`image ${mode} failed`, error ?? formatReplData(output, 1200)].join("\n");
|
|
4217
|
-
const provider = typeof output.provider === "string" ? output.provider : "openai";
|
|
4218
|
-
const model = typeof output.model === "string" ? output.model : undefined;
|
|
4219
|
-
const returnedImages = typeof output.returnedImages === "number" ? output.returnedImages : Array.isArray(output.images) ? output.images.length : undefined;
|
|
4220
|
-
const size = typeof output.size === "string" ? output.size : undefined;
|
|
4221
|
-
const quality = typeof output.quality === "string" ? output.quality : undefined;
|
|
4222
|
-
const format = typeof output.outputFormat === "string" ? output.outputFormat : undefined;
|
|
4223
|
-
const sourceImages = typeof output.sourceImages === "number" ? output.sourceImages : undefined;
|
|
4224
|
-
const lines = [`${mode === "edit" ? "edited" : "generated"} ${returnedImages ?? 0} image${returnedImages === 1 ? "" : "s"}`];
|
|
4225
|
-
const details = [provider, model, size, quality && quality !== "auto" ? quality : undefined, format].filter((value) => Boolean(value));
|
|
4226
|
-
if (details.length > 0)
|
|
4227
|
-
lines.push(details.join(" · "));
|
|
4228
|
-
if (sourceImages !== undefined)
|
|
4229
|
-
lines.push(`source images: ${sourceImages}`);
|
|
4230
|
-
const duration = imageGenerationDuration(output);
|
|
4231
|
-
if (duration !== undefined)
|
|
4232
|
-
lines.push(`duration: ${duration}ms`);
|
|
4233
|
-
return lines.join("\n");
|
|
4234
|
-
}
|
|
4235
|
-
function imageGenerationDuration(output) {
|
|
4236
|
-
const value = output.duration ?? output.elapsed ?? output.durationMs ?? output.elapsedMs;
|
|
4237
|
-
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.round(value)) : undefined;
|
|
4238
|
-
}
|
|
4239
|
-
function formatListToolResult(output, ok) {
|
|
4240
|
-
const pathValue = typeof output.path === "string" ? output.path : "";
|
|
4241
|
-
const typeValue = typeof output.type === "string" ? output.type : "result";
|
|
4242
|
-
const returnedEntries = typeof output.returnedEntries === "number" ? output.returnedEntries : undefined;
|
|
4243
|
-
const totalFiles = typeof output.totalFiles === "number" ? output.totalFiles : undefined;
|
|
4244
|
-
const totalDirectories = typeof output.totalDirectories === "number" ? output.totalDirectories : undefined;
|
|
4245
|
-
const entries = Array.isArray(output.entries) ? output.entries : [];
|
|
4246
|
-
const names = entries
|
|
4247
|
-
.map((entry) => (isRecord(entry) && typeof entry.name === "string" ? entry.name : undefined))
|
|
4248
|
-
.filter((name) => Boolean(name))
|
|
4249
|
-
.slice(0, 3);
|
|
4250
|
-
const lines = [ok ? typeValue : "failed"];
|
|
4251
|
-
if (pathValue)
|
|
4252
|
-
lines.push(pathValue);
|
|
4253
|
-
const counts = [
|
|
4254
|
-
returnedEntries !== undefined ? `${returnedEntries} shown` : undefined,
|
|
4255
|
-
totalFiles !== undefined ? `${totalFiles} files` : undefined,
|
|
4256
|
-
totalDirectories !== undefined ? `${totalDirectories} dirs` : undefined,
|
|
4257
|
-
].filter((value) => Boolean(value));
|
|
4258
|
-
if (counts.length > 0)
|
|
4259
|
-
lines.push(counts.join(" · "));
|
|
4260
|
-
for (const name of names)
|
|
4261
|
-
lines.push(name);
|
|
4262
|
-
return lines.join("\n");
|
|
4263
|
-
}
|
|
4264
|
-
function formatReadToolResult(output, ok) {
|
|
4265
|
-
const error = typeof output.error === "string" ? output.error : undefined;
|
|
4266
|
-
if (!ok || error)
|
|
4267
|
-
return ["failed", error ?? formatJson(output, 1200)].join("\n");
|
|
4268
|
-
const pathValue = typeof output.path === "string" ? output.path : undefined;
|
|
4269
|
-
const startLine = typeof output.startLine === "number" ? output.startLine : undefined;
|
|
4270
|
-
const endLine = typeof output.endLine === "number" ? output.endLine : undefined;
|
|
4271
|
-
const totalLines = typeof output.totalLines === "number" ? output.totalLines : undefined;
|
|
4272
|
-
const hasMoreBefore = output.hasMoreBefore === true;
|
|
4273
|
-
const hasMoreAfter = output.hasMoreAfter === true;
|
|
4274
|
-
const content = typeof output.content === "string" ? output.content.trimEnd() : "";
|
|
4275
|
-
const lines = ["read result"];
|
|
4276
|
-
if (pathValue)
|
|
4277
|
-
lines.push(`file: ${pathValue}`);
|
|
4278
|
-
if (startLine !== undefined && endLine !== undefined && totalLines !== undefined) {
|
|
4279
|
-
const more = [hasMoreBefore ? "more before" : undefined, hasMoreAfter ? "more after" : undefined]
|
|
4280
|
-
.filter((value) => Boolean(value))
|
|
4281
|
-
.join(", ");
|
|
4282
|
-
lines.push(`range: lines ${startLine}-${endLine} of ${totalLines}${more ? ` (${more})` : ""}`);
|
|
4283
|
-
}
|
|
4284
|
-
lines.push("content:");
|
|
4285
|
-
lines.push(content || "(empty range)");
|
|
4286
|
-
return lines.join("\n");
|
|
4287
|
-
}
|
|
4288
|
-
function formatWebSearchToolResult(output, ok) {
|
|
4289
|
-
const error = typeof output.error === "string" ? output.error : undefined;
|
|
4290
|
-
if (!ok || error)
|
|
4291
|
-
return ["failed", error ?? formatJson(output, 1200)].join("\n");
|
|
4292
|
-
const provider = typeof output.provider === "string" ? output.provider : "unknown";
|
|
4293
|
-
const query = typeof output.query === "string" ? output.query : "";
|
|
4294
|
-
const returnedResults = typeof output.returnedResults === "number" ? output.returnedResults : undefined;
|
|
4295
|
-
const results = Array.isArray(output.results) ? output.results : [];
|
|
4296
|
-
const header = [`${returnedResults ?? results.length} web result(s) via ${provider}`];
|
|
4297
|
-
if (query)
|
|
4298
|
-
header.push(`query: ${query}`);
|
|
4299
|
-
if (output.truncated === true)
|
|
4300
|
-
header.push("truncated");
|
|
4301
|
-
if (results.length === 0)
|
|
4302
|
-
return [...header, "no results"].join("\n");
|
|
4303
|
-
const lines = [...header];
|
|
4304
|
-
results.slice(0, 8).forEach((item, index) => {
|
|
4305
|
-
if (!isRecord(item))
|
|
4306
|
-
return;
|
|
4307
|
-
const title = typeof item.title === "string" && item.title.trim() ? item.title.trim() : "Untitled";
|
|
4308
|
-
const url = typeof item.url === "string" ? item.url : "";
|
|
4309
|
-
const published = typeof item.published === "string" ? ` · ${item.published}` : "";
|
|
4310
|
-
lines.push(`[${index + 1}] ${title}${published}`);
|
|
4311
|
-
if (url)
|
|
4312
|
-
lines.push(url);
|
|
4313
|
-
const highlights = Array.isArray(item.highlights) ? item.highlights.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
|
|
4314
|
-
const snippet = highlights[0] ?? (typeof item.text === "string" ? item.text : undefined);
|
|
4315
|
-
if (snippet)
|
|
4316
|
-
lines.push(truncate(snippet.replace(/\s+/gu, " "), 400));
|
|
4317
|
-
});
|
|
4318
|
-
return lines.join("\n");
|
|
4319
|
-
}
|
|
4320
|
-
function formatGrepToolResult(output, ok) {
|
|
4321
|
-
const error = typeof output.error === "string" ? output.error : undefined;
|
|
4322
|
-
if (!ok || error)
|
|
4323
|
-
return ["failed", error ?? formatJson(output, 1200)].join("\n");
|
|
4324
|
-
const query = typeof output.query === "string" ? output.query : undefined;
|
|
4325
|
-
const grepPath = typeof output.grepPath === "string" ? output.grepPath : undefined;
|
|
4326
|
-
const returnedMatches = typeof output.returnedMatches === "number" ? output.returnedMatches : undefined;
|
|
4327
|
-
const totalMatchesKnown = typeof output.totalMatchesKnown === "number" ? output.totalMatchesKnown : undefined;
|
|
4328
|
-
const truncated = output.truncated === true;
|
|
4329
|
-
const matches = Array.isArray(output.matches) ? output.matches.filter(isGrepMatchLike) : [];
|
|
4330
|
-
const errors = Array.isArray(output.errors)
|
|
4331
|
-
? output.errors.filter((value) => typeof value === "string")
|
|
4332
|
-
: [];
|
|
4333
|
-
const transportTruncation = isRecord(output.transportTruncation) ? output.transportTruncation : undefined;
|
|
4334
|
-
const omittedMatches = typeof transportTruncation?.omittedMatches === "number" ? transportTruncation.omittedMatches : undefined;
|
|
4335
|
-
const lines = ["grep result"];
|
|
4336
|
-
if (query !== undefined)
|
|
4337
|
-
lines.push(`query: ${query}`);
|
|
4338
|
-
if (grepPath !== undefined)
|
|
4339
|
-
lines.push(`path: ${grepPath}`);
|
|
4340
|
-
const countParts = [
|
|
4341
|
-
`${returnedMatches ?? matches.length} shown`,
|
|
4342
|
-
totalMatchesKnown !== undefined ? `${totalMatchesKnown} known` : undefined,
|
|
4343
|
-
truncated ? "truncated" : undefined,
|
|
4344
|
-
omittedMatches !== undefined && omittedMatches > 0 ? `${omittedMatches} omitted` : undefined,
|
|
4345
|
-
].filter((value) => Boolean(value));
|
|
4346
|
-
lines.push(`matches: ${countParts.join(" · ")}`);
|
|
4347
|
-
if (errors.length > 0) {
|
|
4348
|
-
lines.push("errors:");
|
|
4349
|
-
lines.push(...errors.slice(0, 5).map((message) => ` ${message}`));
|
|
4350
|
-
if (errors.length > 5)
|
|
4351
|
-
lines.push(` ... ${errors.length - 5} more error(s)`);
|
|
4352
|
-
}
|
|
4353
|
-
if (matches.length === 0) {
|
|
4354
|
-
lines.push("no matches");
|
|
4355
|
-
return lines.join("\n");
|
|
4356
|
-
}
|
|
4357
|
-
lines.push("results:");
|
|
4358
|
-
for (const match of matches) {
|
|
4359
|
-
for (const context of match.contextBefore ?? []) {
|
|
4360
|
-
lines.push(formatGrepContextLine(context, "-"));
|
|
4361
|
-
}
|
|
4362
|
-
lines.push(formatGrepMatchLine(match));
|
|
4363
|
-
for (const context of match.contextAfter ?? []) {
|
|
4364
|
-
lines.push(formatGrepContextLine(context, "+"));
|
|
4365
|
-
}
|
|
4366
|
-
}
|
|
4367
|
-
return lines.join("\n");
|
|
4368
|
-
}
|
|
4369
|
-
function isGrepMatchLike(value) {
|
|
4370
|
-
if (!isRecord(value))
|
|
4371
|
-
return false;
|
|
4372
|
-
return (typeof value.file === "string" &&
|
|
4373
|
-
typeof value.line === "number" &&
|
|
4374
|
-
typeof value.text === "string" &&
|
|
4375
|
-
(value.column === undefined || typeof value.column === "number"));
|
|
4376
|
-
}
|
|
4377
|
-
function formatGrepMatchLine(match) {
|
|
4378
|
-
const column = match.column !== undefined ? `:${match.column}` : "";
|
|
4379
|
-
return ` ${match.file}:${match.line}${column}: ${match.text}`;
|
|
4380
|
-
}
|
|
4381
|
-
function formatGrepContextLine(line, marker) {
|
|
4382
|
-
return ` ${line.file}:${line.line}${marker} ${line.text}`;
|
|
4383
|
-
}
|
|
4384
|
-
function renderContextParts(metrics) {
|
|
4385
|
-
if (!metrics)
|
|
4386
|
-
return { percent: "?" };
|
|
4387
|
-
const percent = metrics.contextUsageRatio === undefined ? "?" : `${(metrics.contextUsageRatio * 100).toFixed(1)}%`;
|
|
4388
|
-
return { percent };
|
|
4389
|
-
}
|
|
4390
|
-
function contextColor(metrics) {
|
|
4391
|
-
const ratio = metrics?.contextUsageRatio;
|
|
4392
|
-
if (ratio === undefined)
|
|
4393
|
-
return "gray";
|
|
4394
|
-
if (ratio >= 0.9)
|
|
4395
|
-
return "red";
|
|
4396
|
-
if (ratio >= 0.75)
|
|
4397
|
-
return "yellow";
|
|
4398
|
-
return "gray";
|
|
4399
|
-
}
|
|
4400
|
-
function statusInputTokens(status) {
|
|
4401
|
-
return status.usage?.inputTokens ?? status.metrics?.estimatedInputTokens;
|
|
4402
|
-
}
|
|
4403
|
-
function statusOutputTokens(status) {
|
|
4404
|
-
return status.usage?.outputTokens ?? status.streamedOutputTokens;
|
|
4405
|
-
}
|
|
4406
|
-
function tokenArrowColor(updatedAt, now, activeColor) {
|
|
4407
|
-
return updatedAt !== undefined && now - updatedAt <= TOKEN_PULSE_MS ? activeColor : "gray";
|
|
4408
|
-
}
|
|
4409
|
-
function retryCooldownActive(status, now) {
|
|
4410
|
-
return status.retryCooldownUntil !== undefined && now < status.retryCooldownUntil;
|
|
4411
|
-
}
|
|
4412
|
-
function modelOutputPending(status, now) {
|
|
4413
|
-
if (retryCooldownActive(status, now))
|
|
4414
|
-
return true;
|
|
4415
|
-
if (status.phase !== "calling_model")
|
|
4416
|
-
return false;
|
|
4417
|
-
return tokenArrowColor(status.outputTokenUpdatedAt, now, "cyan") === "gray";
|
|
4418
|
-
}
|
|
4419
|
-
function slowBlinkVisible(tick) {
|
|
4420
|
-
return Math.floor(tick / STATUS_BLINK_TICKS) % 2 === 0;
|
|
4421
|
-
}
|
|
4422
|
-
function estimateTokens(text) {
|
|
4423
|
-
return text ? Math.max(1, Math.ceil(text.length / 4)) : 0;
|
|
4424
|
-
}
|
|
4425
|
-
function formatNumber(value) {
|
|
4426
|
-
return value === undefined ? "?" : new Intl.NumberFormat("en-US").format(Math.round(value));
|
|
4427
|
-
}
|
|
4428
|
-
function formatCompactNumber(value) {
|
|
4429
|
-
if (value === undefined)
|
|
4430
|
-
return "?";
|
|
4431
|
-
if (value >= 1_000_000)
|
|
4432
|
-
return `${Number((value / 1_000_000).toFixed(1))}M`;
|
|
4433
|
-
if (value >= 1_000)
|
|
4434
|
-
return `${Number((value / 1_000).toFixed(1))}K`;
|
|
4435
|
-
return String(Math.round(value));
|
|
4436
|
-
}
|
|
4437
|
-
function truncate(value, maxLength) {
|
|
4438
|
-
return value.length <= maxLength ? value : `${value.slice(0, maxLength - 3)}...`;
|
|
4439
|
-
}
|
|
4440
|
-
function truncateAnsi(value, maxLength) {
|
|
4441
|
-
if (stripAnsi(value).length <= maxLength)
|
|
4442
|
-
return value;
|
|
4443
|
-
if (maxLength <= 0)
|
|
4444
|
-
return "";
|
|
4445
|
-
let visibleLength = 0;
|
|
4446
|
-
let index = 0;
|
|
4447
|
-
let output = "";
|
|
4448
|
-
const ansiPattern = /\x1b\[[0-9;]*m/y;
|
|
4449
|
-
while (index < value.length && visibleLength < maxLength) {
|
|
4450
|
-
ansiPattern.lastIndex = index;
|
|
4451
|
-
const ansiMatch = ansiPattern.exec(value);
|
|
4452
|
-
if (ansiMatch) {
|
|
4453
|
-
output += ansiMatch[0];
|
|
4454
|
-
index = ansiPattern.lastIndex;
|
|
4455
|
-
continue;
|
|
4456
|
-
}
|
|
4457
|
-
const codePoint = value.codePointAt(index);
|
|
4458
|
-
if (codePoint === undefined)
|
|
4459
|
-
break;
|
|
4460
|
-
const char = String.fromCodePoint(codePoint);
|
|
4461
|
-
output += char;
|
|
4462
|
-
visibleLength += 1;
|
|
4463
|
-
index += char.length;
|
|
4464
|
-
}
|
|
4465
|
-
return hasAnsi(output) ? `${output}\x1b[0m` : output;
|
|
4466
|
-
}
|
|
4467
|
-
function phaseLabelForStatus(phase) {
|
|
4468
|
-
if (phase === "calling_model")
|
|
4469
|
-
return "model";
|
|
4470
|
-
if (phase === "thinking")
|
|
4471
|
-
return "think";
|
|
4472
|
-
if (phase === "running_tools")
|
|
4473
|
-
return "tools";
|
|
4474
|
-
if (phase === "injecting_context")
|
|
4475
|
-
return "context";
|
|
4476
|
-
return phase;
|
|
4477
|
-
}
|
|
4478
|
-
function isActivePhase(phase) {
|
|
4479
|
-
return phase === "running" ||
|
|
4480
|
-
phase === "preparing" ||
|
|
4481
|
-
phase === "calling_model" ||
|
|
4482
|
-
phase === "thinking" ||
|
|
4483
|
-
phase === "running_tools" ||
|
|
4484
|
-
phase === "compacting" ||
|
|
4485
|
-
phase === "injecting_context";
|
|
4486
|
-
}
|
|
4487
|
-
function phaseColor(phase) {
|
|
4488
|
-
if (phase === "ready")
|
|
4489
|
-
return "green";
|
|
4490
|
-
if (phase === "stopped")
|
|
4491
|
-
return "yellow";
|
|
4492
|
-
if (phase === "failed")
|
|
4493
|
-
return "red";
|
|
4494
|
-
if (phase === "thinking")
|
|
4495
|
-
return THINKING_COLOR;
|
|
4496
|
-
if (phase === "running_tools")
|
|
4497
|
-
return "#d4b04c";
|
|
4498
|
-
if (phase === "compacting" || phase === "injecting_context")
|
|
4499
|
-
return "magenta";
|
|
4500
|
-
return "cyan";
|
|
4501
|
-
}
|
|
4502
|
-
function renderPhaseStatusSegments(text, phase, animationTick) {
|
|
4503
|
-
const color = phaseColor(phase);
|
|
4504
|
-
if (!isActivePhase(phase) || text.length <= 1)
|
|
4505
|
-
return [{ text, color, bold: true }];
|
|
4506
|
-
const shimmerCenter = animationTick % (text.length + STATUS_SHIMMER_GAP_TICKS);
|
|
4507
|
-
return [...text].map((char, index) => ({
|
|
4508
|
-
text: char,
|
|
4509
|
-
color: Math.abs(index - shimmerCenter) <= STATUS_SHIMMER_RADIUS ? STATUS_SHIMMER_COLOR : color,
|
|
4510
|
-
bold: true,
|
|
4511
|
-
}));
|
|
4512
|
-
}
|
|
4513
|
-
function compactNumber(value) {
|
|
4514
|
-
if (value === undefined)
|
|
4515
|
-
return "?";
|
|
4516
|
-
const rounded = Math.max(0, Math.round(value));
|
|
4517
|
-
if (rounded >= 1_000_000)
|
|
4518
|
-
return `${trimFixed(rounded / 1_000_000)}m`;
|
|
4519
|
-
if (rounded >= 10_000)
|
|
4520
|
-
return `${Math.round(rounded / 1000)}k`;
|
|
4521
|
-
if (rounded >= 1000)
|
|
4522
|
-
return `${trimFixed(rounded / 1000)}k`;
|
|
4523
|
-
return String(rounded);
|
|
4524
|
-
}
|
|
4525
|
-
function statusDividerSegment() {
|
|
4526
|
-
return { text: STATUS_SEPARATOR, color: "gray" };
|
|
4527
|
-
}
|
|
4528
|
-
function statusLabelSegment(text, color = "gray") {
|
|
4529
|
-
return { text, color, bold: color !== "gray" };
|
|
4530
|
-
}
|
|
4531
|
-
function trimFixed(value) {
|
|
4532
|
-
return value >= 10 ? value.toFixed(0) : value.toFixed(1).replace(/\.0$/, "");
|
|
4533
|
-
}
|
|
4534
|
-
function statusBarWidth(columns) {
|
|
4535
|
-
return Math.max(1, Math.min(columns - 1, 160));
|
|
4536
|
-
}
|
|
4537
|
-
function useTerminalSize() {
|
|
4538
|
-
const [size, setSize] = useState(() => currentTerminalSize());
|
|
4539
|
-
useEffect(() => {
|
|
4540
|
-
const onResize = () => setSize(currentTerminalSize());
|
|
4541
|
-
stdout.on("resize", onResize);
|
|
4542
|
-
onResize();
|
|
4543
|
-
return () => {
|
|
4544
|
-
stdout.off("resize", onResize);
|
|
4545
|
-
};
|
|
4546
|
-
}, []);
|
|
4547
|
-
return size;
|
|
4548
|
-
}
|
|
4549
|
-
function currentTerminalSize() {
|
|
4550
|
-
return {
|
|
4551
|
-
columns: terminalColumns(),
|
|
4552
|
-
rows: terminalRows(),
|
|
4553
|
-
};
|
|
4554
|
-
}
|
|
4555
|
-
function terminalRows() {
|
|
4556
|
-
return Math.max(8, stdout.rows ?? 30);
|
|
4557
|
-
}
|
|
4558
|
-
function terminalColumns() {
|
|
4559
|
-
return Math.max(1, stdout.columns ?? 100);
|
|
4560
|
-
}
|
|
4561
|
-
function promptPrefix(busy) {
|
|
4562
|
-
return messageRoleMarker();
|
|
4563
|
-
}
|
|
4564
|
-
function promptTextView(text, cursor, terminalWidth, prompt) {
|
|
4565
|
-
const normalized = text.replace(/\r?\n/g, " ");
|
|
4566
|
-
const safeCursor = Math.max(0, Math.min(cursor, normalized.length));
|
|
4567
|
-
const prefixWidth = stringCellWidth(prompt);
|
|
4568
|
-
const firstContentWidth = Math.max(1, terminalWidth - prefixWidth);
|
|
4569
|
-
const continuationWidth = firstContentWidth;
|
|
4570
|
-
const segments = wrapPromptText(normalized, safeCursor, firstContentWidth, continuationWidth);
|
|
4571
|
-
return segments.length > 0 ? segments : [{ before: "", selected: " ", after: "" }];
|
|
4572
|
-
}
|
|
4573
|
-
function wrapPromptText(text, cursor, firstWidth, continuationWidth) {
|
|
4574
|
-
const segments = [];
|
|
4575
|
-
let start = 0;
|
|
4576
|
-
let index = 0;
|
|
4577
|
-
let width = Math.max(1, firstWidth);
|
|
4578
|
-
let used = 0;
|
|
4579
|
-
while (index < text.length) {
|
|
4580
|
-
const char = nextTextChar(text, index);
|
|
4581
|
-
const charWidth = Math.max(1, stringCellWidth(char.value));
|
|
4582
|
-
if (used > 0 && used + charWidth > width) {
|
|
4583
|
-
segments.push({ start, end: index });
|
|
4584
|
-
start = index;
|
|
4585
|
-
used = 0;
|
|
4586
|
-
width = Math.max(1, continuationWidth);
|
|
4587
|
-
continue;
|
|
4588
|
-
}
|
|
4589
|
-
used += charWidth;
|
|
4590
|
-
index = char.nextIndex;
|
|
4591
|
-
}
|
|
4592
|
-
segments.push({ start, end: text.length });
|
|
4593
|
-
const cursorSegmentIndex = segmentIndexForCursor(segments, cursor);
|
|
4594
|
-
return segments.map((segment, index) => {
|
|
4595
|
-
if (index !== cursorSegmentIndex)
|
|
4596
|
-
return { before: text.slice(segment.start, segment.end), selected: "", after: "" };
|
|
4597
|
-
const selected = cursor < segment.end ? nextTextChar(text, cursor).value : " ";
|
|
4598
|
-
const selectedEnd = cursor < segment.end ? nextTextChar(text, cursor).nextIndex : cursor;
|
|
4599
|
-
return {
|
|
4600
|
-
before: text.slice(segment.start, cursor),
|
|
4601
|
-
selected,
|
|
4602
|
-
after: text.slice(selectedEnd, segment.end),
|
|
4603
|
-
};
|
|
4604
|
-
});
|
|
4605
|
-
}
|
|
4606
|
-
function segmentIndexForCursor(segments, cursor) {
|
|
4607
|
-
for (let index = 0; index < segments.length; index += 1) {
|
|
4608
|
-
const segment = segments[index];
|
|
4609
|
-
if (!segment)
|
|
4610
|
-
continue;
|
|
4611
|
-
const isLast = index === segments.length - 1;
|
|
4612
|
-
if (cursor >= segment.start && (cursor < segment.end || isLast || segment.start === segment.end))
|
|
4613
|
-
return index;
|
|
4614
|
-
}
|
|
4615
|
-
return Math.max(0, segments.length - 1);
|
|
4616
|
-
}
|
|
4617
|
-
function nextTextChar(text, index) {
|
|
4618
|
-
const codePoint = text.codePointAt(index);
|
|
4619
|
-
if (codePoint === undefined)
|
|
4620
|
-
return { value: "", nextIndex: index };
|
|
4621
|
-
const value = String.fromCodePoint(codePoint);
|
|
4622
|
-
return { value, nextIndex: index + value.length };
|
|
4623
|
-
}
|
|
4624
|
-
function messageContentWidth(columns) {
|
|
4625
|
-
return Math.max(10, columns - messageRoleMarker().length);
|
|
4626
|
-
}
|
|
4627
|
-
function toolContentWidth(columns) {
|
|
4628
|
-
return Math.max(10, columns - 2);
|
|
4629
|
-
}
|
|
4630
|
-
function stringCellWidth(value) {
|
|
4631
|
-
let width = 0;
|
|
4632
|
-
for (const char of [...value])
|
|
4633
|
-
width += charCellWidth(char);
|
|
4634
|
-
return width;
|
|
4635
|
-
}
|
|
4636
|
-
function charCellWidth(char) {
|
|
4637
|
-
const codePoint = char.codePointAt(0);
|
|
4638
|
-
if (codePoint === undefined)
|
|
4639
|
-
return 0;
|
|
4640
|
-
if (codePoint === 0)
|
|
4641
|
-
return 0;
|
|
4642
|
-
if (codePoint < 32 || (codePoint >= 0x7f && codePoint < 0xa0))
|
|
4643
|
-
return 0;
|
|
4644
|
-
if (isCombiningMark(codePoint))
|
|
4645
|
-
return 0;
|
|
4646
|
-
return isFullWidthCodePoint(codePoint) ? 2 : 1;
|
|
4647
|
-
}
|
|
4648
|
-
function isCombiningMark(codePoint) {
|
|
4649
|
-
return ((codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
4650
|
-
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
4651
|
-
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
4652
|
-
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
4653
|
-
(codePoint >= 0xfe20 && codePoint <= 0xfe2f));
|
|
4654
|
-
}
|
|
4655
|
-
function isFullWidthCodePoint(codePoint) {
|
|
4656
|
-
return (codePoint >= 0x1100 && (codePoint <= 0x115f ||
|
|
4657
|
-
codePoint === 0x2329 ||
|
|
4658
|
-
codePoint === 0x232a ||
|
|
4659
|
-
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
4660
|
-
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
4661
|
-
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
4662
|
-
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
4663
|
-
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
4664
|
-
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
4665
|
-
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
4666
|
-
(codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
|
|
4667
|
-
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
4668
|
-
(codePoint >= 0x20000 && codePoint <= 0x3fffd)));
|
|
4669
|
-
}
|
|
4670
|
-
const SESSIONS_DEFAULT_PAGE_SIZE = 10;
|
|
4671
|
-
const TERMINAL_TITLE_WORKING_PREFIX = "● ";
|
|
4672
|
-
const TERMINAL_TITLE_READY_PREFIX = "✓ ";
|
|
4673
|
-
const REPL_ANIMATION_INTERVAL_MS = 420;
|
|
4674
|
-
const TOOL_RESULT_REPLACEMENT_DELAY_MS = 2000;
|
|
4675
|
-
const SUBAGENT_ACTIVITY_UPDATE_DEBOUNCE_MS = 1000;
|
|
4676
|
-
const SUBAGENT_COMPLETED_LINGER_MS = 8000;
|
|
4677
|
-
const TOKEN_PULSE_MS = 900;
|
|
4678
|
-
const ANIMATED_NUMBER_INTERVAL_MS = 50;
|
|
4679
|
-
const ANIMATED_NUMBER_MIN_DURATION_MS = 180;
|
|
4680
|
-
const ANIMATED_NUMBER_MAX_DURATION_MS = 700;
|
|
4681
|
-
const ANIMATED_NUMBER_DURATION_SCALE_MS = 130;
|
|
4682
|
-
const STATUS_BLINK_TICKS = 2;
|
|
4683
|
-
const STATUS_PHASE_MIN_DISPLAY_MS = 2000;
|
|
4684
|
-
const STATUS_SHIMMER_GAP_TICKS = 3;
|
|
4685
|
-
const STATUS_SHIMMER_RADIUS = 1;
|
|
4686
|
-
const STATUS_SHIMMER_COLOR = "whiteBright";
|
|
4687
|
-
const STATUS_SEPARATOR = " · ";
|
|
4688
|
-
const STATUS_BAR_RENDER_ROWS = 1;
|
|
4689
|
-
const FOREGROUND_EXEC_DETACH_HINT_RENDER_ROWS = 1;
|
|
4690
|
-
const FOREGROUND_EXEC_DETACH_HINT_DELAY_MS = 2000;
|
|
4691
|
-
const BACKGROUND_TASK_STATUS_RENDER_ROWS = 1;
|
|
4692
|
-
const QUEUED_INPUT_RENDER_ROWS = 1;
|
|
4693
|
-
const EMPTY_CTRL_C_EXIT_PLACEHOLDER = "Press Ctrl+C again to exit";
|
|
4694
|
-
const LONG_CLIPBOARD_TEXT_THRESHOLD = 200;
|
|
4695
|
-
const PASTE_STATUS_DISPLAY_MS = 2500;
|
|
4696
|
-
const MIN_LIVE_VIEWPORT_LINES = 1;
|
|
4697
|
-
const FULLSCREEN_RENDER_GUARD_ROWS = 3;
|
|
4698
|
-
const COMPACT_LIVE_LAYOUT_ROWS = 24;
|
|
4699
|
-
const COMPACT_LIVE_DYNAMIC_BLOCKS = 1;
|
|
4700
|
-
const MESSAGE_BLOCK_SPACING_LINES = 1;
|
|
4701
|
-
const SUMMARY_BLOCK = {
|
|
4702
|
-
maxLines: 6,
|
|
4703
|
-
detailIndent: " ",
|
|
4704
|
-
};
|
|
4705
|
-
const THINKING_COLOR = "#a855f7";
|
|
4706
|
-
const THINKING_MARKER = "◆";
|
|
4707
|
-
const THINKING_SUMMARY_MAX_LINES = 1000;
|
|
4708
|
-
const EXPANDED_SUMMARY_MAX_LINES = 1000;
|
|
4709
|
-
const EDIT_TOOL_SUMMARY_MAX_LINES = EXPANDED_SUMMARY_MAX_LINES;
|
|
4710
|
-
function fixed(value, width, align = "right") {
|
|
4711
|
-
const stripped = stripAnsi(value);
|
|
4712
|
-
const trimmed = stripped.length > width ? stripped.slice(0, width) : stripped;
|
|
4713
|
-
return align === "left" ? trimmed.padEnd(width, " ") : trimmed.padStart(width, " ");
|
|
4714
|
-
}
|
|
4715
|
-
function fitToWidth(value, width) {
|
|
4716
|
-
const stripped = stripAnsi(value);
|
|
4717
|
-
if (stripped.length === width)
|
|
4718
|
-
return stripped;
|
|
4719
|
-
if (stripped.length > width)
|
|
4720
|
-
return stripped.slice(0, width);
|
|
4721
|
-
return stripped.padEnd(width, " ");
|
|
4722
|
-
}
|
|
4723
|
-
function truncateMiddle(value, maxLength) {
|
|
4724
|
-
if (value.length <= maxLength)
|
|
4725
|
-
return value;
|
|
4726
|
-
if (maxLength <= 3)
|
|
4727
|
-
return value.slice(0, maxLength);
|
|
4728
|
-
const left = Math.ceil((maxLength - 3) / 2);
|
|
4729
|
-
const right = Math.floor((maxLength - 3) / 2);
|
|
4730
|
-
return `${value.slice(0, left)}...${value.slice(value.length - right)}`;
|
|
4731
|
-
}
|
|
4732
1954
|
main().catch((error) => {
|
|
4733
1955
|
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
4734
1956
|
process.exitCode = 1;
|