neoctl 0.2.4 → 0.2.6
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/README.md +16 -26
- package/dist/agents/agent-activity.d.ts +70 -0
- package/dist/agents/agent-activity.js +261 -0
- package/dist/agents/agent-activity.js.map +1 -0
- package/dist/agents/agent-definition.d.ts +7 -0
- package/dist/agents/agent-definition.js +44 -1
- package/dist/agents/agent-definition.js.map +1 -1
- package/dist/agents/agent-report-tool.d.ts +11 -0
- package/dist/agents/agent-report-tool.js +50 -0
- package/dist/agents/agent-report-tool.js.map +1 -0
- package/dist/agents/agent-tool.d.ts +3 -1
- package/dist/agents/agent-tool.js +56 -11
- package/dist/agents/agent-tool.js.map +1 -1
- package/dist/agents/local-agent-task.d.ts +3 -0
- package/dist/agents/local-agent-task.js +2 -0
- package/dist/agents/local-agent-task.js.map +1 -1
- package/dist/agents/smoke-agents.js +131 -7
- package/dist/agents/smoke-agents.js.map +1 -1
- package/dist/context/compaction.js +2 -1
- package/dist/context/compaction.js.map +1 -1
- package/dist/context/context-manager.d.ts +2 -1
- package/dist/context/context-manager.js +24 -11
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/prompts.js +4 -0
- package/dist/context/prompts.js.map +1 -1
- package/dist/context/smoke-context.js +14 -6
- package/dist/context/smoke-context.js.map +1 -1
- package/dist/core/context-metrics.d.ts +2 -1
- package/dist/core/context-metrics.js +3 -1
- package/dist/core/context-metrics.js.map +1 -1
- package/dist/core/image-registry.js +3 -3
- package/dist/core/image-registry.js.map +1 -1
- package/dist/core/image-storage.d.ts +14 -0
- package/dist/core/image-storage.js +31 -0
- package/dist/core/image-storage.js.map +1 -1
- package/dist/core/message-pipeline.d.ts +6 -0
- package/dist/core/message-pipeline.js +89 -10
- package/dist/core/message-pipeline.js.map +1 -1
- package/dist/core/prompt-cache-telemetry.d.ts +11 -0
- package/dist/core/prompt-cache-telemetry.js +71 -0
- package/dist/core/prompt-cache-telemetry.js.map +1 -0
- package/dist/core/query-engine.d.ts +3 -1
- package/dist/core/query-engine.js +21 -6
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +4 -0
- package/dist/core/query.js +28 -5
- package/dist/core/query.js.map +1 -1
- package/dist/core/run-agent.d.ts +2 -0
- package/dist/core/run-agent.js +156 -21
- package/dist/core/run-agent.js.map +1 -1
- package/dist/core/smoke-core-loop.js +11 -2
- package/dist/core/smoke-core-loop.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/model/anthropic-mapper.js +29 -3
- package/dist/model/anthropic-mapper.js.map +1 -1
- package/dist/model/config.d.ts +2 -8
- package/dist/model/config.js +1 -41
- package/dist/model/config.js.map +1 -1
- package/dist/model/env.js +6 -11
- package/dist/model/env.js.map +1 -1
- package/dist/model/model-metadata.json +1 -115
- package/dist/model/openai-chat-mapper.js +4 -19
- package/dist/model/openai-chat-mapper.js.map +1 -1
- package/dist/model/openai-mappers.js +15 -6
- package/dist/model/openai-mappers.js.map +1 -1
- package/dist/model/provider-factory.js +0 -32
- package/dist/model/provider-factory.js.map +1 -1
- package/dist/model/smoke-anthropic-mapper.js +6 -2
- package/dist/model/smoke-anthropic-mapper.js.map +1 -1
- package/dist/repl/commands.d.ts +14 -0
- package/dist/repl/commands.js +54 -0
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/index.js +1008 -117
- package/dist/repl/index.js.map +1 -1
- package/dist/secrets/secret-crypto.d.ts +22 -0
- package/dist/secrets/secret-crypto.js +58 -0
- package/dist/secrets/secret-crypto.js.map +1 -0
- package/dist/secrets/secret-redaction.d.ts +8 -0
- package/dist/secrets/secret-redaction.js +40 -0
- package/dist/secrets/secret-redaction.js.map +1 -0
- package/dist/secrets/secret-store.d.ts +28 -0
- package/dist/secrets/secret-store.js +158 -0
- package/dist/secrets/secret-store.js.map +1 -0
- package/dist/secrets/secret-types.d.ts +31 -0
- package/dist/secrets/secret-types.js +17 -0
- package/dist/secrets/secret-types.js.map +1 -0
- package/dist/secrets/smoke-secrets.js +68 -0
- package/dist/secrets/smoke-secrets.js.map +1 -0
- package/dist/session/session-export.js +2 -1
- package/dist/session/session-export.js.map +1 -1
- package/dist/skills/skill-filesystem.js +1 -1
- package/dist/skills/skill-filesystem.js.map +1 -1
- package/dist/skills/skill-tool.js +86 -22
- package/dist/skills/skill-tool.js.map +1 -1
- package/dist/tools/builtins/exec-tool.d.ts +20 -1
- package/dist/tools/builtins/exec-tool.js +167 -29
- package/dist/tools/builtins/exec-tool.js.map +1 -1
- package/dist/tools/builtins/image-generation-tool.js +22 -4
- package/dist/tools/builtins/image-generation-tool.js.map +1 -1
- package/dist/tools/builtins/plan-tool.d.ts +1 -0
- package/dist/tools/builtins/plan-tool.js +80 -27
- package/dist/tools/builtins/plan-tool.js.map +1 -1
- package/dist/tools/builtins/secret-tools.d.ts +10 -0
- package/dist/tools/builtins/secret-tools.js +75 -0
- package/dist/tools/builtins/secret-tools.js.map +1 -0
- package/dist/tools/run-tool-use.js +4 -2
- package/dist/tools/run-tool-use.js.map +1 -1
- package/dist/tools/smoke-tool-system.js +111 -10
- package/dist/tools/smoke-tool-system.js.map +1 -1
- package/dist/tools/tool.d.ts +4 -0
- package/dist/tools/tool.js.map +1 -1
- package/dist/types/events.d.ts +17 -0
- package/dist/ui/display-message.js +8 -4
- package/dist/ui/display-message.js.map +1 -1
- package/dist/web/html.js +1 -1
- package/dist/web/index.js +34 -56
- package/dist/web/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/model/deepseek-adapter.d.ts +0 -29
- package/dist/model/deepseek-adapter.js +0 -108
- package/dist/model/deepseek-adapter.js.map +0 -1
- package/dist/model/kimi-adapter.d.ts +0 -29
- package/dist/model/kimi-adapter.js +0 -108
- package/dist/model/kimi-adapter.js.map +0 -1
- package/dist/model/smoke-deepseek-mapper.js +0 -65
- package/dist/model/smoke-deepseek-mapper.js.map +0 -1
- /package/dist/{model/smoke-deepseek-mapper.d.ts → secrets/smoke-secrets.d.ts} +0 -0
package/dist/repl/index.js
CHANGED
|
@@ -22,17 +22,60 @@ import { searchTool } from "../tools/builtins/search-tool.js";
|
|
|
22
22
|
import { planTool } from "../tools/builtins/plan-tool.js";
|
|
23
23
|
import { createOpenAIImageGenerationTool } from "../tools/builtins/image-generation-tool.js";
|
|
24
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";
|
|
25
28
|
import { createAgentTool, resumeAgentTask } from "../agents/agent-tool.js";
|
|
29
|
+
import { AgentActivityStore } from "../agents/agent-activity.js";
|
|
26
30
|
import { createTaskTools } from "../tasks/task-tools.js";
|
|
27
31
|
import { TaskStore } from "../tasks/task-store.js";
|
|
28
32
|
import { cliHelpText, isModelReasoningArgument, isValidReplCommandLine, parseCliReplCommandArgs, parseReplCommand, helpText, replCommandDefinitions } from "./commands.js";
|
|
29
33
|
import { estimateMarkdownLineCount, markdownRenderKey, MarkdownText } from "./markdown-renderer.js";
|
|
34
|
+
import { DefaultContextManager } from "../context/context-manager.js";
|
|
35
|
+
import { buildEffectiveSystemPrompt } from "../context/prompts.js";
|
|
30
36
|
import { writeSessionMarkdownExport } from "../session/session-export.js";
|
|
31
37
|
import { readClipboard } from "./clipboard.js";
|
|
32
38
|
import { formatTipLine, initialTipIndex, tipAt } from "../tips.js";
|
|
33
39
|
import { openDirectory } from "../open-directory.js";
|
|
34
40
|
import { runWebServer } from "../web/index.js";
|
|
41
|
+
import { getNeoctlHome } from "../paths.js";
|
|
42
|
+
import { FileSystemSkillCatalog } from "../skills/skill-filesystem.js";
|
|
43
|
+
import { createSkillAwareCanUseTool, createSkillTool, requireSkillName } from "../skills/skill-tool.js";
|
|
44
|
+
import { createSkillManagementTools } from "../skills/skill-management-tools.js";
|
|
35
45
|
const e = React.createElement;
|
|
46
|
+
class ReplForegroundExecDetachRegistry {
|
|
47
|
+
handle;
|
|
48
|
+
subscribers = new Set();
|
|
49
|
+
set(handle) {
|
|
50
|
+
this.handle = handle;
|
|
51
|
+
this.notify();
|
|
52
|
+
return () => {
|
|
53
|
+
if (this.handle === handle) {
|
|
54
|
+
this.handle = undefined;
|
|
55
|
+
this.notify();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
current() {
|
|
60
|
+
return this.handle;
|
|
61
|
+
}
|
|
62
|
+
subscribe(listener) {
|
|
63
|
+
this.subscribers.add(listener);
|
|
64
|
+
return () => {
|
|
65
|
+
this.subscribers.delete(listener);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
detachCurrent() {
|
|
69
|
+
const handle = this.handle;
|
|
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
|
+
}
|
|
36
79
|
class SessionUsageTracker {
|
|
37
80
|
totals = emptyUsageTotals();
|
|
38
81
|
lastUsage;
|
|
@@ -128,6 +171,50 @@ function binaryName() {
|
|
|
128
171
|
const name = parsed.name || "neo";
|
|
129
172
|
return name === "index" ? "neo" : name;
|
|
130
173
|
}
|
|
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
|
+
}
|
|
131
218
|
function createTaskNotificationSource(taskStore) {
|
|
132
219
|
return {
|
|
133
220
|
collectUnnotifiedCompletions() {
|
|
@@ -150,10 +237,22 @@ async function createRuntime() {
|
|
|
150
237
|
const communicationLogger = new CommunicationLogger();
|
|
151
238
|
const modelGateway = new LoggingModelGateway(createModelGatewayFromProcessEnv(process.env), communicationLogger);
|
|
152
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();
|
|
153
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
|
+
});
|
|
154
253
|
tools.register(editTool);
|
|
155
254
|
tools.register(writeTool);
|
|
156
|
-
tools.register(createExecTool({ taskStore }));
|
|
255
|
+
tools.register(createExecTool({ taskStore, foregroundDetachRegistry: foregroundExecDetach }));
|
|
157
256
|
tools.register(listDirectoryTool);
|
|
158
257
|
tools.register(readFileTool);
|
|
159
258
|
tools.register(grepTool);
|
|
@@ -162,13 +261,20 @@ async function createRuntime() {
|
|
|
162
261
|
if (modelConfig?.provider === "openai")
|
|
163
262
|
tools.register(createOpenAIImageGenerationTool());
|
|
164
263
|
tools.register(planTool);
|
|
165
|
-
const
|
|
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 };
|
|
166
270
|
tools.register(createAgentTool(agentRuntime));
|
|
167
271
|
const resumeHandler = async (taskId, directive) => {
|
|
168
272
|
const dummyContext = {
|
|
169
273
|
agentId: "main",
|
|
170
274
|
tools,
|
|
171
275
|
appState: new (await import("../app/app-state.js")).InMemoryAppState("main"),
|
|
276
|
+
secrets: secretStore,
|
|
277
|
+
secretRedactions,
|
|
172
278
|
emit: () => undefined,
|
|
173
279
|
};
|
|
174
280
|
return resumeAgentTask(taskId, directive, agentRuntime, taskStore, dummyContext);
|
|
@@ -183,6 +289,10 @@ async function createRuntime() {
|
|
|
183
289
|
reasoning: modelConfig?.defaultReasoning,
|
|
184
290
|
modelGateway,
|
|
185
291
|
tools,
|
|
292
|
+
contextManager: new SkillCatalogContextManager(skills),
|
|
293
|
+
canUseTool: createSkillAwareCanUseTool(skills),
|
|
294
|
+
secrets: secretStore,
|
|
295
|
+
secretRedactions,
|
|
186
296
|
taskNotificationSource,
|
|
187
297
|
commands: replCommandDefinitions.map((command) => command.usage),
|
|
188
298
|
session: {
|
|
@@ -204,7 +314,12 @@ async function createRuntime() {
|
|
|
204
314
|
agentRuntime,
|
|
205
315
|
usage: new SessionUsageTracker(),
|
|
206
316
|
taskStore,
|
|
317
|
+
agentActivityStore,
|
|
318
|
+
foregroundExecDetach,
|
|
207
319
|
tools,
|
|
320
|
+
skills,
|
|
321
|
+
secretStore,
|
|
322
|
+
skillWorkspaceRoot,
|
|
208
323
|
initialMetrics,
|
|
209
324
|
defaultReasoning: modelConfig?.defaultReasoning,
|
|
210
325
|
envPath: process.env.NEO_ENV_FILE?.trim() ? path.resolve(process.env.NEO_ENV_FILE.trim()) : envLoad.userDotEnvPath,
|
|
@@ -217,7 +332,7 @@ function syncImageGenerationTool(runtime, provider) {
|
|
|
217
332
|
runtime.tools.register(createOpenAIImageGenerationTool());
|
|
218
333
|
}
|
|
219
334
|
function formatCreatedEnvNotice(path) {
|
|
220
|
-
return `Created default config file: ${path}\nSet MODEL_PROVIDER and the matching provider section (
|
|
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.`;
|
|
221
336
|
}
|
|
222
337
|
function parseResumeFlag(value) {
|
|
223
338
|
if (!value)
|
|
@@ -227,6 +342,34 @@ function parseResumeFlag(value) {
|
|
|
227
342
|
function activeBackgroundTasks(runtime) {
|
|
228
343
|
return runtime.taskStore.list().filter((task) => !runtime.taskStore.isTerminal(task));
|
|
229
344
|
}
|
|
345
|
+
function debounceVoid(callback, delayMs) {
|
|
346
|
+
let timer;
|
|
347
|
+
return {
|
|
348
|
+
run: () => {
|
|
349
|
+
if (timer)
|
|
350
|
+
clearTimeout(timer);
|
|
351
|
+
timer = setTimeout(() => {
|
|
352
|
+
timer = undefined;
|
|
353
|
+
callback();
|
|
354
|
+
}, delayMs);
|
|
355
|
+
},
|
|
356
|
+
cancel: () => {
|
|
357
|
+
if (!timer)
|
|
358
|
+
return;
|
|
359
|
+
clearTimeout(timer);
|
|
360
|
+
timer = undefined;
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function activeAgentActivities(runtime) {
|
|
365
|
+
const now = Date.now();
|
|
366
|
+
return runtime.agentActivityStore.list().filter((activity) => {
|
|
367
|
+
if (activity.status === "running" || activity.status === "pending")
|
|
368
|
+
return true;
|
|
369
|
+
const completedAt = activity.completedAt ? new Date(activity.completedAt).getTime() : new Date(activity.updatedAt).getTime();
|
|
370
|
+
return Number.isFinite(completedAt) && now - completedAt < SUBAGENT_COMPLETED_LINGER_MS;
|
|
371
|
+
});
|
|
372
|
+
}
|
|
230
373
|
function runningSessionIds(runs) {
|
|
231
374
|
return [...runs.keys()];
|
|
232
375
|
}
|
|
@@ -393,6 +536,9 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
393
536
|
const [status, setStatus] = useState(() => initialStatus(runtime));
|
|
394
537
|
const sessionTitleRef = useRef(sessionTerminalTitle(runtime.engine.snapshot().session));
|
|
395
538
|
const [backgroundTasks, setBackgroundTasks] = useState(() => activeBackgroundTasks(runtime));
|
|
539
|
+
const [agentActivities, setAgentActivities] = useState(() => runtime.agentActivityStore.list());
|
|
540
|
+
const [foregroundExecDetachHandle, setForegroundExecDetachHandle] = useState(() => runtime.foregroundExecDetach.current());
|
|
541
|
+
const [showForegroundExecDetachHint, setShowForegroundExecDetachHint] = useState(false);
|
|
396
542
|
const [backgroundSessionRuns, setBackgroundSessionRuns] = useState([]);
|
|
397
543
|
const backgroundSessionRunsRef = useRef(new Map());
|
|
398
544
|
const suppressReattachedStreamingRef = useRef(new Set());
|
|
@@ -403,6 +549,8 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
403
549
|
const backgroundTaskCount = backgroundTasks.length;
|
|
404
550
|
const terminalTitleWorking = isActivePhase(status.phase) || backgroundTaskCount > 0 || backgroundSessionRuns.length > 0;
|
|
405
551
|
const [sessionsBrowser, setSessionsBrowser] = useState(undefined);
|
|
552
|
+
const [skillsBrowser, setSkillsBrowser] = useState(undefined);
|
|
553
|
+
const [secretsBrowser, setSecretsBrowser] = useState(undefined);
|
|
406
554
|
const inputRef = useRef(input);
|
|
407
555
|
const queuedInputRef = useRef(undefined);
|
|
408
556
|
const cursorRef = useRef(cursor);
|
|
@@ -418,6 +566,8 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
418
566
|
const [pasteStatus, setPasteStatus] = useState(undefined);
|
|
419
567
|
const pasteStatusTimerRef = useRef(undefined);
|
|
420
568
|
const [slashCompletionIndex, setSlashCompletionIndex] = useState(0);
|
|
569
|
+
const [skillCompletions, setSkillCompletions] = useState([]);
|
|
570
|
+
const [secretCompletions, setSecretCompletions] = useState([]);
|
|
421
571
|
const [loginForm, setLoginForm] = useState(undefined);
|
|
422
572
|
const loginFormRef = useRef(undefined);
|
|
423
573
|
useEffect(() => {
|
|
@@ -429,16 +579,48 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
429
579
|
};
|
|
430
580
|
}, []);
|
|
431
581
|
useEffect(() => {
|
|
432
|
-
if (!busy && backgroundTaskCount === 0 && backgroundSessionRuns.length === 0)
|
|
582
|
+
if (!busy && backgroundTaskCount === 0 && backgroundSessionRuns.length === 0 && agentActivities.length === 0)
|
|
433
583
|
return undefined;
|
|
434
|
-
const interval = setInterval(() =>
|
|
584
|
+
const interval = setInterval(() => {
|
|
585
|
+
setAnimationTick((current) => current + 1);
|
|
586
|
+
setAgentActivities(activeAgentActivities(runtime));
|
|
587
|
+
}, REPL_ANIMATION_INTERVAL_MS);
|
|
435
588
|
return () => clearInterval(interval);
|
|
436
|
-
}, [busy, backgroundTaskCount, backgroundSessionRuns.length]);
|
|
589
|
+
}, [busy, backgroundTaskCount, backgroundSessionRuns.length, agentActivities.length, runtime]);
|
|
437
590
|
useEffect(() => {
|
|
438
591
|
const updateBackgroundTasks = () => setBackgroundTasks(activeBackgroundTasks(runtime));
|
|
439
592
|
updateBackgroundTasks();
|
|
440
593
|
return runtime.taskStore.subscribe(updateBackgroundTasks);
|
|
441
594
|
}, [runtime]);
|
|
595
|
+
useEffect(() => {
|
|
596
|
+
const updateAgentActivities = () => setAgentActivities(activeAgentActivities(runtime));
|
|
597
|
+
const debouncedUpdateAgentActivities = debounceVoid(updateAgentActivities, SUBAGENT_ACTIVITY_UPDATE_DEBOUNCE_MS);
|
|
598
|
+
updateAgentActivities();
|
|
599
|
+
const unsubscribe = runtime.agentActivityStore.subscribe(debouncedUpdateAgentActivities.run);
|
|
600
|
+
return () => {
|
|
601
|
+
unsubscribe();
|
|
602
|
+
debouncedUpdateAgentActivities.cancel();
|
|
603
|
+
};
|
|
604
|
+
}, [runtime]);
|
|
605
|
+
useEffect(() => {
|
|
606
|
+
const updateForegroundExecDetachHandle = () => setForegroundExecDetachHandle(runtime.foregroundExecDetach.current());
|
|
607
|
+
updateForegroundExecDetachHandle();
|
|
608
|
+
return runtime.foregroundExecDetach.subscribe(updateForegroundExecDetachHandle);
|
|
609
|
+
}, [runtime]);
|
|
610
|
+
useEffect(() => {
|
|
611
|
+
if (!foregroundExecDetachHandle) {
|
|
612
|
+
setShowForegroundExecDetachHint(false);
|
|
613
|
+
return undefined;
|
|
614
|
+
}
|
|
615
|
+
const elapsedMs = Date.now() - foregroundExecDetachHandle.startedAt;
|
|
616
|
+
if (elapsedMs >= FOREGROUND_EXEC_DETACH_HINT_DELAY_MS) {
|
|
617
|
+
setShowForegroundExecDetachHint(true);
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
620
|
+
setShowForegroundExecDetachHint(false);
|
|
621
|
+
const timer = setTimeout(() => setShowForegroundExecDetachHint(true), FOREGROUND_EXEC_DETACH_HINT_DELAY_MS - elapsedMs);
|
|
622
|
+
return () => clearTimeout(timer);
|
|
623
|
+
}, [foregroundExecDetachHandle]);
|
|
442
624
|
useEffect(() => {
|
|
443
625
|
if (!terminalTitleWorking) {
|
|
444
626
|
setTerminalTitlePrefix(TERMINAL_TITLE_READY_PREFIX);
|
|
@@ -488,6 +670,30 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
488
670
|
loginFormRef.current = next;
|
|
489
671
|
setLoginForm(next);
|
|
490
672
|
};
|
|
673
|
+
const refreshSkillCompletions = useCallback(async () => {
|
|
674
|
+
try {
|
|
675
|
+
const skills = await runtime.skills.list();
|
|
676
|
+
setSkillCompletions(skills.map((skill) => ({ name: skill.name, description: skill.description, execution: skill.execution, tags: skill.tags })));
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
setSkillCompletions([]);
|
|
680
|
+
}
|
|
681
|
+
}, [runtime]);
|
|
682
|
+
const refreshSecretCompletions = useCallback(async () => {
|
|
683
|
+
try {
|
|
684
|
+
const secrets = await runtime.secretStore.list();
|
|
685
|
+
setSecretCompletions(secrets.map((secret) => ({ key: secret.key, status: secret.status, length: secret.length, requestReason: secret.requestReason })));
|
|
686
|
+
}
|
|
687
|
+
catch {
|
|
688
|
+
setSecretCompletions([]);
|
|
689
|
+
}
|
|
690
|
+
}, [runtime]);
|
|
691
|
+
useEffect(() => {
|
|
692
|
+
void refreshSkillCompletions();
|
|
693
|
+
}, [refreshSkillCompletions]);
|
|
694
|
+
useEffect(() => {
|
|
695
|
+
void refreshSecretCompletions();
|
|
696
|
+
}, [refreshSecretCompletions]);
|
|
491
697
|
const syncAttachmentsForText = (text) => {
|
|
492
698
|
const next = attachmentsRef.current.filter((attachment) => text.includes(attachment.label));
|
|
493
699
|
if (next.length === attachmentsRef.current.length)
|
|
@@ -855,6 +1061,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
855
1061
|
};
|
|
856
1062
|
const handleCommandOrPrompt = async (text, submitAttachments = []) => {
|
|
857
1063
|
const command = parseReplCommand(text);
|
|
1064
|
+
let promptText = command.type === "input" ? command.text : text;
|
|
858
1065
|
if (command.type === "exit") {
|
|
859
1066
|
app.exit();
|
|
860
1067
|
return;
|
|
@@ -990,11 +1197,48 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
990
1197
|
}
|
|
991
1198
|
if (command.type === "sessions") {
|
|
992
1199
|
detachRunningForeground("session browser");
|
|
1200
|
+
setSkillsBrowser(undefined);
|
|
1201
|
+
setSecretsBrowser(undefined);
|
|
993
1202
|
await handleSessionsCommand(runtime, runningSessionIds(backgroundSessionRunsRef.current), setSessionsBrowser, (line) => append(line));
|
|
994
1203
|
return;
|
|
995
1204
|
}
|
|
1205
|
+
if (command.type === "secret") {
|
|
1206
|
+
try {
|
|
1207
|
+
if (command.action === "list") {
|
|
1208
|
+
setSessionsBrowser(undefined);
|
|
1209
|
+
setSkillsBrowser(undefined);
|
|
1210
|
+
await handleSecretsCommand(runtime, setSecretsBrowser, (line) => append(line));
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
append(await handleSecretCommand(command, runtime));
|
|
1214
|
+
void refreshSecretCompletions();
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
catch (error) {
|
|
1218
|
+
append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
1219
|
+
}
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
if (command.type === "skill") {
|
|
1223
|
+
if (command.action === "list") {
|
|
1224
|
+
setSessionsBrowser(undefined);
|
|
1225
|
+
setSecretsBrowser(undefined);
|
|
1226
|
+
await handleSkillsCommand(runtime, setSkillsBrowser, (line) => append(line));
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
if (command.action !== "invoke" || !command.name || !command.args) {
|
|
1230
|
+
append(await handleSkillCommand(command, runtime));
|
|
1231
|
+
if (command.action === "import" || command.action === "delete")
|
|
1232
|
+
void refreshSkillCompletions();
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
promptText = renderSkillInvocationPrompt(command.name, command.args);
|
|
1236
|
+
text = `/skill ${command.name} ${command.args}`;
|
|
1237
|
+
}
|
|
996
1238
|
if (command.type === "login") {
|
|
997
1239
|
setSessionsBrowser(undefined);
|
|
1240
|
+
setSkillsBrowser(undefined);
|
|
1241
|
+
setSecretsBrowser(undefined);
|
|
998
1242
|
setLoginFormState(createLoginFormState(runtime.envPath));
|
|
999
1243
|
append(systemLine("Opening provider login. Use ↑/↓ to choose, Enter to continue/save, Esc to cancel."));
|
|
1000
1244
|
return;
|
|
@@ -1023,11 +1267,11 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1023
1267
|
}
|
|
1024
1268
|
return;
|
|
1025
1269
|
}
|
|
1026
|
-
if (text.trimStart().startsWith("/")) {
|
|
1270
|
+
if (command.type !== "skill" && text.trimStart().startsWith("/")) {
|
|
1027
1271
|
append({ kind: "error", text: `Unknown or incomplete command: ${text.trim()}\nType /help for commands.` });
|
|
1028
1272
|
return;
|
|
1029
1273
|
}
|
|
1030
|
-
const promptPayload = buildPromptPayload(
|
|
1274
|
+
const promptPayload = buildPromptPayload(promptText, submitAttachments);
|
|
1031
1275
|
append({ kind: "user", text });
|
|
1032
1276
|
const runToken = ++foregroundRunTokenRef.current;
|
|
1033
1277
|
const abortController = new AbortController();
|
|
@@ -1112,6 +1356,8 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1112
1356
|
clearPendingToolResultTimers();
|
|
1113
1357
|
setStatus(initialStatus(runtime));
|
|
1114
1358
|
setSessionsBrowser(undefined);
|
|
1359
|
+
setSkillsBrowser(undefined);
|
|
1360
|
+
setSecretsBrowser(undefined);
|
|
1115
1361
|
setLoginFormState(undefined);
|
|
1116
1362
|
setQueuedPromptState(undefined);
|
|
1117
1363
|
setPromptState("", 0);
|
|
@@ -1131,7 +1377,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1131
1377
|
const promptDisplayCursor = cursor;
|
|
1132
1378
|
const promptLayoutText = activePlaceholder ? ` ${activePlaceholder}` : promptDisplayText;
|
|
1133
1379
|
const promptLayoutCursor = activePlaceholder ? 0 : promptDisplayCursor;
|
|
1134
|
-
const slashCompletions = inputLockedByQueue || (input.length === 0 && promptPlaceholder !== undefined) || loginForm ? [] : slashCommandCompletions(input, cursor);
|
|
1380
|
+
const slashCompletions = inputLockedByQueue || (input.length === 0 && promptPlaceholder !== undefined) || loginForm ? [] : slashCommandCompletions(input, cursor, skillCompletions, secretCompletions);
|
|
1135
1381
|
const visibleSlashCompletionCount = slashCompletions.length;
|
|
1136
1382
|
const selectedSlashCompletionIndex = visibleSlashCompletionCount === 0
|
|
1137
1383
|
? 0
|
|
@@ -1147,10 +1393,18 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1147
1393
|
const blockIndex = staticLines.length + i;
|
|
1148
1394
|
return sum + (blockIndex > 0 ? MESSAGE_BLOCK_SPACING_LINES : 0);
|
|
1149
1395
|
}, 0);
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1396
|
+
const subagentRows = subagentLivePanelRenderRows(agentActivities, terminalSize.rows);
|
|
1397
|
+
const nonAgentBackgroundTasks = backgroundTasks.filter((task) => task.type !== "agent");
|
|
1398
|
+
const statusRenderRows = STATUS_BAR_RENDER_ROWS + (showForegroundExecDetachHint && foregroundExecDetachHandle ? FOREGROUND_EXEC_DETACH_HINT_RENDER_ROWS : 0) + subagentRows + backgroundTaskStatusRenderRows(subagentRows > 0 ? nonAgentBackgroundTasks.length : backgroundTasks.length);
|
|
1399
|
+
const managementBrowserHeight = sessionsBrowser
|
|
1400
|
+
? sessionsBrowserViewHeight(sessionsBrowser)
|
|
1401
|
+
: skillsBrowser
|
|
1402
|
+
? skillsBrowserViewHeight(skillsBrowser)
|
|
1403
|
+
: secretsBrowser
|
|
1404
|
+
? secretsBrowserViewHeight(secretsBrowser)
|
|
1405
|
+
: 0;
|
|
1152
1406
|
const loginFormHeight = loginForm ? loginFormViewHeight(loginForm) : 0;
|
|
1153
|
-
const liveViewportLines = Math.max(MIN_LIVE_VIEWPORT_LINES, terminalSize.rows - promptHeight - statusRenderRows -
|
|
1407
|
+
const liveViewportLines = Math.max(MIN_LIVE_VIEWPORT_LINES, terminalSize.rows - promptHeight - statusRenderRows - managementBrowserHeight - loginFormHeight - dynamicMarginOverhead - 1);
|
|
1154
1408
|
useInput((value, key) => {
|
|
1155
1409
|
if (isTerminalFocusInSequence(value)) {
|
|
1156
1410
|
terminalFocusedRef.current = true;
|
|
@@ -1171,6 +1425,13 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1171
1425
|
void handleClipboardPaste();
|
|
1172
1426
|
return;
|
|
1173
1427
|
}
|
|
1428
|
+
if (key.ctrl && value.toLowerCase() === "b") {
|
|
1429
|
+
const result = runtime.foregroundExecDetach.detachCurrent();
|
|
1430
|
+
append(result.ok
|
|
1431
|
+
? systemLine(`Detached foreground exec to background task ${result.taskId ?? "unknown"}.`)
|
|
1432
|
+
: systemLine(result.message));
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1174
1435
|
if (key.ctrl && value === "c") {
|
|
1175
1436
|
if (inputRef.current.length > 0) {
|
|
1176
1437
|
setPromptState("", 0);
|
|
@@ -1245,15 +1506,128 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1245
1506
|
}
|
|
1246
1507
|
return;
|
|
1247
1508
|
}
|
|
1509
|
+
if (skillsBrowser) {
|
|
1510
|
+
if (key.escape) {
|
|
1511
|
+
setSkillsBrowser(undefined);
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
if (key.upArrow) {
|
|
1515
|
+
setSkillsBrowser((current) => current ? movePagedSelection(current, -1) : current);
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
if (key.downArrow) {
|
|
1519
|
+
setSkillsBrowser((current) => current ? movePagedSelection(current, 1) : current);
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
if (key.leftArrow || key.pageUp) {
|
|
1523
|
+
setSkillsBrowser((current) => current ? movePagedPage(current, -1) : current);
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
if (key.rightArrow || key.pageDown) {
|
|
1527
|
+
setSkillsBrowser((current) => current ? movePagedPage(current, 1) : current);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const selected = skillsBrowser.skills[pagedAbsoluteIndex(skillsBrowser)];
|
|
1531
|
+
if (key.return && selected) {
|
|
1532
|
+
setSkillsBrowser(undefined);
|
|
1533
|
+
append(systemLine(formatSkillDetails(selected), EXPANDED_SUMMARY_MAX_LINES));
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
if (value.toLowerCase() === "i" && selected) {
|
|
1537
|
+
setSkillsBrowser(undefined);
|
|
1538
|
+
setPromptState(`/skill ${selected.name} `, selected.name.length + 8);
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
if ((key.delete || key.backspace || value.toLowerCase() === "d") && selected) {
|
|
1542
|
+
void handleSkillDeleteByName(selected.name, runtime).then((line) => {
|
|
1543
|
+
append(line);
|
|
1544
|
+
void refreshSkillCompletions();
|
|
1545
|
+
void handleSkillsCommand(runtime, setSkillsBrowser, (nextLine) => append(nextLine));
|
|
1546
|
+
});
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
if (value.toLowerCase() === "a") {
|
|
1550
|
+
setSkillsBrowser(undefined);
|
|
1551
|
+
setPromptState("/skill import ", 14);
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
if (secretsBrowser) {
|
|
1557
|
+
if (key.escape) {
|
|
1558
|
+
setSecretsBrowser(undefined);
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
if (key.upArrow) {
|
|
1562
|
+
setSecretsBrowser((current) => current ? movePagedSelection(current, -1) : current);
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
if (key.downArrow) {
|
|
1566
|
+
setSecretsBrowser((current) => current ? movePagedSelection(current, 1) : current);
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (key.leftArrow || key.pageUp) {
|
|
1570
|
+
setSecretsBrowser((current) => current ? movePagedPage(current, -1) : current);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
if (key.rightArrow || key.pageDown) {
|
|
1574
|
+
setSecretsBrowser((current) => current ? movePagedPage(current, 1) : current);
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const selected = secretsBrowser.secrets[pagedAbsoluteIndex(secretsBrowser)];
|
|
1578
|
+
if (key.return && selected) {
|
|
1579
|
+
setSecretsBrowser(undefined);
|
|
1580
|
+
void handleSecretCommand({ type: "secret", action: "info", key: selected.key }, runtime).then((line) => append(line));
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
if (value.toLowerCase() === "s" && selected) {
|
|
1584
|
+
setSecretsBrowser(undefined);
|
|
1585
|
+
setPromptState(`/secret set ${selected.key} `, selected.key.length + 13);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
if (value.toLowerCase() === "r" && selected) {
|
|
1589
|
+
setSecretsBrowser(undefined);
|
|
1590
|
+
setPromptState(`/secret rename ${selected.key} `, selected.key.length + 16);
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
if ((key.delete || key.backspace || value.toLowerCase() === "d") && selected) {
|
|
1594
|
+
void handleSecretCommand({ type: "secret", action: "delete", key: selected.key }, runtime).then((line) => {
|
|
1595
|
+
append(line);
|
|
1596
|
+
void refreshSecretCompletions();
|
|
1597
|
+
void handleSecretsCommand(runtime, setSecretsBrowser, (nextLine) => append(nextLine));
|
|
1598
|
+
}).catch((error) => append({ kind: "error", text: error instanceof Error ? error.message : String(error) }));
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
if (value.toLowerCase() === "a") {
|
|
1602
|
+
setSecretsBrowser(undefined);
|
|
1603
|
+
setPromptState("/secret set ", 12);
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (value.toLowerCase() === "e") {
|
|
1607
|
+
setSecretsBrowser(undefined);
|
|
1608
|
+
setPromptState("/secret request ", 16);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1248
1613
|
if (key.return) {
|
|
1249
1614
|
const currentText = inputRef.current;
|
|
1250
1615
|
const currentCursor = cursorRef.current;
|
|
1251
|
-
const completion = selectedSlashCommandCompletion(currentText, currentCursor, slashCompletionIndexRef.current);
|
|
1616
|
+
const completion = selectedSlashCommandCompletion(currentText, currentCursor, slashCompletionIndexRef.current, skillCompletions, secretCompletions);
|
|
1252
1617
|
if (completion !== undefined && completion.kind === "command" && completion.arguments !== "none") {
|
|
1253
1618
|
const nextText = `${completion.insertText} ${currentText.slice(currentCursor)}`;
|
|
1254
1619
|
setPromptState(nextText, completion.insertText.length + 1);
|
|
1255
1620
|
return;
|
|
1256
1621
|
}
|
|
1622
|
+
if (currentText.trimEnd() === "/skill" || currentText.trimEnd() === "/secret") {
|
|
1623
|
+
void submitLine(currentText);
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
if (completion !== undefined && (completion.kind === "skill-action" || completion.kind === "secret-action")) {
|
|
1627
|
+
const nextText = `${completion.insertText}${currentText.slice(currentCursor)}`;
|
|
1628
|
+
setPromptState(nextText, completion.insertText.length);
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1257
1631
|
if (currentText.trimEnd() === "/model" && completion?.kind !== "command") {
|
|
1258
1632
|
void submitLine(currentText);
|
|
1259
1633
|
return;
|
|
@@ -1274,7 +1648,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1274
1648
|
return;
|
|
1275
1649
|
}
|
|
1276
1650
|
if (key.leftArrow) {
|
|
1277
|
-
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current);
|
|
1651
|
+
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions, secretCompletions);
|
|
1278
1652
|
if (completionCount > SLASH_COMPLETION_PAGE_SIZE) {
|
|
1279
1653
|
setSlashCompletionSelection((slashCompletionIndexRef.current + completionCount - SLASH_COMPLETION_PAGE_SIZE) % completionCount);
|
|
1280
1654
|
return;
|
|
@@ -1287,7 +1661,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1287
1661
|
return;
|
|
1288
1662
|
}
|
|
1289
1663
|
if (key.rightArrow) {
|
|
1290
|
-
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current);
|
|
1664
|
+
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions, secretCompletions);
|
|
1291
1665
|
if (completionCount > SLASH_COMPLETION_PAGE_SIZE) {
|
|
1292
1666
|
setSlashCompletionSelection((slashCompletionIndexRef.current + SLASH_COMPLETION_PAGE_SIZE) % completionCount);
|
|
1293
1667
|
return;
|
|
@@ -1318,7 +1692,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1318
1692
|
setTipIndex((current) => current - 1);
|
|
1319
1693
|
return;
|
|
1320
1694
|
}
|
|
1321
|
-
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current);
|
|
1695
|
+
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions, secretCompletions);
|
|
1322
1696
|
if (completionCount > 0) {
|
|
1323
1697
|
setSlashCompletionSelection((slashCompletionIndexRef.current + completionCount - 1) % completionCount);
|
|
1324
1698
|
return;
|
|
@@ -1335,7 +1709,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1335
1709
|
setTipIndex((current) => current + 1);
|
|
1336
1710
|
return;
|
|
1337
1711
|
}
|
|
1338
|
-
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current);
|
|
1712
|
+
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions, secretCompletions);
|
|
1339
1713
|
if (completionCount > 0) {
|
|
1340
1714
|
setSlashCompletionSelection((slashCompletionIndexRef.current + 1) % completionCount);
|
|
1341
1715
|
return;
|
|
@@ -1361,7 +1735,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1361
1735
|
return;
|
|
1362
1736
|
}
|
|
1363
1737
|
const currentCursor = cursorRef.current;
|
|
1364
|
-
const completions = slashCommandCompletions(currentText, currentCursor);
|
|
1738
|
+
const completions = slashCommandCompletions(currentText, currentCursor, skillCompletions, secretCompletions);
|
|
1365
1739
|
const completion = completions[Math.min(slashCompletionIndexRef.current, completions.length - 1)];
|
|
1366
1740
|
if (completion !== undefined) {
|
|
1367
1741
|
const nextText = `${completion.insertText}${currentText.slice(currentCursor)}`;
|
|
@@ -1374,7 +1748,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1374
1748
|
return;
|
|
1375
1749
|
}
|
|
1376
1750
|
});
|
|
1377
|
-
return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: dynamicLines, width, liveMaxLines: liveViewportLines, lineIndexOffset: staticLines.length, onMarkdownRenderComplete: markLineRendered }), sessionsBrowser ? e(SessionsBrowser, { state: sessionsBrowser, width }) : null, loginForm ? e(LoginFormView, { state: loginForm, width }) : null, e(StatusBar, { status, animationTick, width }), backgroundTasks.length > 0 ? e(BackgroundTaskStatusLine, { tasks: backgroundTasks, 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 }));
|
|
1751
|
+
return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: dynamicLines, 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, 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 }));
|
|
1378
1752
|
}
|
|
1379
1753
|
const MessageList = React.memo(function MessageList({ lines, width, liveMaxLines, lineIndexOffset = 0, onMarkdownRenderComplete }) {
|
|
1380
1754
|
const contentWidth = messageContentWidth(width);
|
|
@@ -1731,6 +2105,139 @@ function backgroundTaskStatusRenderRows(taskCount) {
|
|
|
1731
2105
|
return 0;
|
|
1732
2106
|
return 1 + Math.min(taskCount, 2);
|
|
1733
2107
|
}
|
|
2108
|
+
function ForegroundExecDetachHintLine({ handle, width: terminalWidth }) {
|
|
2109
|
+
const width = statusBarWidth(terminalWidth);
|
|
2110
|
+
const label = handle.description?.trim() || handle.command;
|
|
2111
|
+
const text = `↳ exec still running · Ctrl+B to detach · ${truncateMiddle(label, Math.max(12, width - 38))}`;
|
|
2112
|
+
return e(Text, { color: "yellow" }, fitToWidth(text, width));
|
|
2113
|
+
}
|
|
2114
|
+
function SubagentLivePanel({ activities, width: terminalWidth, terminalRows, animationTick }) {
|
|
2115
|
+
const width = statusBarWidth(terminalWidth);
|
|
2116
|
+
const rows = subagentLivePanelRenderRows(activities, terminalRows);
|
|
2117
|
+
if (rows <= 0)
|
|
2118
|
+
return null;
|
|
2119
|
+
const sorted = sortAgentActivitiesForPanel(activities);
|
|
2120
|
+
const selected = sorted[0];
|
|
2121
|
+
if (!selected)
|
|
2122
|
+
return null;
|
|
2123
|
+
const activeCount = activities.filter((activity) => activity.status === "running" || activity.status === "pending").length;
|
|
2124
|
+
const header = `◇ subagents: ${activeCount} active${activities.length > activeCount ? ` · ${activities.length - activeCount} recent` : ""} | auto: latest activity`;
|
|
2125
|
+
if (rows <= 1) {
|
|
2126
|
+
return e(Text, { color: "yellow" }, fitToWidth(`${header} | ${compactAgentSummary(selected, width - header.length - 3)}`, width));
|
|
2127
|
+
}
|
|
2128
|
+
const detailLines = buildSubagentDetailLines(selected, sorted, animationTick);
|
|
2129
|
+
return e(Box, { flexDirection: "column", width, overflow: "hidden" }, e(Text, { color: "yellow" }, fitToWidth(header, width)), ...detailLines.map((line, index) => e(Text, {
|
|
2130
|
+
key: `agent-detail-${selected.agentId}-${index}`,
|
|
2131
|
+
color: line.color,
|
|
2132
|
+
}, fitToWidth(line.text, width))));
|
|
2133
|
+
}
|
|
2134
|
+
const SUBAGENT_DETAIL_ROWS = 3;
|
|
2135
|
+
function subagentLivePanelRenderRows(activities, terminalRows) {
|
|
2136
|
+
if (activities.length === 0)
|
|
2137
|
+
return 0;
|
|
2138
|
+
if (terminalRows < 18)
|
|
2139
|
+
return 1;
|
|
2140
|
+
return 1 + SUBAGENT_DETAIL_ROWS;
|
|
2141
|
+
}
|
|
2142
|
+
function sortAgentActivitiesForPanel(activities) {
|
|
2143
|
+
const rank = (status) => {
|
|
2144
|
+
if (status === "running")
|
|
2145
|
+
return 0;
|
|
2146
|
+
if (status === "pending")
|
|
2147
|
+
return 1;
|
|
2148
|
+
if (status === "failed" || status === "killed")
|
|
2149
|
+
return 2;
|
|
2150
|
+
return 3;
|
|
2151
|
+
};
|
|
2152
|
+
return [...activities].sort((left, right) => rank(left.status) - rank(right.status) || right.updatedAt.localeCompare(left.updatedAt));
|
|
2153
|
+
}
|
|
2154
|
+
function buildSubagentDetailLines(selected, sorted, animationTick) {
|
|
2155
|
+
const spinner = selected.status === "running" ? spinnerFrame(animationTick) : statusGlyph(selected.status);
|
|
2156
|
+
const elapsed = formatElapsed(Date.now() - new Date(selected.startedAt).getTime());
|
|
2157
|
+
const headerLine = `${spinner} ${selected.description || selected.agentId} · ${agentModeLabel(selected)} · ${selected.agentType} · ${selected.status} · ${elapsed}`;
|
|
2158
|
+
const currentLine = selected.currentTool
|
|
2159
|
+
? `→ ${selected.currentTool.name}${selected.currentTool.inputPreview ? ` · ${selected.currentTool.inputPreview}` : ""}`
|
|
2160
|
+
: selected.error
|
|
2161
|
+
? `✖ ${selected.error}`
|
|
2162
|
+
: selected.resultPreview
|
|
2163
|
+
? `✓ ${selected.resultPreview}`
|
|
2164
|
+
: selected.lastText
|
|
2165
|
+
? `• ${selected.lastText}`
|
|
2166
|
+
: `• ${selected.prompt}`;
|
|
2167
|
+
const recent = selected.timeline.slice(-2).map((entry) => `${timelinePrefix(entry)} ${formatTimelineEntry(entry, 240)}`);
|
|
2168
|
+
const otherRunning = sorted
|
|
2169
|
+
.filter((activity) => activity.agentId !== selected.agentId && (activity.status === "running" || activity.status === "pending"))
|
|
2170
|
+
.slice(0, 2)
|
|
2171
|
+
.map((activity) => compactAgentSummary(activity, 180));
|
|
2172
|
+
const tail = [...recent, ...otherRunning.map((summary) => `· ${summary}`)].find((line) => line.trim()) ?? `tools:${selected.totalToolUseCount}`;
|
|
2173
|
+
return [
|
|
2174
|
+
{ text: headerLine, color: statusColor(selected.status) },
|
|
2175
|
+
{ text: currentLine, color: selected.error ? "red" : selected.currentTool ? "#d4b04c" : "yellow" },
|
|
2176
|
+
{ text: tail, color: "gray" },
|
|
2177
|
+
];
|
|
2178
|
+
}
|
|
2179
|
+
function compactAgentSummary(activity, maxLength) {
|
|
2180
|
+
const current = activity.currentTool
|
|
2181
|
+
? `${activity.currentTool.name}${activity.currentTool.inputPreview ? ` ${activity.currentTool.inputPreview}` : ""}`
|
|
2182
|
+
: activity.lastText ?? activity.resultPreview ?? activity.error ?? activity.prompt;
|
|
2183
|
+
return truncateMiddle(`${activity.description || activity.agentId} · ${agentModeLabel(activity)} · ${activity.status} · tools:${activity.totalToolUseCount} · ${current.replace(/\s+/g, " ")}`, Math.max(8, maxLength));
|
|
2184
|
+
}
|
|
2185
|
+
function agentModeLabel(activity) {
|
|
2186
|
+
if (activity.mode === "explore")
|
|
2187
|
+
return "explore";
|
|
2188
|
+
return activity.mode;
|
|
2189
|
+
}
|
|
2190
|
+
function formatTimelineEntry(entry, maxLength) {
|
|
2191
|
+
const detail = entry.detail ? ` · ${entry.detail.replace(/\s+/g, " ")}` : "";
|
|
2192
|
+
return truncateMiddle(`${entry.title}${detail}`, Math.max(8, maxLength));
|
|
2193
|
+
}
|
|
2194
|
+
function timelinePrefix(entry) {
|
|
2195
|
+
if (entry.kind === "tool_start")
|
|
2196
|
+
return "→";
|
|
2197
|
+
if (entry.kind === "tool_result")
|
|
2198
|
+
return entry.status === "failed" ? "✖" : "←";
|
|
2199
|
+
if (entry.kind === "thinking")
|
|
2200
|
+
return "◆";
|
|
2201
|
+
if (entry.kind === "error")
|
|
2202
|
+
return "✖";
|
|
2203
|
+
if (entry.kind === "status")
|
|
2204
|
+
return "•";
|
|
2205
|
+
return "assistant:";
|
|
2206
|
+
}
|
|
2207
|
+
function timelineColor(entry) {
|
|
2208
|
+
if (entry.status === "failed" || entry.kind === "error")
|
|
2209
|
+
return "red";
|
|
2210
|
+
if (entry.kind === "tool_start" || entry.kind === "tool_result")
|
|
2211
|
+
return "#d4b04c";
|
|
2212
|
+
if (entry.kind === "thinking")
|
|
2213
|
+
return THINKING_COLOR;
|
|
2214
|
+
if (entry.kind === "status")
|
|
2215
|
+
return "gray";
|
|
2216
|
+
return "green";
|
|
2217
|
+
}
|
|
2218
|
+
function statusGlyph(status) {
|
|
2219
|
+
if (status === "completed")
|
|
2220
|
+
return "✓";
|
|
2221
|
+
if (status === "failed")
|
|
2222
|
+
return "✖";
|
|
2223
|
+
if (status === "killed")
|
|
2224
|
+
return "■";
|
|
2225
|
+
if (status === "pending")
|
|
2226
|
+
return "…";
|
|
2227
|
+
return "●";
|
|
2228
|
+
}
|
|
2229
|
+
function statusColor(status) {
|
|
2230
|
+
if (status === "completed")
|
|
2231
|
+
return "green";
|
|
2232
|
+
if (status === "failed" || status === "killed")
|
|
2233
|
+
return "red";
|
|
2234
|
+
if (status === "pending")
|
|
2235
|
+
return "gray";
|
|
2236
|
+
return "yellow";
|
|
2237
|
+
}
|
|
2238
|
+
function spinnerFrame(tick) {
|
|
2239
|
+
return ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"][tick % 10] ?? "●";
|
|
2240
|
+
}
|
|
1734
2241
|
function BackgroundTaskStatusLine({ tasks, width: terminalWidth }) {
|
|
1735
2242
|
const width = statusBarWidth(terminalWidth);
|
|
1736
2243
|
const summary = `◇ background tools: ${tasks.length} task${tasks.length === 1 ? "" : "s"}`;
|
|
@@ -1806,7 +2313,21 @@ function fitStatusSegments(segments, width) {
|
|
|
1806
2313
|
const SLASH_COMPLETION_PAGE_SIZE = 10;
|
|
1807
2314
|
const MODEL_REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
1808
2315
|
const MODEL_REASONING_CONTROL_CHOICES = ["default", "off"];
|
|
1809
|
-
|
|
2316
|
+
const SKILL_COMMAND_ACTIONS = [
|
|
2317
|
+
{ name: "list", description: "Open the skill management browser", aliases: ["ls"] },
|
|
2318
|
+
{ name: "import", description: "Import by linking a skill directory" },
|
|
2319
|
+
{ name: "delete", description: "Delete a workspace skill link/directory", aliases: ["remove", "rm"] },
|
|
2320
|
+
];
|
|
2321
|
+
const SECRET_COMMAND_ACTIONS = [
|
|
2322
|
+
{ name: "list", description: "List secret keys/status/length; add --show to print values" },
|
|
2323
|
+
{ name: "get", description: "Print one secret value in the REPL" },
|
|
2324
|
+
{ name: "set", description: "Set a plaintext secret value" },
|
|
2325
|
+
{ name: "request", description: "Create an empty placeholder secret", aliases: ["empty"] },
|
|
2326
|
+
{ name: "info", description: "Show one secret's metadata" },
|
|
2327
|
+
{ name: "rename", description: "Rename a secret key", aliases: ["mv"] },
|
|
2328
|
+
{ name: "delete", description: "Delete a secret", aliases: ["remove", "rm"] },
|
|
2329
|
+
];
|
|
2330
|
+
function slashCommandCompletions(text, cursor, skills = [], secrets = []) {
|
|
1810
2331
|
const safeCursor = Math.max(0, Math.min(cursor, text.length));
|
|
1811
2332
|
const prefix = text.slice(0, safeCursor);
|
|
1812
2333
|
if (!prefix.startsWith("/") || /\r|\n/.test(prefix))
|
|
@@ -1819,6 +2340,12 @@ function slashCommandCompletions(text, cursor) {
|
|
|
1819
2340
|
if (prefix.startsWith("/model") && (prefix.length === "/model".length || prefix["/model".length] === " ")) {
|
|
1820
2341
|
return modelCommandCompletions(prefix);
|
|
1821
2342
|
}
|
|
2343
|
+
if (prefix.startsWith("/skill") && (prefix.length === "/skill".length || prefix["/skill".length] === " ")) {
|
|
2344
|
+
return skillCommandCompletions(prefix, skills);
|
|
2345
|
+
}
|
|
2346
|
+
if (prefix.startsWith("/secret") && (prefix.length === "/secret".length || prefix["/secret".length] === " ")) {
|
|
2347
|
+
return secretCommandCompletions(prefix, secrets);
|
|
2348
|
+
}
|
|
1822
2349
|
if (prefix.length > 1 && !/^\/[\w-]*$/.test(prefix))
|
|
1823
2350
|
return [];
|
|
1824
2351
|
const normalizedPrefix = prefix.toLowerCase();
|
|
@@ -1826,6 +2353,137 @@ function slashCommandCompletions(text, cursor) {
|
|
|
1826
2353
|
.flatMap((command) => [command.name, ...(command.aliases ?? [])].map((name) => ({ value: name, insertText: name, description: command.description, arguments: command.arguments, kind: "command" })))
|
|
1827
2354
|
.filter((command) => command.value.toLowerCase().startsWith(normalizedPrefix));
|
|
1828
2355
|
}
|
|
2356
|
+
function skillCommandCompletions(prefix, skills) {
|
|
2357
|
+
const hasTrailingSpace = /\s$/.test(prefix);
|
|
2358
|
+
const tokens = prefix.trim().split(/\s+/).filter(Boolean);
|
|
2359
|
+
const argumentTokens = tokens.slice(1);
|
|
2360
|
+
if (!hasTrailingSpace && argumentTokens.length === 0 && !"/skill".startsWith(prefix.toLowerCase()))
|
|
2361
|
+
return [];
|
|
2362
|
+
if (argumentTokens.length === 0)
|
|
2363
|
+
return skillActionCompletions("");
|
|
2364
|
+
const [first = "", second = ""] = argumentTokens;
|
|
2365
|
+
if (first === "list" || first === "ls" || first === "import")
|
|
2366
|
+
return [];
|
|
2367
|
+
if (first === "delete" || first === "remove" || first === "rm") {
|
|
2368
|
+
if (argumentTokens.length > 1 && hasTrailingSpace)
|
|
2369
|
+
return [];
|
|
2370
|
+
return skillNameCompletions(skills, hasTrailingSpace ? "" : second, "delete");
|
|
2371
|
+
}
|
|
2372
|
+
if (argumentTokens.length > 1 || hasTrailingSpace)
|
|
2373
|
+
return [];
|
|
2374
|
+
return skillActionCompletions(first);
|
|
2375
|
+
}
|
|
2376
|
+
function skillActionCompletions(current) {
|
|
2377
|
+
return SKILL_COMMAND_ACTIONS
|
|
2378
|
+
.flatMap((action) => [action.name, ...("aliases" in action ? action.aliases ?? [] : [])].map((name) => ({ name, description: action.description })))
|
|
2379
|
+
.filter((action) => action.name.startsWith(current.toLowerCase()))
|
|
2380
|
+
.map((action) => ({
|
|
2381
|
+
value: action.name,
|
|
2382
|
+
insertText: action.name === "list" || action.name === "ls" ? `/skill ${action.name}` : `/skill ${action.name} `,
|
|
2383
|
+
description: action.description,
|
|
2384
|
+
arguments: "optional",
|
|
2385
|
+
kind: "skill-action",
|
|
2386
|
+
}));
|
|
2387
|
+
}
|
|
2388
|
+
function skillNameCompletions(skills, current, action) {
|
|
2389
|
+
return skills
|
|
2390
|
+
.filter((skill) => skill.name.toLowerCase().includes(current.toLowerCase()))
|
|
2391
|
+
.map((skill) => ({
|
|
2392
|
+
value: skill.name,
|
|
2393
|
+
insertText: action === "delete" ? `/skill delete ${skill.name}` : `/skill ${skill.name}`,
|
|
2394
|
+
description: formatSkillCompletionDescription(skill),
|
|
2395
|
+
arguments: "optional",
|
|
2396
|
+
kind: "skill",
|
|
2397
|
+
}));
|
|
2398
|
+
}
|
|
2399
|
+
function formatSkillCompletionDescription(skill) {
|
|
2400
|
+
const tags = skill.tags?.length ? ` · ${skill.tags.join(",")}` : "";
|
|
2401
|
+
return `${skill.description}${skill.execution ? ` · ${skill.execution}` : ""}${tags}`;
|
|
2402
|
+
}
|
|
2403
|
+
function secretCommandCompletions(prefix, secrets) {
|
|
2404
|
+
const hasTrailingSpace = /\s$/.test(prefix);
|
|
2405
|
+
const tokens = prefix.trim().split(/\s+/).filter(Boolean);
|
|
2406
|
+
const argumentTokens = tokens.slice(1);
|
|
2407
|
+
if (!hasTrailingSpace && argumentTokens.length === 0 && !"/secret".startsWith(prefix.toLowerCase()))
|
|
2408
|
+
return [];
|
|
2409
|
+
if (argumentTokens.length === 0)
|
|
2410
|
+
return secretActionCompletions("");
|
|
2411
|
+
const [action = "", key = "", newKey = ""] = argumentTokens;
|
|
2412
|
+
const normalizedAction = secretCanonicalAction(action);
|
|
2413
|
+
if (!normalizedAction) {
|
|
2414
|
+
if (argumentTokens.length > 1 || hasTrailingSpace)
|
|
2415
|
+
return [];
|
|
2416
|
+
return secretActionCompletions(action);
|
|
2417
|
+
}
|
|
2418
|
+
if (normalizedAction === "list") {
|
|
2419
|
+
if (argumentTokens.length === 1 && hasTrailingSpace)
|
|
2420
|
+
return [{ value: "--show", insertText: "/secret list --show", description: "Print plaintext values in the REPL", arguments: "optional", kind: "secret-action" }];
|
|
2421
|
+
if (argumentTokens.length === 2 && !hasTrailingSpace)
|
|
2422
|
+
return "--show".startsWith(key) ? [{ value: "--show", insertText: "/secret list --show", description: "Print plaintext values in the REPL", arguments: "optional", kind: "secret-action" }] : [];
|
|
2423
|
+
return [];
|
|
2424
|
+
}
|
|
2425
|
+
if (normalizedAction === "set" || normalizedAction === "request") {
|
|
2426
|
+
if (argumentTokens.length <= 1 && hasTrailingSpace)
|
|
2427
|
+
return [];
|
|
2428
|
+
return [];
|
|
2429
|
+
}
|
|
2430
|
+
if (normalizedAction === "rename") {
|
|
2431
|
+
if (argumentTokens.length <= 1)
|
|
2432
|
+
return hasTrailingSpace ? secretKeyCompletions(secrets, "", normalizedAction) : [];
|
|
2433
|
+
if (argumentTokens.length === 2 && !hasTrailingSpace)
|
|
2434
|
+
return secretKeyCompletions(secrets, key, normalizedAction);
|
|
2435
|
+
if (argumentTokens.length === 2 && hasTrailingSpace)
|
|
2436
|
+
return [];
|
|
2437
|
+
if (argumentTokens.length === 3 && !hasTrailingSpace && newKey)
|
|
2438
|
+
return [];
|
|
2439
|
+
return [];
|
|
2440
|
+
}
|
|
2441
|
+
if (normalizedAction === "get" || normalizedAction === "info" || normalizedAction === "delete") {
|
|
2442
|
+
if (argumentTokens.length <= 1)
|
|
2443
|
+
return hasTrailingSpace ? secretKeyCompletions(secrets, "", normalizedAction) : [];
|
|
2444
|
+
if (argumentTokens.length === 2 && !hasTrailingSpace)
|
|
2445
|
+
return secretKeyCompletions(secrets, key, normalizedAction);
|
|
2446
|
+
return [];
|
|
2447
|
+
}
|
|
2448
|
+
return [];
|
|
2449
|
+
}
|
|
2450
|
+
function secretCanonicalAction(action) {
|
|
2451
|
+
const lower = action.toLowerCase();
|
|
2452
|
+
if (lower === "ls")
|
|
2453
|
+
return "list";
|
|
2454
|
+
if (lower === "show")
|
|
2455
|
+
return "get";
|
|
2456
|
+
if (lower === "empty")
|
|
2457
|
+
return "request";
|
|
2458
|
+
if (lower === "mv")
|
|
2459
|
+
return "rename";
|
|
2460
|
+
if (lower === "remove" || lower === "rm")
|
|
2461
|
+
return "delete";
|
|
2462
|
+
return ["list", "get", "set", "request", "info", "rename", "delete"].includes(lower) ? lower : undefined;
|
|
2463
|
+
}
|
|
2464
|
+
function secretActionCompletions(current) {
|
|
2465
|
+
return SECRET_COMMAND_ACTIONS
|
|
2466
|
+
.flatMap((action) => [action.name, ...("aliases" in action ? action.aliases ?? [] : [])].map((name) => ({ name, description: action.description })))
|
|
2467
|
+
.filter((action) => action.name.startsWith(current.toLowerCase()))
|
|
2468
|
+
.map((action) => ({
|
|
2469
|
+
value: action.name,
|
|
2470
|
+
insertText: `/secret ${action.name} `,
|
|
2471
|
+
description: action.description,
|
|
2472
|
+
arguments: "optional",
|
|
2473
|
+
kind: "secret-action",
|
|
2474
|
+
}));
|
|
2475
|
+
}
|
|
2476
|
+
function secretKeyCompletions(secrets, current, action) {
|
|
2477
|
+
return secrets
|
|
2478
|
+
.filter((secret) => secret.key.toLowerCase().includes(current.toLowerCase()))
|
|
2479
|
+
.map((secret) => ({
|
|
2480
|
+
value: secret.key,
|
|
2481
|
+
insertText: `/secret ${action} ${secret.key}${action === "rename" ? " " : ""}`,
|
|
2482
|
+
description: `${secret.status} · length=${secret.length}${secret.requestReason ? ` · ${secret.requestReason}` : ""}`,
|
|
2483
|
+
arguments: "optional",
|
|
2484
|
+
kind: "secret-key",
|
|
2485
|
+
}));
|
|
2486
|
+
}
|
|
1829
2487
|
function modelCommandCompletions(prefix) {
|
|
1830
2488
|
const hasTrailingSpace = /\s$/.test(prefix);
|
|
1831
2489
|
const tokens = prefix.trim().split(/\s+/).filter(Boolean);
|
|
@@ -1911,11 +2569,11 @@ function slashCompletionViewHeight(completions) {
|
|
|
1911
2569
|
return 0;
|
|
1912
2570
|
return Math.min(completions.length, SLASH_COMPLETION_PAGE_SIZE) + 2;
|
|
1913
2571
|
}
|
|
1914
|
-
function slashCompletionSelectableCount(text, cursor) {
|
|
1915
|
-
return slashCommandCompletions(text, cursor).length;
|
|
2572
|
+
function slashCompletionSelectableCount(text, cursor, skills = [], secrets = []) {
|
|
2573
|
+
return slashCommandCompletions(text, cursor, skills, secrets).length;
|
|
1916
2574
|
}
|
|
1917
|
-
function selectedSlashCommandCompletion(text, cursor, selectedIndex) {
|
|
1918
|
-
const completions = slashCommandCompletions(text, cursor);
|
|
2575
|
+
function selectedSlashCommandCompletion(text, cursor, selectedIndex, skills = [], secrets = []) {
|
|
2576
|
+
const completions = slashCommandCompletions(text, cursor, skills, secrets);
|
|
1919
2577
|
if (completions.length === 0)
|
|
1920
2578
|
return undefined;
|
|
1921
2579
|
return completions[Math.max(0, Math.min(selectedIndex, completions.length - 1))];
|
|
@@ -1988,6 +2646,188 @@ function SlashCompletionLines({ completions, width, prompt, selectedIndex }) {
|
|
|
1988
2646
|
e(Text, { key: "slash-completion-footer", color: "gray" }, fitToWidth(footer, contentWidth)),
|
|
1989
2647
|
].map((line, index) => e(Box, { key: `slash-completion-line-${index}`, height: 1, overflow: "hidden" }, e(Text, { color: "gray" }, " ".repeat(prompt.length)), line));
|
|
1990
2648
|
}
|
|
2649
|
+
async function handleSecretCommand(command, runtime) {
|
|
2650
|
+
const usage = "Usage: /secret <list|get|set|request|delete|rename|info> ...";
|
|
2651
|
+
const action = command.action ?? "list";
|
|
2652
|
+
const requireKey = () => {
|
|
2653
|
+
if (!command.key)
|
|
2654
|
+
throw new Error(usage);
|
|
2655
|
+
return command.key;
|
|
2656
|
+
};
|
|
2657
|
+
if (action === "list") {
|
|
2658
|
+
const entries = await runtime.secretStore.list();
|
|
2659
|
+
if (entries.length === 0)
|
|
2660
|
+
return systemLine("No secrets stored.");
|
|
2661
|
+
const lines = await Promise.all(entries.map(async (entry) => {
|
|
2662
|
+
if (command.show) {
|
|
2663
|
+
const value = entry.status === "set" ? await runtime.secretStore.getPlaintext(entry.key) : "";
|
|
2664
|
+
return `${entry.key} = ${value}`;
|
|
2665
|
+
}
|
|
2666
|
+
const reason = entry.requestReason ? ` reason=${JSON.stringify(entry.requestReason)}` : "";
|
|
2667
|
+
return `${entry.key}\t${entry.status}\tlength=${entry.length}${reason}`;
|
|
2668
|
+
}));
|
|
2669
|
+
return systemLine(lines.join("\n"), EXPANDED_SUMMARY_MAX_LINES);
|
|
2670
|
+
}
|
|
2671
|
+
if (action === "get") {
|
|
2672
|
+
const key = requireKey();
|
|
2673
|
+
const info = await runtime.secretStore.info(key);
|
|
2674
|
+
if (!info)
|
|
2675
|
+
return systemLine(`Secret "${key}" does not exist.`);
|
|
2676
|
+
const value = await runtime.secretStore.getPlaintext(key);
|
|
2677
|
+
return systemLine(info.status === "empty" ? `Secret "${key}" is empty.` : value, EXPANDED_SUMMARY_MAX_LINES);
|
|
2678
|
+
}
|
|
2679
|
+
if (action === "set") {
|
|
2680
|
+
const key = requireKey();
|
|
2681
|
+
const meta = await runtime.secretStore.setPlaintext(key, command.value ?? "");
|
|
2682
|
+
return systemLine(`Secret "${meta.key}" saved, status=${meta.status}, length=${meta.length}.`);
|
|
2683
|
+
}
|
|
2684
|
+
if (action === "request" || action === "empty") {
|
|
2685
|
+
const key = requireKey();
|
|
2686
|
+
const meta = await runtime.secretStore.requestEmpty(key, { reason: command.reason, requestedBy: "user" });
|
|
2687
|
+
return systemLine(`Secret "${meta.key}" is ${meta.status}. Fill it with: /secret set ${meta.key} <value>`);
|
|
2688
|
+
}
|
|
2689
|
+
if (action === "delete") {
|
|
2690
|
+
const key = requireKey();
|
|
2691
|
+
const deleted = await runtime.secretStore.delete(key);
|
|
2692
|
+
return systemLine(deleted ? `Secret "${key}" deleted.` : `Secret "${key}" did not exist.`);
|
|
2693
|
+
}
|
|
2694
|
+
if (action === "rename") {
|
|
2695
|
+
const key = requireKey();
|
|
2696
|
+
if (!command.newKey)
|
|
2697
|
+
throw new Error("Usage: /secret rename <oldKey> <newKey>");
|
|
2698
|
+
const meta = await runtime.secretStore.rename(key, command.newKey);
|
|
2699
|
+
return systemLine(`Secret renamed to "${meta.key}".`);
|
|
2700
|
+
}
|
|
2701
|
+
if (action === "info") {
|
|
2702
|
+
const key = requireKey();
|
|
2703
|
+
const info = await runtime.secretStore.info(key);
|
|
2704
|
+
return systemLine(info ? formatReplData(info, 4000) : `Secret "${key}" does not exist.`, EXPANDED_SUMMARY_MAX_LINES);
|
|
2705
|
+
}
|
|
2706
|
+
return systemLine(usage);
|
|
2707
|
+
}
|
|
2708
|
+
async function handleSkillCommand(command, runtime) {
|
|
2709
|
+
if (command.action === "import")
|
|
2710
|
+
return handleSkillImportCommand(command, runtime);
|
|
2711
|
+
if (command.action === "delete")
|
|
2712
|
+
return handleSkillDeleteCommand(command, runtime);
|
|
2713
|
+
if (!command.name) {
|
|
2714
|
+
const skills = await runtime.skills.list();
|
|
2715
|
+
return systemLine(formatSkillList(skills), EXPANDED_SUMMARY_MAX_LINES);
|
|
2716
|
+
}
|
|
2717
|
+
const skill = await runtime.skills.get(command.name);
|
|
2718
|
+
if (!skill)
|
|
2719
|
+
return { kind: "error", text: `Unknown skill: ${command.name}\nUse /skill to list available skills.` };
|
|
2720
|
+
return systemLine(formatSkillDetails(skill), EXPANDED_SUMMARY_MAX_LINES);
|
|
2721
|
+
}
|
|
2722
|
+
async function handleSkillImportCommand(command, runtime) {
|
|
2723
|
+
if (!command.path)
|
|
2724
|
+
return { kind: "error", text: "Usage: /skill import <path-to-skill-directory> [name]" };
|
|
2725
|
+
const sourceDirectory = path.resolve(command.path);
|
|
2726
|
+
const skillFile = path.join(sourceDirectory, "SKILL.md");
|
|
2727
|
+
try {
|
|
2728
|
+
const stat = await fs.stat(skillFile);
|
|
2729
|
+
if (!stat.isFile())
|
|
2730
|
+
return { kind: "error", text: `SKILL.md is not a file: ${skillFile}` };
|
|
2731
|
+
}
|
|
2732
|
+
catch (error) {
|
|
2733
|
+
return { kind: "error", text: `Invalid skill path: ${skillFile}\n${error instanceof Error ? error.message : String(error)}` };
|
|
2734
|
+
}
|
|
2735
|
+
const name = requireSkillName(command.name ?? path.basename(sourceDirectory));
|
|
2736
|
+
const linkPath = path.join(runtime.skillWorkspaceRoot, name);
|
|
2737
|
+
const relativeTarget = path.relative(path.dirname(linkPath), sourceDirectory) || sourceDirectory;
|
|
2738
|
+
try {
|
|
2739
|
+
await fs.mkdir(runtime.skillWorkspaceRoot, { recursive: true });
|
|
2740
|
+
const existing = await safeLstat(linkPath);
|
|
2741
|
+
if (existing)
|
|
2742
|
+
return { kind: "error", text: `Skill already exists at ${linkPath}. Delete it first with /skill delete ${name}.` };
|
|
2743
|
+
await fs.symlink(relativeTarget, linkPath, "junction");
|
|
2744
|
+
const imported = await runtime.skills.get(name);
|
|
2745
|
+
return systemLine(`Imported skill ${name}\nLink: ${linkPath}\nTarget: ${sourceDirectory}${imported ? `\nDescription: ${imported.description}` : ""}`);
|
|
2746
|
+
}
|
|
2747
|
+
catch (error) {
|
|
2748
|
+
return { kind: "error", text: `Failed to import skill ${name}: ${error instanceof Error ? error.message : String(error)}` };
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
async function handleSkillDeleteCommand(command, runtime) {
|
|
2752
|
+
if (!command.name)
|
|
2753
|
+
return { kind: "error", text: "Usage: /skill delete <name>" };
|
|
2754
|
+
return handleSkillDeleteByName(command.name, runtime);
|
|
2755
|
+
}
|
|
2756
|
+
async function handleSkillDeleteByName(nameInput, runtime) {
|
|
2757
|
+
const name = requireSkillName(nameInput);
|
|
2758
|
+
const skillPath = path.join(runtime.skillWorkspaceRoot, name);
|
|
2759
|
+
const existing = await safeLstat(skillPath);
|
|
2760
|
+
if (!existing)
|
|
2761
|
+
return { kind: "error", text: `No workspace skill named ${name} at ${skillPath}` };
|
|
2762
|
+
try {
|
|
2763
|
+
if (existing.isSymbolicLink())
|
|
2764
|
+
await fs.unlink(skillPath);
|
|
2765
|
+
else
|
|
2766
|
+
await fs.rm(skillPath, { recursive: true, force: true });
|
|
2767
|
+
return systemLine(`Deleted workspace skill ${name}: ${skillPath}`);
|
|
2768
|
+
}
|
|
2769
|
+
catch (error) {
|
|
2770
|
+
return { kind: "error", text: `Failed to delete skill ${name}: ${error instanceof Error ? error.message : String(error)}` };
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
async function safeLstat(file) {
|
|
2774
|
+
try {
|
|
2775
|
+
return await fs.lstat(file);
|
|
2776
|
+
}
|
|
2777
|
+
catch {
|
|
2778
|
+
return undefined;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
function renderSkillInvocationPrompt(name, args) {
|
|
2782
|
+
return `Use skill ${JSON.stringify(name)} with these arguments:\n${args}`;
|
|
2783
|
+
}
|
|
2784
|
+
function formatSkillList(skills) {
|
|
2785
|
+
if (skills.length === 0) {
|
|
2786
|
+
return [
|
|
2787
|
+
"No skills found.",
|
|
2788
|
+
"Skill roots:",
|
|
2789
|
+
" - .neo/skills/<name>/SKILL.md",
|
|
2790
|
+
" - ~/.neoctl/skills/<name>/SKILL.md",
|
|
2791
|
+
"Create one with the skill_create tool or add a SKILL.md file.",
|
|
2792
|
+
].join("\n");
|
|
2793
|
+
}
|
|
2794
|
+
const width = Math.max(...skills.map((skill) => skill.name.length));
|
|
2795
|
+
return [
|
|
2796
|
+
"Available skills:",
|
|
2797
|
+
...skills.map((skill) => {
|
|
2798
|
+
const tags = skill.tags?.length ? ` [${skill.tags.join(", ")}]` : "";
|
|
2799
|
+
const execution = skill.execution ? ` (${skill.execution})` : "";
|
|
2800
|
+
return ` ${skill.name.padEnd(width)} ${skill.description}${execution}${tags}`;
|
|
2801
|
+
}),
|
|
2802
|
+
"",
|
|
2803
|
+
"Usage:",
|
|
2804
|
+
" /skill <name> Show skill details",
|
|
2805
|
+
" /skill <name> <args> Invoke skill with arguments",
|
|
2806
|
+
" /skill import <path> [name] Import by linking a skill directory",
|
|
2807
|
+
" /skill delete <name> Delete workspace skill link/directory",
|
|
2808
|
+
].join("\n");
|
|
2809
|
+
}
|
|
2810
|
+
function formatSkillDetails(skill) {
|
|
2811
|
+
const lines = [
|
|
2812
|
+
`Skill: ${skill.name}`,
|
|
2813
|
+
skill.title ? `Title: ${skill.title}` : undefined,
|
|
2814
|
+
`Description: ${skill.description}`,
|
|
2815
|
+
`Execution: ${skill.execution}`,
|
|
2816
|
+
skill.version ? `Version: ${skill.version}` : undefined,
|
|
2817
|
+
skill.tags?.length ? `Tags: ${skill.tags.join(", ")}` : undefined,
|
|
2818
|
+
skill.allowedTools?.length ? `Allowed tools: ${skill.allowedTools.join(", ")}` : undefined,
|
|
2819
|
+
skill.model ? `Model: ${skill.model}` : undefined,
|
|
2820
|
+
skill.effort ? `Effort: ${skill.effort}` : undefined,
|
|
2821
|
+
skill.trustLevel ? `Trust: ${skill.trustLevel}` : undefined,
|
|
2822
|
+
skill.source?.path ? `Path: ${skill.source.path}` : undefined,
|
|
2823
|
+
"",
|
|
2824
|
+
"Entrypoint:",
|
|
2825
|
+
skill.entrypoint,
|
|
2826
|
+
"",
|
|
2827
|
+
`Invoke: /skill ${skill.name} <args>`,
|
|
2828
|
+
].filter((line) => line !== undefined);
|
|
2829
|
+
return lines.join("\n");
|
|
2830
|
+
}
|
|
1991
2831
|
async function handleModelCommand(command, runtime) {
|
|
1992
2832
|
const current = runtime.engine.getModelSettings();
|
|
1993
2833
|
const nextModel = command.model ?? current.model;
|
|
@@ -2069,10 +2909,6 @@ function currentModelProvider() {
|
|
|
2069
2909
|
function modelEnvKeyForProvider(provider) {
|
|
2070
2910
|
if (provider === "anthropic")
|
|
2071
2911
|
return "ANTHROPIC_MODEL";
|
|
2072
|
-
if (provider === "deepseek")
|
|
2073
|
-
return "DEEPSEEK_MODEL";
|
|
2074
|
-
if (provider === "kimi")
|
|
2075
|
-
return "KIMI_MODEL";
|
|
2076
2912
|
return "OPENAI_MODEL";
|
|
2077
2913
|
}
|
|
2078
2914
|
function envValueForReasoning(reasoning) {
|
|
@@ -2180,6 +3016,8 @@ function renderMessage(message, append, activeAssistantId, options = {}) {
|
|
|
2180
3016
|
rendered = true;
|
|
2181
3017
|
}
|
|
2182
3018
|
if (block.type === "thinking") {
|
|
3019
|
+
if (options.includeThinkingBlocks === false)
|
|
3020
|
+
continue;
|
|
2183
3021
|
append(thinkingLine(block.text));
|
|
2184
3022
|
rendered = true;
|
|
2185
3023
|
}
|
|
@@ -2323,7 +3161,25 @@ async function handleSessionsCommand(runtime, runningSessionIds, setBrowser, app
|
|
|
2323
3161
|
append(systemLine("No saved sessions found."));
|
|
2324
3162
|
return;
|
|
2325
3163
|
}
|
|
2326
|
-
setBrowser({ sessions, runningSessionIds, pageSize: SESSIONS_DEFAULT_PAGE_SIZE, pageIndex: 0, selectedIndex: 0 });
|
|
3164
|
+
setBrowser({ items: sessions, sessions, runningSessionIds, pageSize: SESSIONS_DEFAULT_PAGE_SIZE, pageIndex: 0, selectedIndex: 0 });
|
|
3165
|
+
}
|
|
3166
|
+
async function handleSkillsCommand(runtime, setBrowser, append) {
|
|
3167
|
+
const skills = await runtime.skills.list();
|
|
3168
|
+
if (skills.length === 0) {
|
|
3169
|
+
setBrowser(undefined);
|
|
3170
|
+
append(systemLine(formatSkillList(skills), EXPANDED_SUMMARY_MAX_LINES));
|
|
3171
|
+
return;
|
|
3172
|
+
}
|
|
3173
|
+
setBrowser({ items: skills, skills, pageSize: SESSIONS_DEFAULT_PAGE_SIZE, pageIndex: 0, selectedIndex: 0 });
|
|
3174
|
+
}
|
|
3175
|
+
async function handleSecretsCommand(runtime, setBrowser, append) {
|
|
3176
|
+
const secrets = await runtime.secretStore.list();
|
|
3177
|
+
if (secrets.length === 0) {
|
|
3178
|
+
setBrowser(undefined);
|
|
3179
|
+
append(systemLine("No secrets stored. Press /secret set <key> <value> to add one, or /secret request <key> to create an empty placeholder."));
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
setBrowser({ items: secrets, secrets, pageSize: SESSIONS_DEFAULT_PAGE_SIZE, pageIndex: 0, selectedIndex: 0 });
|
|
2327
3183
|
}
|
|
2328
3184
|
async function handleExportCommand(command, runtime) {
|
|
2329
3185
|
const snapshot = runtime.engine.snapshot();
|
|
@@ -2371,6 +3227,7 @@ async function handleDeleteSessionCommand(sessionId, current, runtime, setBrowse
|
|
|
2371
3227
|
const pageLength = nextSessions.slice(pageIndex * current.pageSize, pageIndex * current.pageSize + current.pageSize).length;
|
|
2372
3228
|
setBrowser({
|
|
2373
3229
|
...current,
|
|
3230
|
+
items: nextSessions,
|
|
2374
3231
|
sessions: nextSessions,
|
|
2375
3232
|
runningSessionIds: current.runningSessionIds.filter((id) => id !== sessionId),
|
|
2376
3233
|
pageIndex,
|
|
@@ -2408,11 +3265,11 @@ function restoredHistoryLines(runtime) {
|
|
|
2408
3265
|
return lines.length;
|
|
2409
3266
|
};
|
|
2410
3267
|
for (const message of runtime.engine.getHistoryMessages()) {
|
|
2411
|
-
renderMessage(message, append, undefined, { includeToolUseBlocks: true });
|
|
3268
|
+
renderMessage(message, append, undefined, { includeToolUseBlocks: true, includeThinkingBlocks: false });
|
|
2412
3269
|
}
|
|
2413
3270
|
return lines;
|
|
2414
3271
|
}
|
|
2415
|
-
const LOGIN_PROVIDERS = ["openai", "anthropic"
|
|
3272
|
+
const LOGIN_PROVIDERS = ["openai", "anthropic"];
|
|
2416
3273
|
const SHARED_LOGIN_FIELDS = [
|
|
2417
3274
|
{ key: "reasoningEffort", label: "Reasoning effort", envKey: "MODEL_REASONING_EFFORT", scope: "shared", options: ["", "off", "none", "minimal", "low", "medium", "high", "xhigh", "max"] },
|
|
2418
3275
|
{ key: "reasoningSummary", label: "Reasoning summary", envKey: "MODEL_REASONING_SUMMARY", scope: "shared", options: ["", "auto", "concise", "detailed"] },
|
|
@@ -2438,20 +3295,6 @@ const LOGIN_FIELD_DEFINITIONS = {
|
|
|
2438
3295
|
{ key: "version", label: "Anthropic version", envKey: "ANTHROPIC_VERSION", scope: "provider", placeholder: "2023-06-01" },
|
|
2439
3296
|
...SHARED_LOGIN_FIELDS,
|
|
2440
3297
|
],
|
|
2441
|
-
deepseek: [
|
|
2442
|
-
{ key: "apiKey", label: "API key", envKey: "DEEPSEEK_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
|
|
2443
|
-
{ key: "baseUrl", label: "Base URL", envKey: "DEEPSEEK_BASE_URL", scope: "provider", placeholder: "https://api.deepseek.com" },
|
|
2444
|
-
{ key: "model", label: "Model", envKey: "DEEPSEEK_MODEL", scope: "provider", required: true, placeholder: "deepseek-chat" },
|
|
2445
|
-
{ key: "fallbackModel", label: "Fallback model", envKey: "DEEPSEEK_FALLBACK_MODEL", scope: "provider" },
|
|
2446
|
-
...SHARED_LOGIN_FIELDS,
|
|
2447
|
-
],
|
|
2448
|
-
kimi: [
|
|
2449
|
-
{ key: "apiKey", label: "API key", envKey: "KIMI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
|
|
2450
|
-
{ key: "baseUrl", label: "Base URL", envKey: "KIMI_BASE_URL", scope: "provider", placeholder: "https://api.moonshot.cn/v1" },
|
|
2451
|
-
{ key: "model", label: "Model", envKey: "KIMI_MODEL", scope: "provider", required: true, placeholder: "kimi-k2.6" },
|
|
2452
|
-
{ key: "fallbackModel", label: "Fallback model", envKey: "KIMI_FALLBACK_MODEL", scope: "provider" },
|
|
2453
|
-
...SHARED_LOGIN_FIELDS,
|
|
2454
|
-
],
|
|
2455
3298
|
};
|
|
2456
3299
|
const DEPRECATED_MODEL_ENV_KEYS = [
|
|
2457
3300
|
"MODEL_API_KEY",
|
|
@@ -2472,55 +3315,55 @@ const DEPRECATED_MODEL_ENV_KEYS = [
|
|
|
2472
3315
|
"ANTHROPIC_TIMEOUT_MS",
|
|
2473
3316
|
"ANTHROPIC_STREAM_IDLE_TIMEOUT_MS",
|
|
2474
3317
|
"ANTHROPIC_MAX_RETRIES",
|
|
2475
|
-
"DEEPSEEK_REASONING_EFFORT",
|
|
2476
|
-
"DEEPSEEK_REASONING_SUMMARY",
|
|
2477
|
-
"DEEPSEEK_MAX_OUTPUT_TOKENS",
|
|
2478
|
-
"DEEPSEEK_TIMEOUT_MS",
|
|
2479
|
-
"DEEPSEEK_STREAM_IDLE_TIMEOUT_MS",
|
|
2480
|
-
"DEEPSEEK_MAX_RETRIES",
|
|
2481
|
-
"KIMI_REASONING_EFFORT",
|
|
2482
|
-
"KIMI_REASONING_SUMMARY",
|
|
2483
|
-
"KIMI_MAX_OUTPUT_TOKENS",
|
|
2484
|
-
"KIMI_TIMEOUT_MS",
|
|
2485
|
-
"KIMI_STREAM_IDLE_TIMEOUT_MS",
|
|
2486
|
-
"KIMI_MAX_RETRIES",
|
|
2487
|
-
"MOONSHOT_REASONING_EFFORT",
|
|
2488
|
-
"MOONSHOT_REASONING_SUMMARY",
|
|
2489
|
-
"MOONSHOT_MAX_OUTPUT_TOKENS",
|
|
2490
|
-
"MOONSHOT_TIMEOUT_MS",
|
|
2491
|
-
"MOONSHOT_STREAM_IDLE_TIMEOUT_MS",
|
|
2492
|
-
"MOONSHOT_MAX_RETRIES",
|
|
2493
3318
|
];
|
|
2494
|
-
function
|
|
2495
|
-
return Math.max(1, Math.ceil(state.
|
|
3319
|
+
function pagedPageCount(state) {
|
|
3320
|
+
return Math.max(1, Math.ceil(state.items.length / state.pageSize));
|
|
2496
3321
|
}
|
|
2497
|
-
function
|
|
3322
|
+
function pagedPageItems(state) {
|
|
2498
3323
|
const start = state.pageIndex * state.pageSize;
|
|
2499
|
-
return state.
|
|
3324
|
+
return state.items.slice(start, start + state.pageSize);
|
|
2500
3325
|
}
|
|
2501
|
-
function
|
|
3326
|
+
function pagedAbsoluteIndex(state) {
|
|
2502
3327
|
return state.pageIndex * state.pageSize + state.selectedIndex;
|
|
2503
3328
|
}
|
|
2504
|
-
function
|
|
2505
|
-
const pageLength =
|
|
3329
|
+
function movePagedSelection(state, delta) {
|
|
3330
|
+
const pageLength = pagedPageItems(state).length;
|
|
2506
3331
|
if (pageLength <= 0)
|
|
2507
3332
|
return state;
|
|
2508
3333
|
const selectedIndex = (state.selectedIndex + delta + pageLength) % pageLength;
|
|
2509
3334
|
return { ...state, selectedIndex };
|
|
2510
3335
|
}
|
|
2511
|
-
function
|
|
2512
|
-
const pageCount =
|
|
3336
|
+
function movePagedPage(state, delta) {
|
|
3337
|
+
const pageCount = pagedPageCount(state);
|
|
2513
3338
|
if (pageCount <= 1)
|
|
2514
3339
|
return state;
|
|
2515
3340
|
const pageIndex = (state.pageIndex + delta + pageCount) % pageCount;
|
|
2516
|
-
const pageLength = state.
|
|
3341
|
+
const pageLength = state.items.slice(pageIndex * state.pageSize, pageIndex * state.pageSize + state.pageSize).length;
|
|
2517
3342
|
return { ...state, pageIndex, selectedIndex: Math.min(state.selectedIndex, Math.max(0, pageLength - 1)) };
|
|
2518
3343
|
}
|
|
3344
|
+
function sessionsPageItems(state) {
|
|
3345
|
+
return pagedPageItems(state);
|
|
3346
|
+
}
|
|
3347
|
+
function sessionAbsoluteIndex(state) {
|
|
3348
|
+
return pagedAbsoluteIndex(state);
|
|
3349
|
+
}
|
|
3350
|
+
function moveSessionsSelection(state, delta) {
|
|
3351
|
+
return movePagedSelection(state, delta);
|
|
3352
|
+
}
|
|
3353
|
+
function moveSessionsPage(state, delta) {
|
|
3354
|
+
return movePagedPage(state, delta);
|
|
3355
|
+
}
|
|
2519
3356
|
function sessionsBrowserViewHeight(state) {
|
|
2520
3357
|
return sessionsPageItems(state).length + 3;
|
|
2521
3358
|
}
|
|
3359
|
+
function skillsBrowserViewHeight(state) {
|
|
3360
|
+
return pagedPageItems(state).length + 3;
|
|
3361
|
+
}
|
|
3362
|
+
function secretsBrowserViewHeight(state) {
|
|
3363
|
+
return pagedPageItems(state).length + 3;
|
|
3364
|
+
}
|
|
2522
3365
|
function SessionsBrowser({ state, width }) {
|
|
2523
|
-
const pageCount =
|
|
3366
|
+
const pageCount = pagedPageCount(state);
|
|
2524
3367
|
const pageItems = sessionsPageItems(state);
|
|
2525
3368
|
const showPagination = pageCount > 1;
|
|
2526
3369
|
const contentWidth = Math.max(20, width);
|
|
@@ -2540,6 +3383,51 @@ function SessionsBrowser({ state, width }) {
|
|
|
2540
3383
|
}, row.numberPrefix), row.rest);
|
|
2541
3384
|
}), e(Text, { color: "gray" }, fitToWidth(footer, contentWidth)));
|
|
2542
3385
|
}
|
|
3386
|
+
function SkillsBrowser({ state, width }) {
|
|
3387
|
+
const pageCount = pagedPageCount(state);
|
|
3388
|
+
const pageItems = pagedPageItems(state);
|
|
3389
|
+
const showPagination = pageCount > 1;
|
|
3390
|
+
const contentWidth = Math.max(20, width);
|
|
3391
|
+
const header = showPagination
|
|
3392
|
+
? `Skills (${state.skills.length}) · page ${state.pageIndex + 1}/${pageCount}`
|
|
3393
|
+
: `Skills (${state.skills.length})`;
|
|
3394
|
+
const footer = showPagination
|
|
3395
|
+
? "↑/↓ select · ←/→ page · Enter details · i invoke · a import · d/Delete remove · Esc close"
|
|
3396
|
+
: "↑/↓ select · Enter details · i invoke · a import · d/Delete remove · Esc close";
|
|
3397
|
+
const nameWidth = Math.min(28, Math.max(...pageItems.map((skill) => skill.name.length)));
|
|
3398
|
+
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(header, contentWidth)), ...pageItems.map((skill, index) => {
|
|
3399
|
+
const selected = index === state.selectedIndex;
|
|
3400
|
+
const absoluteIndex = state.pageIndex * state.pageSize + index;
|
|
3401
|
+
const prefix = `${absoluteIndex + 1}.`.padStart(String(state.skills.length).length + 1);
|
|
3402
|
+
const tags = skill.tags?.length ? ` [${skill.tags.join(",")}]` : "";
|
|
3403
|
+
const execution = skill.execution ? ` (${skill.execution})` : "";
|
|
3404
|
+
const restWidth = Math.max(0, contentWidth - prefix.length - nameWidth - 4);
|
|
3405
|
+
const rest = fitToWidth(`${skill.description}${execution}${tags}`, restWidth);
|
|
3406
|
+
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));
|
|
3407
|
+
}), e(Text, { color: "gray" }, fitToWidth(footer, contentWidth)));
|
|
3408
|
+
}
|
|
3409
|
+
function SecretsBrowser({ state, width }) {
|
|
3410
|
+
const pageCount = pagedPageCount(state);
|
|
3411
|
+
const pageItems = pagedPageItems(state);
|
|
3412
|
+
const showPagination = pageCount > 1;
|
|
3413
|
+
const contentWidth = Math.max(20, width);
|
|
3414
|
+
const header = showPagination
|
|
3415
|
+
? `Secrets (${state.secrets.length}) · page ${state.pageIndex + 1}/${pageCount}`
|
|
3416
|
+
: `Secrets (${state.secrets.length})`;
|
|
3417
|
+
const footer = showPagination
|
|
3418
|
+
? "↑/↓ select · ←/→ page · Enter info · s set · r rename · a add · e empty · d/Delete remove · Esc close"
|
|
3419
|
+
: "↑/↓ select · Enter info · s set · r rename · a add · e empty · d/Delete remove · Esc close";
|
|
3420
|
+
const keyWidth = Math.min(32, Math.max(...pageItems.map((secret) => secret.key.length)));
|
|
3421
|
+
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(header, contentWidth)), ...pageItems.map((secret, index) => {
|
|
3422
|
+
const selected = index === state.selectedIndex;
|
|
3423
|
+
const absoluteIndex = state.pageIndex * state.pageSize + index;
|
|
3424
|
+
const prefix = `${absoluteIndex + 1}.`.padStart(String(state.secrets.length).length + 1);
|
|
3425
|
+
const reason = secret.requestReason ? ` reason=${JSON.stringify(secret.requestReason)}` : "";
|
|
3426
|
+
const restWidth = Math.max(0, contentWidth - prefix.length - keyWidth - 4);
|
|
3427
|
+
const rest = fitToWidth(`${secret.status} · length=${secret.length}${reason}`, restWidth);
|
|
3428
|
+
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));
|
|
3429
|
+
}), e(Text, { color: "gray" }, fitToWidth(footer, contentWidth)));
|
|
3430
|
+
}
|
|
2543
3431
|
function handleLoginFormInput(value, key, state, setLoginFormState, runtime, append, setStatus) {
|
|
2544
3432
|
if (key.escape) {
|
|
2545
3433
|
if (state.step === "fields")
|
|
@@ -2708,12 +3596,6 @@ function loginValuesForProvider(provider, env) {
|
|
|
2708
3596
|
for (const field of LOGIN_FIELD_DEFINITIONS[provider]) {
|
|
2709
3597
|
values[field.key] = env[field.envKey] ?? "";
|
|
2710
3598
|
}
|
|
2711
|
-
if (provider === "kimi") {
|
|
2712
|
-
values.apiKey ||= env.MOONSHOT_API_KEY ?? process.env.MOONSHOT_API_KEY ?? "";
|
|
2713
|
-
values.baseUrl ||= env.MOONSHOT_BASE_URL ?? process.env.MOONSHOT_BASE_URL ?? "";
|
|
2714
|
-
values.model ||= env.MOONSHOT_MODEL ?? process.env.MOONSHOT_MODEL ?? "";
|
|
2715
|
-
values.fallbackModel ||= env.MOONSHOT_FALLBACK_MODEL ?? process.env.MOONSHOT_FALLBACK_MODEL ?? "";
|
|
2716
|
-
}
|
|
2717
3599
|
if (!values.baseUrl)
|
|
2718
3600
|
values.baseUrl = defaultBaseUrlForLoginProvider(provider);
|
|
2719
3601
|
if (!values.model)
|
|
@@ -2723,15 +3605,11 @@ function loginValuesForProvider(provider, env) {
|
|
|
2723
3605
|
return values;
|
|
2724
3606
|
}
|
|
2725
3607
|
function parseLoginProvider(value) {
|
|
2726
|
-
if (value === "openai" || value === "anthropic"
|
|
3608
|
+
if (value === "openai" || value === "anthropic")
|
|
2727
3609
|
return value;
|
|
2728
3610
|
return undefined;
|
|
2729
3611
|
}
|
|
2730
3612
|
function guessLoginProvider(env) {
|
|
2731
|
-
if (env.KIMI_API_KEY ?? env.MOONSHOT_API_KEY ?? process.env.KIMI_API_KEY ?? process.env.MOONSHOT_API_KEY)
|
|
2732
|
-
return "kimi";
|
|
2733
|
-
if (env.DEEPSEEK_API_KEY ?? process.env.DEEPSEEK_API_KEY)
|
|
2734
|
-
return "deepseek";
|
|
2735
3613
|
if (env.ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY)
|
|
2736
3614
|
return "anthropic";
|
|
2737
3615
|
return "openai";
|
|
@@ -2739,19 +3617,11 @@ function guessLoginProvider(env) {
|
|
|
2739
3617
|
function defaultBaseUrlForLoginProvider(provider) {
|
|
2740
3618
|
if (provider === "anthropic")
|
|
2741
3619
|
return "https://api.anthropic.com";
|
|
2742
|
-
if (provider === "deepseek")
|
|
2743
|
-
return "https://api.deepseek.com";
|
|
2744
|
-
if (provider === "kimi")
|
|
2745
|
-
return "https://api.moonshot.cn/v1";
|
|
2746
3620
|
return "https://api.openai.com";
|
|
2747
3621
|
}
|
|
2748
3622
|
function defaultModelForLoginProvider(provider) {
|
|
2749
3623
|
if (provider === "anthropic")
|
|
2750
3624
|
return "claude-sonnet-4-6";
|
|
2751
|
-
if (provider === "deepseek")
|
|
2752
|
-
return "deepseek-chat";
|
|
2753
|
-
if (provider === "kimi")
|
|
2754
|
-
return "kimi-k2.6";
|
|
2755
3625
|
return "gpt-5.5";
|
|
2756
3626
|
}
|
|
2757
3627
|
function loginFormViewHeight(state) {
|
|
@@ -2770,7 +3640,7 @@ function LoginFormView({ state, width }) {
|
|
|
2770
3640
|
const visibleValue = formatLoginFieldValue(field, rawValue, selected ? state.cursor : undefined);
|
|
2771
3641
|
const placeholder = rawValue ? "" : (field.placeholder ? ` (${field.placeholder})` : "");
|
|
2772
3642
|
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))));
|
|
2773
|
-
}), 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_
|
|
3643
|
+
}), 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)));
|
|
2774
3644
|
}
|
|
2775
3645
|
function formatLoginFieldValue(field, value, cursor) {
|
|
2776
3646
|
const display = field.secret && value ? "•".repeat(Math.min(value.length, 24)) : value;
|
|
@@ -2796,12 +3666,6 @@ function envEntriesForLoginForm(state) {
|
|
|
2796
3666
|
const value = (state.values[field.key] ?? "").trim();
|
|
2797
3667
|
entries[field.envKey] = value || undefined;
|
|
2798
3668
|
}
|
|
2799
|
-
if (state.provider === "kimi") {
|
|
2800
|
-
entries.MOONSHOT_API_KEY = undefined;
|
|
2801
|
-
entries.MOONSHOT_BASE_URL = undefined;
|
|
2802
|
-
entries.MOONSHOT_MODEL = undefined;
|
|
2803
|
-
entries.MOONSHOT_FALLBACK_MODEL = undefined;
|
|
2804
|
-
}
|
|
2805
3669
|
return entries;
|
|
2806
3670
|
}
|
|
2807
3671
|
function updateEnvContent(content, updates, removeKeys = []) {
|
|
@@ -2829,8 +3693,6 @@ function updateEnvContent(content, updates, removeKeys = []) {
|
|
|
2829
3693
|
appendEnvGroup(updatedLines, "# Neo active provider", grouped.active);
|
|
2830
3694
|
appendEnvGroup(updatedLines, "# OpenAI provider settings", grouped.openai);
|
|
2831
3695
|
appendEnvGroup(updatedLines, "# Anthropic provider settings", grouped.anthropic);
|
|
2832
|
-
appendEnvGroup(updatedLines, "# DeepSeek provider settings", grouped.deepseek);
|
|
2833
|
-
appendEnvGroup(updatedLines, "# Kimi provider settings", grouped.kimi);
|
|
2834
3696
|
appendEnvGroup(updatedLines, "# Shared model runtime settings", grouped.shared);
|
|
2835
3697
|
}
|
|
2836
3698
|
return `${updatedLines.join("\n").replace(/\n*$/u, "")}\n`;
|
|
@@ -2840,8 +3702,6 @@ function groupLoginEnvEntries(entries) {
|
|
|
2840
3702
|
active: entries.filter(([key]) => key === "MODEL_PROVIDER"),
|
|
2841
3703
|
openai: entries.filter(([key]) => key.startsWith("OPENAI_")),
|
|
2842
3704
|
anthropic: entries.filter(([key]) => key.startsWith("ANTHROPIC_")),
|
|
2843
|
-
deepseek: entries.filter(([key]) => key.startsWith("DEEPSEEK_")),
|
|
2844
|
-
kimi: entries.filter(([key]) => key.startsWith("KIMI_") || key.startsWith("MOONSHOT_")),
|
|
2845
3705
|
shared: entries.filter(([key]) => key.startsWith("MODEL_") && key !== "MODEL_PROVIDER"),
|
|
2846
3706
|
};
|
|
2847
3707
|
}
|
|
@@ -3034,9 +3894,11 @@ function formatToolUse(toolUse) {
|
|
|
3034
3894
|
text: formatPlanToolPayload(toolUse.input),
|
|
3035
3895
|
};
|
|
3036
3896
|
}
|
|
3897
|
+
const description = toolUse.name === "exec" ? execDescriptionFromInput(toolUse.input) : undefined;
|
|
3037
3898
|
return {
|
|
3038
3899
|
kind: "tool",
|
|
3039
3900
|
title: toolTitle(toolUse.name, "running"),
|
|
3901
|
+
bodyTitle: description,
|
|
3040
3902
|
text: formatJson(toolUse.input, 1200),
|
|
3041
3903
|
previewStyle: "summary",
|
|
3042
3904
|
};
|
|
@@ -3063,9 +3925,11 @@ function formatToolResultLine(toolName, output, ok) {
|
|
|
3063
3925
|
}
|
|
3064
3926
|
function formatToolFinishedWithoutResult(toolUse, ok) {
|
|
3065
3927
|
const inputText = formatJson(toolUse.input, 1200);
|
|
3928
|
+
const description = toolUse.name === "exec" ? execDescriptionFromInput(toolUse.input) : undefined;
|
|
3066
3929
|
return {
|
|
3067
3930
|
kind: ok ? "tool" : "error",
|
|
3068
3931
|
title: toolTitle(toolUse.name, "finished"),
|
|
3932
|
+
bodyTitle: description,
|
|
3069
3933
|
titleStatus: ok ? "success" : "failure",
|
|
3070
3934
|
text: inputText ? `${ok ? "finished" : "failed"}\n${inputText}` : ok ? "finished" : "failed",
|
|
3071
3935
|
previewStyle: "summary",
|
|
@@ -3078,15 +3942,27 @@ function toolTitle(toolName, phase) {
|
|
|
3078
3942
|
return `${phase === "running" ? "◇" : "◆"} plan`;
|
|
3079
3943
|
return `${phase === "running" ? "◇" : "◆"} ${toolName}`;
|
|
3080
3944
|
}
|
|
3945
|
+
function execDescriptionFromInput(input) {
|
|
3946
|
+
if (!isRecord(input))
|
|
3947
|
+
return undefined;
|
|
3948
|
+
const description = typeof input.description === "string" ? input.description.trim() : "";
|
|
3949
|
+
return description || undefined;
|
|
3950
|
+
}
|
|
3081
3951
|
function isPlanToolPayload(value) {
|
|
3082
3952
|
if (!isRecord(value) || !Array.isArray(value.items))
|
|
3083
3953
|
return false;
|
|
3084
|
-
return value.items.every(
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3954
|
+
return value.items.every(isPlanItemLike);
|
|
3955
|
+
}
|
|
3956
|
+
function isPlanItemLike(item) {
|
|
3957
|
+
if (!isRecord(item))
|
|
3958
|
+
return false;
|
|
3959
|
+
if (typeof item.description !== "string")
|
|
3960
|
+
return false;
|
|
3961
|
+
if (item.status !== "pending" && item.status !== "in_progress" && item.status !== "completed")
|
|
3962
|
+
return false;
|
|
3963
|
+
if (item.subitems === undefined)
|
|
3964
|
+
return true;
|
|
3965
|
+
return Array.isArray(item.subitems) && item.subitems.every(isPlanItemLike);
|
|
3090
3966
|
}
|
|
3091
3967
|
function planToolBodyTitle(payload) {
|
|
3092
3968
|
const title = payload.title?.trim();
|
|
@@ -3098,16 +3974,25 @@ function formatPlanToolPayload(payload) {
|
|
|
3098
3974
|
sections.push(payload.summary.trim());
|
|
3099
3975
|
if (payload.note?.trim())
|
|
3100
3976
|
sections.push(payload.note.trim());
|
|
3101
|
-
sections.push(payload.items.
|
|
3977
|
+
sections.push(payload.items.flatMap((item) => formatPlanItem(item)).join("\n"));
|
|
3102
3978
|
return sections.filter(Boolean).join("\n");
|
|
3103
3979
|
}
|
|
3104
|
-
function formatPlanItem(item) {
|
|
3980
|
+
function formatPlanItem(item, depth = 0) {
|
|
3981
|
+
const indent = " ".repeat(Math.max(0, depth));
|
|
3105
3982
|
const text = escapePlanMarkdown(item.description.trim());
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3983
|
+
const marker = planItemMarker(item.status);
|
|
3984
|
+
const line = item.status === "completed"
|
|
3985
|
+
? `${indent}- ${marker} ~~${text}~~`
|
|
3986
|
+
: `${indent}- ${marker} ${text}`;
|
|
3987
|
+
const subitems = item.subitems?.flatMap((subitem) => formatPlanItem(subitem, depth + 1)) ?? [];
|
|
3988
|
+
return [line, ...subitems];
|
|
3989
|
+
}
|
|
3990
|
+
function planItemMarker(status) {
|
|
3991
|
+
if (status === "completed")
|
|
3992
|
+
return "✓";
|
|
3993
|
+
if (status === "in_progress")
|
|
3994
|
+
return "▶";
|
|
3995
|
+
return "○";
|
|
3111
3996
|
}
|
|
3112
3997
|
function escapePlanMarkdown(text) {
|
|
3113
3998
|
return text.replace(/([\\`*_{}[\]()#+.!|>~-])/g, "\\$1");
|
|
@@ -3301,8 +4186,10 @@ function formatExecToolResult(output, ok) {
|
|
|
3301
4186
|
: output.exitCode === 0
|
|
3302
4187
|
? "exit 0"
|
|
3303
4188
|
: `exit ${output.exitCode ?? output.signal ?? "unknown"}`;
|
|
4189
|
+
const description = typeof output.description === "string" ? output.description.trim() : "";
|
|
3304
4190
|
const lines = [
|
|
3305
4191
|
"exec result",
|
|
4192
|
+
...(description ? [`purpose: ${description}`] : []),
|
|
3306
4193
|
`status: ${status}`,
|
|
3307
4194
|
`duration: ${output.durationMs}ms`,
|
|
3308
4195
|
`command: ${output.command}`,
|
|
@@ -3783,6 +4670,8 @@ const TERMINAL_TITLE_WORKING_PREFIX = "● ";
|
|
|
3783
4670
|
const TERMINAL_TITLE_READY_PREFIX = "✓ ";
|
|
3784
4671
|
const REPL_ANIMATION_INTERVAL_MS = 420;
|
|
3785
4672
|
const TOOL_RESULT_REPLACEMENT_DELAY_MS = 2000;
|
|
4673
|
+
const SUBAGENT_ACTIVITY_UPDATE_DEBOUNCE_MS = 180;
|
|
4674
|
+
const SUBAGENT_COMPLETED_LINGER_MS = 8000;
|
|
3786
4675
|
const TOKEN_PULSE_MS = 900;
|
|
3787
4676
|
const ANIMATED_NUMBER_INTERVAL_MS = 50;
|
|
3788
4677
|
const ANIMATED_NUMBER_MIN_DURATION_MS = 180;
|
|
@@ -3795,6 +4684,8 @@ const STATUS_SHIMMER_RADIUS = 1;
|
|
|
3795
4684
|
const STATUS_SHIMMER_COLOR = "whiteBright";
|
|
3796
4685
|
const STATUS_SEPARATOR = " · ";
|
|
3797
4686
|
const STATUS_BAR_RENDER_ROWS = 2;
|
|
4687
|
+
const FOREGROUND_EXEC_DETACH_HINT_RENDER_ROWS = 1;
|
|
4688
|
+
const FOREGROUND_EXEC_DETACH_HINT_DELAY_MS = 2000;
|
|
3798
4689
|
const BACKGROUND_TASK_STATUS_RENDER_ROWS = 1;
|
|
3799
4690
|
const QUEUED_INPUT_RENDER_ROWS = 1;
|
|
3800
4691
|
const EMPTY_CTRL_C_EXIT_PLACEHOLDER = "Press Ctrl+C again to exit";
|