neoctl 0.2.5 → 0.2.7

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.
Files changed (126) hide show
  1. package/README.md +16 -26
  2. package/dist/agents/agent-activity.d.ts +70 -0
  3. package/dist/agents/agent-activity.js +261 -0
  4. package/dist/agents/agent-activity.js.map +1 -0
  5. package/dist/agents/agent-definition.d.ts +7 -0
  6. package/dist/agents/agent-definition.js +44 -1
  7. package/dist/agents/agent-definition.js.map +1 -1
  8. package/dist/agents/agent-report-tool.d.ts +11 -0
  9. package/dist/agents/agent-report-tool.js +50 -0
  10. package/dist/agents/agent-report-tool.js.map +1 -0
  11. package/dist/agents/agent-tool.d.ts +3 -1
  12. package/dist/agents/agent-tool.js +56 -11
  13. package/dist/agents/agent-tool.js.map +1 -1
  14. package/dist/agents/local-agent-task.d.ts +3 -0
  15. package/dist/agents/local-agent-task.js +2 -0
  16. package/dist/agents/local-agent-task.js.map +1 -1
  17. package/dist/agents/smoke-agents.js +131 -7
  18. package/dist/agents/smoke-agents.js.map +1 -1
  19. package/dist/context/prompts.js +4 -0
  20. package/dist/context/prompts.js.map +1 -1
  21. package/dist/core/query-engine.d.ts +3 -1
  22. package/dist/core/query-engine.js +2 -0
  23. package/dist/core/query-engine.js.map +1 -1
  24. package/dist/core/query.d.ts +4 -0
  25. package/dist/core/query.js +10 -1
  26. package/dist/core/query.js.map +1 -1
  27. package/dist/core/run-agent.d.ts +2 -0
  28. package/dist/core/run-agent.js +156 -21
  29. package/dist/core/run-agent.js.map +1 -1
  30. package/dist/core/smoke-core-loop.js +1 -1
  31. package/dist/core/smoke-core-loop.js.map +1 -1
  32. package/dist/index.d.ts +5 -2
  33. package/dist/index.js +5 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/model/anthropic-mapper.js +27 -2
  36. package/dist/model/anthropic-mapper.js.map +1 -1
  37. package/dist/model/config.d.ts +2 -8
  38. package/dist/model/config.js +1 -41
  39. package/dist/model/config.js.map +1 -1
  40. package/dist/model/env.js +6 -11
  41. package/dist/model/env.js.map +1 -1
  42. package/dist/model/model-metadata.json +1 -115
  43. package/dist/model/openai-chat-mapper.js +4 -19
  44. package/dist/model/openai-chat-mapper.js.map +1 -1
  45. package/dist/model/provider-factory.js +0 -32
  46. package/dist/model/provider-factory.js.map +1 -1
  47. package/dist/model/smoke-anthropic-mapper.js +6 -2
  48. package/dist/model/smoke-anthropic-mapper.js.map +1 -1
  49. package/dist/repl/commands.d.ts +8 -0
  50. package/dist/repl/commands.js +35 -1
  51. package/dist/repl/commands.js.map +1 -1
  52. package/dist/repl/index.js +731 -127
  53. package/dist/repl/index.js.map +1 -1
  54. package/dist/secrets/secret-crypto.d.ts +22 -0
  55. package/dist/secrets/secret-crypto.js +58 -0
  56. package/dist/secrets/secret-crypto.js.map +1 -0
  57. package/dist/secrets/secret-redaction.d.ts +8 -0
  58. package/dist/secrets/secret-redaction.js +40 -0
  59. package/dist/secrets/secret-redaction.js.map +1 -0
  60. package/dist/secrets/secret-store.d.ts +28 -0
  61. package/dist/secrets/secret-store.js +158 -0
  62. package/dist/secrets/secret-store.js.map +1 -0
  63. package/dist/secrets/secret-types.d.ts +31 -0
  64. package/dist/secrets/secret-types.js +17 -0
  65. package/dist/secrets/secret-types.js.map +1 -0
  66. package/dist/secrets/smoke-secrets.js +68 -0
  67. package/dist/secrets/smoke-secrets.js.map +1 -0
  68. package/dist/tools/builtins/exec-tool.d.ts +20 -1
  69. package/dist/tools/builtins/exec-tool.js +164 -29
  70. package/dist/tools/builtins/exec-tool.js.map +1 -1
  71. package/dist/tools/builtins/plan-tool.d.ts +1 -0
  72. package/dist/tools/builtins/plan-tool.js +80 -27
  73. package/dist/tools/builtins/plan-tool.js.map +1 -1
  74. package/dist/tools/builtins/secret-tools.d.ts +10 -0
  75. package/dist/tools/builtins/secret-tools.js +75 -0
  76. package/dist/tools/builtins/secret-tools.js.map +1 -0
  77. package/dist/tools/run-tool-use.js +4 -2
  78. package/dist/tools/run-tool-use.js.map +1 -1
  79. package/dist/tools/smoke-tool-system.js +42 -10
  80. package/dist/tools/smoke-tool-system.js.map +1 -1
  81. package/dist/tools/tool.d.ts +3 -0
  82. package/dist/tools/tool.js.map +1 -1
  83. package/dist/web/html.js +1 -1
  84. package/dist/web/index.js +11 -50
  85. package/dist/web/index.js.map +1 -1
  86. package/package.json +5 -3
  87. package/scripts/install-ripgrep.cjs +62 -18
  88. package/vendor/ripgrep/darwin-arm64/COPYING +3 -0
  89. package/vendor/ripgrep/darwin-arm64/LICENSE-MIT +21 -0
  90. package/vendor/ripgrep/darwin-arm64/UNLICENSE +24 -0
  91. package/vendor/ripgrep/darwin-arm64/manifest.json +7 -0
  92. package/vendor/ripgrep/darwin-arm64/rg +0 -0
  93. package/vendor/ripgrep/darwin-x64/COPYING +3 -0
  94. package/vendor/ripgrep/darwin-x64/LICENSE-MIT +21 -0
  95. package/vendor/ripgrep/darwin-x64/UNLICENSE +24 -0
  96. package/vendor/ripgrep/darwin-x64/manifest.json +7 -0
  97. package/vendor/ripgrep/darwin-x64/rg +0 -0
  98. package/vendor/ripgrep/linux-arm64/COPYING +3 -0
  99. package/vendor/ripgrep/linux-arm64/LICENSE-MIT +21 -0
  100. package/vendor/ripgrep/linux-arm64/UNLICENSE +24 -0
  101. package/vendor/ripgrep/linux-arm64/manifest.json +7 -0
  102. package/vendor/ripgrep/linux-arm64/rg +0 -0
  103. package/vendor/ripgrep/linux-x64/COPYING +3 -0
  104. package/vendor/ripgrep/linux-x64/LICENSE-MIT +21 -0
  105. package/vendor/ripgrep/linux-x64/UNLICENSE +24 -0
  106. package/vendor/ripgrep/linux-x64/manifest.json +7 -0
  107. package/vendor/ripgrep/linux-x64/rg +0 -0
  108. package/vendor/ripgrep/win32-arm64/COPYING +3 -0
  109. package/vendor/ripgrep/win32-arm64/LICENSE-MIT +21 -0
  110. package/vendor/ripgrep/win32-arm64/UNLICENSE +24 -0
  111. package/vendor/ripgrep/win32-arm64/manifest.json +7 -0
  112. package/vendor/ripgrep/win32-arm64/rg.exe +0 -0
  113. package/vendor/ripgrep/win32-x64/COPYING +3 -0
  114. package/vendor/ripgrep/win32-x64/LICENSE-MIT +21 -0
  115. package/vendor/ripgrep/win32-x64/UNLICENSE +24 -0
  116. package/vendor/ripgrep/win32-x64/manifest.json +7 -0
  117. package/vendor/ripgrep/win32-x64/rg.exe +0 -0
  118. package/dist/model/deepseek-adapter.d.ts +0 -29
  119. package/dist/model/deepseek-adapter.js +0 -108
  120. package/dist/model/deepseek-adapter.js.map +0 -1
  121. package/dist/model/kimi-adapter.d.ts +0 -29
  122. package/dist/model/kimi-adapter.js +0 -108
  123. package/dist/model/kimi-adapter.js.map +0 -1
  124. package/dist/model/smoke-deepseek-mapper.js +0 -65
  125. package/dist/model/smoke-deepseek-mapper.js.map +0 -1
  126. /package/dist/{model/smoke-deepseek-mapper.d.ts → secrets/smoke-secrets.d.ts} +0 -0
@@ -22,7 +22,11 @@ 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";
@@ -39,6 +43,39 @@ import { FileSystemSkillCatalog } from "../skills/skill-filesystem.js";
39
43
  import { createSkillAwareCanUseTool, createSkillTool, requireSkillName } from "../skills/skill-tool.js";
40
44
  import { createSkillManagementTools } from "../skills/skill-management-tools.js";
41
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
+ }
42
79
  class SessionUsageTracker {
43
80
  totals = emptyUsageTotals();
44
81
  lastUsage;
@@ -200,6 +237,10 @@ async function createRuntime() {
200
237
  const communicationLogger = new CommunicationLogger();
201
238
  const modelGateway = new LoggingModelGateway(createModelGatewayFromProcessEnv(process.env), communicationLogger);
202
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();
203
244
  const tools = new ToolRegistry();
204
245
  const skillWorkspaceRoot = path.resolve(process.cwd(), ".neo", "skills");
205
246
  const skills = new FileSystemSkillCatalog({
@@ -211,7 +252,7 @@ async function createRuntime() {
211
252
  });
212
253
  tools.register(editTool);
213
254
  tools.register(writeTool);
214
- tools.register(createExecTool({ taskStore }));
255
+ tools.register(createExecTool({ taskStore, foregroundDetachRegistry: foregroundExecDetach }));
215
256
  tools.register(listDirectoryTool);
216
257
  tools.register(readFileTool);
217
258
  tools.register(grepTool);
@@ -220,16 +261,20 @@ async function createRuntime() {
220
261
  if (modelConfig?.provider === "openai")
221
262
  tools.register(createOpenAIImageGenerationTool());
222
263
  tools.register(planTool);
264
+ for (const tool of createSecretTools())
265
+ tools.register(tool);
223
266
  tools.register(createSkillTool(skills));
224
267
  for (const tool of createSkillManagementTools(skills, { requireApproval: true, allowDelete: false }))
225
268
  tools.register(tool);
226
- const agentRuntime = { modelGateway, tools, taskStore };
269
+ const agentRuntime = { modelGateway, tools, taskStore, agentActivityStore };
227
270
  tools.register(createAgentTool(agentRuntime));
228
271
  const resumeHandler = async (taskId, directive) => {
229
272
  const dummyContext = {
230
273
  agentId: "main",
231
274
  tools,
232
275
  appState: new (await import("../app/app-state.js")).InMemoryAppState("main"),
276
+ secrets: secretStore,
277
+ secretRedactions,
233
278
  emit: () => undefined,
234
279
  };
235
280
  return resumeAgentTask(taskId, directive, agentRuntime, taskStore, dummyContext);
@@ -246,6 +291,8 @@ async function createRuntime() {
246
291
  tools,
247
292
  contextManager: new SkillCatalogContextManager(skills),
248
293
  canUseTool: createSkillAwareCanUseTool(skills),
294
+ secrets: secretStore,
295
+ secretRedactions,
249
296
  taskNotificationSource,
250
297
  commands: replCommandDefinitions.map((command) => command.usage),
251
298
  session: {
@@ -267,8 +314,11 @@ async function createRuntime() {
267
314
  agentRuntime,
268
315
  usage: new SessionUsageTracker(),
269
316
  taskStore,
317
+ agentActivityStore,
318
+ foregroundExecDetach,
270
319
  tools,
271
320
  skills,
321
+ secretStore,
272
322
  skillWorkspaceRoot,
273
323
  initialMetrics,
274
324
  defaultReasoning: modelConfig?.defaultReasoning,
@@ -282,7 +332,7 @@ function syncImageGenerationTool(runtime, provider) {
282
332
  runtime.tools.register(createOpenAIImageGenerationTool());
283
333
  }
284
334
  function formatCreatedEnvNotice(path) {
285
- return `Created default config file: ${path}\nSet MODEL_PROVIDER and the matching provider section (for example OPENAI_API_KEY, ANTHROPIC_API_KEY, or KIMI_API_KEY), then restart neo.`;
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.`;
286
336
  }
287
337
  function parseResumeFlag(value) {
288
338
  if (!value)
@@ -292,6 +342,34 @@ function parseResumeFlag(value) {
292
342
  function activeBackgroundTasks(runtime) {
293
343
  return runtime.taskStore.list().filter((task) => !runtime.taskStore.isTerminal(task));
294
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
+ }
295
373
  function runningSessionIds(runs) {
296
374
  return [...runs.keys()];
297
375
  }
@@ -458,6 +536,9 @@ function InkRepl({ runtime, initialCommandLine }) {
458
536
  const [status, setStatus] = useState(() => initialStatus(runtime));
459
537
  const sessionTitleRef = useRef(sessionTerminalTitle(runtime.engine.snapshot().session));
460
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);
461
542
  const [backgroundSessionRuns, setBackgroundSessionRuns] = useState([]);
462
543
  const backgroundSessionRunsRef = useRef(new Map());
463
544
  const suppressReattachedStreamingRef = useRef(new Set());
@@ -468,6 +549,8 @@ function InkRepl({ runtime, initialCommandLine }) {
468
549
  const backgroundTaskCount = backgroundTasks.length;
469
550
  const terminalTitleWorking = isActivePhase(status.phase) || backgroundTaskCount > 0 || backgroundSessionRuns.length > 0;
470
551
  const [sessionsBrowser, setSessionsBrowser] = useState(undefined);
552
+ const [skillsBrowser, setSkillsBrowser] = useState(undefined);
553
+ const [secretsBrowser, setSecretsBrowser] = useState(undefined);
471
554
  const inputRef = useRef(input);
472
555
  const queuedInputRef = useRef(undefined);
473
556
  const cursorRef = useRef(cursor);
@@ -484,6 +567,7 @@ function InkRepl({ runtime, initialCommandLine }) {
484
567
  const pasteStatusTimerRef = useRef(undefined);
485
568
  const [slashCompletionIndex, setSlashCompletionIndex] = useState(0);
486
569
  const [skillCompletions, setSkillCompletions] = useState([]);
570
+ const [secretCompletions, setSecretCompletions] = useState([]);
487
571
  const [loginForm, setLoginForm] = useState(undefined);
488
572
  const loginFormRef = useRef(undefined);
489
573
  useEffect(() => {
@@ -495,16 +579,48 @@ function InkRepl({ runtime, initialCommandLine }) {
495
579
  };
496
580
  }, []);
497
581
  useEffect(() => {
498
- if (!busy && backgroundTaskCount === 0 && backgroundSessionRuns.length === 0)
582
+ if (!busy && backgroundTaskCount === 0 && backgroundSessionRuns.length === 0 && agentActivities.length === 0)
499
583
  return undefined;
500
- const interval = setInterval(() => setAnimationTick((current) => current + 1), REPL_ANIMATION_INTERVAL_MS);
584
+ const interval = setInterval(() => {
585
+ setAnimationTick((current) => current + 1);
586
+ setAgentActivities(activeAgentActivities(runtime));
587
+ }, REPL_ANIMATION_INTERVAL_MS);
501
588
  return () => clearInterval(interval);
502
- }, [busy, backgroundTaskCount, backgroundSessionRuns.length]);
589
+ }, [busy, backgroundTaskCount, backgroundSessionRuns.length, agentActivities.length, runtime]);
503
590
  useEffect(() => {
504
591
  const updateBackgroundTasks = () => setBackgroundTasks(activeBackgroundTasks(runtime));
505
592
  updateBackgroundTasks();
506
593
  return runtime.taskStore.subscribe(updateBackgroundTasks);
507
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]);
508
624
  useEffect(() => {
509
625
  if (!terminalTitleWorking) {
510
626
  setTerminalTitlePrefix(TERMINAL_TITLE_READY_PREFIX);
@@ -563,9 +679,21 @@ function InkRepl({ runtime, initialCommandLine }) {
563
679
  setSkillCompletions([]);
564
680
  }
565
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]);
566
691
  useEffect(() => {
567
692
  void refreshSkillCompletions();
568
693
  }, [refreshSkillCompletions]);
694
+ useEffect(() => {
695
+ void refreshSecretCompletions();
696
+ }, [refreshSecretCompletions]);
569
697
  const syncAttachmentsForText = (text) => {
570
698
  const next = attachmentsRef.current.filter((attachment) => text.includes(attachment.label));
571
699
  if (next.length === attachmentsRef.current.length)
@@ -1069,10 +1197,35 @@ function InkRepl({ runtime, initialCommandLine }) {
1069
1197
  }
1070
1198
  if (command.type === "sessions") {
1071
1199
  detachRunningForeground("session browser");
1200
+ setSkillsBrowser(undefined);
1201
+ setSecretsBrowser(undefined);
1072
1202
  await handleSessionsCommand(runtime, runningSessionIds(backgroundSessionRunsRef.current), setSessionsBrowser, (line) => append(line));
1073
1203
  return;
1074
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
+ }
1075
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
+ }
1076
1229
  if (command.action !== "invoke" || !command.name || !command.args) {
1077
1230
  append(await handleSkillCommand(command, runtime));
1078
1231
  if (command.action === "import" || command.action === "delete")
@@ -1084,6 +1237,8 @@ function InkRepl({ runtime, initialCommandLine }) {
1084
1237
  }
1085
1238
  if (command.type === "login") {
1086
1239
  setSessionsBrowser(undefined);
1240
+ setSkillsBrowser(undefined);
1241
+ setSecretsBrowser(undefined);
1087
1242
  setLoginFormState(createLoginFormState(runtime.envPath));
1088
1243
  append(systemLine("Opening provider login. Use ↑/↓ to choose, Enter to continue/save, Esc to cancel."));
1089
1244
  return;
@@ -1201,6 +1356,8 @@ function InkRepl({ runtime, initialCommandLine }) {
1201
1356
  clearPendingToolResultTimers();
1202
1357
  setStatus(initialStatus(runtime));
1203
1358
  setSessionsBrowser(undefined);
1359
+ setSkillsBrowser(undefined);
1360
+ setSecretsBrowser(undefined);
1204
1361
  setLoginFormState(undefined);
1205
1362
  setQueuedPromptState(undefined);
1206
1363
  setPromptState("", 0);
@@ -1220,7 +1377,7 @@ function InkRepl({ runtime, initialCommandLine }) {
1220
1377
  const promptDisplayCursor = cursor;
1221
1378
  const promptLayoutText = activePlaceholder ? ` ${activePlaceholder}` : promptDisplayText;
1222
1379
  const promptLayoutCursor = activePlaceholder ? 0 : promptDisplayCursor;
1223
- const slashCompletions = inputLockedByQueue || (input.length === 0 && promptPlaceholder !== undefined) || loginForm ? [] : slashCommandCompletions(input, cursor, skillCompletions);
1380
+ const slashCompletions = inputLockedByQueue || (input.length === 0 && promptPlaceholder !== undefined) || loginForm ? [] : slashCommandCompletions(input, cursor, skillCompletions, secretCompletions);
1224
1381
  const visibleSlashCompletionCount = slashCompletions.length;
1225
1382
  const selectedSlashCompletionIndex = visibleSlashCompletionCount === 0
1226
1383
  ? 0
@@ -1236,10 +1393,18 @@ function InkRepl({ runtime, initialCommandLine }) {
1236
1393
  const blockIndex = staticLines.length + i;
1237
1394
  return sum + (blockIndex > 0 ? MESSAGE_BLOCK_SPACING_LINES : 0);
1238
1395
  }, 0);
1239
- const statusRenderRows = STATUS_BAR_RENDER_ROWS + backgroundTaskStatusRenderRows(backgroundTasks.length);
1240
- const sessionsBrowserHeight = sessionsBrowser ? sessionsBrowserViewHeight(sessionsBrowser) : 0;
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;
1241
1406
  const loginFormHeight = loginForm ? loginFormViewHeight(loginForm) : 0;
1242
- const liveViewportLines = Math.max(MIN_LIVE_VIEWPORT_LINES, terminalSize.rows - promptHeight - statusRenderRows - sessionsBrowserHeight - loginFormHeight - dynamicMarginOverhead - 1);
1407
+ const liveViewportLines = Math.max(MIN_LIVE_VIEWPORT_LINES, terminalSize.rows - promptHeight - statusRenderRows - managementBrowserHeight - loginFormHeight - dynamicMarginOverhead - 1);
1243
1408
  useInput((value, key) => {
1244
1409
  if (isTerminalFocusInSequence(value)) {
1245
1410
  terminalFocusedRef.current = true;
@@ -1260,6 +1425,13 @@ function InkRepl({ runtime, initialCommandLine }) {
1260
1425
  void handleClipboardPaste();
1261
1426
  return;
1262
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
+ }
1263
1435
  if (key.ctrl && value === "c") {
1264
1436
  if (inputRef.current.length > 0) {
1265
1437
  setPromptState("", 0);
@@ -1334,20 +1506,124 @@ function InkRepl({ runtime, initialCommandLine }) {
1334
1506
  }
1335
1507
  return;
1336
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
+ }
1337
1613
  if (key.return) {
1338
1614
  const currentText = inputRef.current;
1339
1615
  const currentCursor = cursorRef.current;
1340
- const completion = selectedSlashCommandCompletion(currentText, currentCursor, slashCompletionIndexRef.current, skillCompletions);
1616
+ const completion = selectedSlashCommandCompletion(currentText, currentCursor, slashCompletionIndexRef.current, skillCompletions, secretCompletions);
1341
1617
  if (completion !== undefined && completion.kind === "command" && completion.arguments !== "none") {
1342
1618
  const nextText = `${completion.insertText} ${currentText.slice(currentCursor)}`;
1343
1619
  setPromptState(nextText, completion.insertText.length + 1);
1344
1620
  return;
1345
1621
  }
1346
- if (currentText.trimEnd() === "/skill") {
1622
+ if (currentText.trimEnd() === "/skill" || currentText.trimEnd() === "/secret") {
1347
1623
  void submitLine(currentText);
1348
1624
  return;
1349
1625
  }
1350
- if (completion !== undefined && completion.kind === "skill-action") {
1626
+ if (completion !== undefined && (completion.kind === "skill-action" || completion.kind === "secret-action")) {
1351
1627
  const nextText = `${completion.insertText}${currentText.slice(currentCursor)}`;
1352
1628
  setPromptState(nextText, completion.insertText.length);
1353
1629
  return;
@@ -1372,7 +1648,7 @@ function InkRepl({ runtime, initialCommandLine }) {
1372
1648
  return;
1373
1649
  }
1374
1650
  if (key.leftArrow) {
1375
- const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions);
1651
+ const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions, secretCompletions);
1376
1652
  if (completionCount > SLASH_COMPLETION_PAGE_SIZE) {
1377
1653
  setSlashCompletionSelection((slashCompletionIndexRef.current + completionCount - SLASH_COMPLETION_PAGE_SIZE) % completionCount);
1378
1654
  return;
@@ -1385,7 +1661,7 @@ function InkRepl({ runtime, initialCommandLine }) {
1385
1661
  return;
1386
1662
  }
1387
1663
  if (key.rightArrow) {
1388
- const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions);
1664
+ const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions, secretCompletions);
1389
1665
  if (completionCount > SLASH_COMPLETION_PAGE_SIZE) {
1390
1666
  setSlashCompletionSelection((slashCompletionIndexRef.current + SLASH_COMPLETION_PAGE_SIZE) % completionCount);
1391
1667
  return;
@@ -1416,7 +1692,7 @@ function InkRepl({ runtime, initialCommandLine }) {
1416
1692
  setTipIndex((current) => current - 1);
1417
1693
  return;
1418
1694
  }
1419
- const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions);
1695
+ const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions, secretCompletions);
1420
1696
  if (completionCount > 0) {
1421
1697
  setSlashCompletionSelection((slashCompletionIndexRef.current + completionCount - 1) % completionCount);
1422
1698
  return;
@@ -1433,7 +1709,7 @@ function InkRepl({ runtime, initialCommandLine }) {
1433
1709
  setTipIndex((current) => current + 1);
1434
1710
  return;
1435
1711
  }
1436
- const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions);
1712
+ const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current, skillCompletions, secretCompletions);
1437
1713
  if (completionCount > 0) {
1438
1714
  setSlashCompletionSelection((slashCompletionIndexRef.current + 1) % completionCount);
1439
1715
  return;
@@ -1459,7 +1735,7 @@ function InkRepl({ runtime, initialCommandLine }) {
1459
1735
  return;
1460
1736
  }
1461
1737
  const currentCursor = cursorRef.current;
1462
- const completions = slashCommandCompletions(currentText, currentCursor, skillCompletions);
1738
+ const completions = slashCommandCompletions(currentText, currentCursor, skillCompletions, secretCompletions);
1463
1739
  const completion = completions[Math.min(slashCompletionIndexRef.current, completions.length - 1)];
1464
1740
  if (completion !== undefined) {
1465
1741
  const nextText = `${completion.insertText}${currentText.slice(currentCursor)}`;
@@ -1472,7 +1748,7 @@ function InkRepl({ runtime, initialCommandLine }) {
1472
1748
  return;
1473
1749
  }
1474
1750
  });
1475
- 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 }));
1476
1752
  }
1477
1753
  const MessageList = React.memo(function MessageList({ lines, width, liveMaxLines, lineIndexOffset = 0, onMarkdownRenderComplete }) {
1478
1754
  const contentWidth = messageContentWidth(width);
@@ -1829,6 +2105,139 @@ function backgroundTaskStatusRenderRows(taskCount) {
1829
2105
  return 0;
1830
2106
  return 1 + Math.min(taskCount, 2);
1831
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
+ }
1832
2241
  function BackgroundTaskStatusLine({ tasks, width: terminalWidth }) {
1833
2242
  const width = statusBarWidth(terminalWidth);
1834
2243
  const summary = `◇ background tools: ${tasks.length} task${tasks.length === 1 ? "" : "s"}`;
@@ -1905,10 +2314,20 @@ const SLASH_COMPLETION_PAGE_SIZE = 10;
1905
2314
  const MODEL_REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
1906
2315
  const MODEL_REASONING_CONTROL_CHOICES = ["default", "off"];
1907
2316
  const SKILL_COMMAND_ACTIONS = [
2317
+ { name: "list", description: "Open the skill management browser", aliases: ["ls"] },
1908
2318
  { name: "import", description: "Import by linking a skill directory" },
1909
2319
  { name: "delete", description: "Delete a workspace skill link/directory", aliases: ["remove", "rm"] },
1910
2320
  ];
1911
- function slashCommandCompletions(text, cursor, skills = []) {
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 = []) {
1912
2331
  const safeCursor = Math.max(0, Math.min(cursor, text.length));
1913
2332
  const prefix = text.slice(0, safeCursor);
1914
2333
  if (!prefix.startsWith("/") || /\r|\n/.test(prefix))
@@ -1924,6 +2343,9 @@ function slashCommandCompletions(text, cursor, skills = []) {
1924
2343
  if (prefix.startsWith("/skill") && (prefix.length === "/skill".length || prefix["/skill".length] === " ")) {
1925
2344
  return skillCommandCompletions(prefix, skills);
1926
2345
  }
2346
+ if (prefix.startsWith("/secret") && (prefix.length === "/secret".length || prefix["/secret".length] === " ")) {
2347
+ return secretCommandCompletions(prefix, secrets);
2348
+ }
1927
2349
  if (prefix.length > 1 && !/^\/[\w-]*$/.test(prefix))
1928
2350
  return [];
1929
2351
  const normalizedPrefix = prefix.toLowerCase();
@@ -1937,22 +2359,19 @@ function skillCommandCompletions(prefix, skills) {
1937
2359
  const argumentTokens = tokens.slice(1);
1938
2360
  if (!hasTrailingSpace && argumentTokens.length === 0 && !"/skill".startsWith(prefix.toLowerCase()))
1939
2361
  return [];
1940
- if (argumentTokens.length === 0) {
1941
- return [...skillActionCompletions(""), ...skillNameCompletions(skills, "")];
1942
- }
2362
+ if (argumentTokens.length === 0)
2363
+ return skillActionCompletions("");
1943
2364
  const [first = "", second = ""] = argumentTokens;
1944
- if (first === "import")
2365
+ if (first === "list" || first === "ls" || first === "import")
1945
2366
  return [];
1946
2367
  if (first === "delete" || first === "remove" || first === "rm") {
1947
2368
  if (argumentTokens.length > 1 && hasTrailingSpace)
1948
2369
  return [];
1949
2370
  return skillNameCompletions(skills, hasTrailingSpace ? "" : second, "delete");
1950
2371
  }
1951
- if (argumentTokens.length > 1)
1952
- return [];
1953
- if (hasTrailingSpace)
2372
+ if (argumentTokens.length > 1 || hasTrailingSpace)
1954
2373
  return [];
1955
- return [...skillActionCompletions(first), ...skillNameCompletions(skills, first)];
2374
+ return skillActionCompletions(first);
1956
2375
  }
1957
2376
  function skillActionCompletions(current) {
1958
2377
  return SKILL_COMMAND_ACTIONS
@@ -1960,7 +2379,7 @@ function skillActionCompletions(current) {
1960
2379
  .filter((action) => action.name.startsWith(current.toLowerCase()))
1961
2380
  .map((action) => ({
1962
2381
  value: action.name,
1963
- insertText: `/skill ${action.name} `,
2382
+ insertText: action.name === "list" || action.name === "ls" ? `/skill ${action.name}` : `/skill ${action.name} `,
1964
2383
  description: action.description,
1965
2384
  arguments: "optional",
1966
2385
  kind: "skill-action",
@@ -1981,6 +2400,90 @@ function formatSkillCompletionDescription(skill) {
1981
2400
  const tags = skill.tags?.length ? ` · ${skill.tags.join(",")}` : "";
1982
2401
  return `${skill.description}${skill.execution ? ` · ${skill.execution}` : ""}${tags}`;
1983
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
+ }
1984
2487
  function modelCommandCompletions(prefix) {
1985
2488
  const hasTrailingSpace = /\s$/.test(prefix);
1986
2489
  const tokens = prefix.trim().split(/\s+/).filter(Boolean);
@@ -2066,11 +2569,11 @@ function slashCompletionViewHeight(completions) {
2066
2569
  return 0;
2067
2570
  return Math.min(completions.length, SLASH_COMPLETION_PAGE_SIZE) + 2;
2068
2571
  }
2069
- function slashCompletionSelectableCount(text, cursor, skills = []) {
2070
- return slashCommandCompletions(text, cursor, skills).length;
2572
+ function slashCompletionSelectableCount(text, cursor, skills = [], secrets = []) {
2573
+ return slashCommandCompletions(text, cursor, skills, secrets).length;
2071
2574
  }
2072
- function selectedSlashCommandCompletion(text, cursor, selectedIndex, skills = []) {
2073
- const completions = slashCommandCompletions(text, cursor, skills);
2575
+ function selectedSlashCommandCompletion(text, cursor, selectedIndex, skills = [], secrets = []) {
2576
+ const completions = slashCommandCompletions(text, cursor, skills, secrets);
2074
2577
  if (completions.length === 0)
2075
2578
  return undefined;
2076
2579
  return completions[Math.max(0, Math.min(selectedIndex, completions.length - 1))];
@@ -2143,6 +2646,65 @@ function SlashCompletionLines({ completions, width, prompt, selectedIndex }) {
2143
2646
  e(Text, { key: "slash-completion-footer", color: "gray" }, fitToWidth(footer, contentWidth)),
2144
2647
  ].map((line, index) => e(Box, { key: `slash-completion-line-${index}`, height: 1, overflow: "hidden" }, e(Text, { color: "gray" }, " ".repeat(prompt.length)), line));
2145
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
+ }
2146
2708
  async function handleSkillCommand(command, runtime) {
2147
2709
  if (command.action === "import")
2148
2710
  return handleSkillImportCommand(command, runtime);
@@ -2189,7 +2751,10 @@ async function handleSkillImportCommand(command, runtime) {
2189
2751
  async function handleSkillDeleteCommand(command, runtime) {
2190
2752
  if (!command.name)
2191
2753
  return { kind: "error", text: "Usage: /skill delete <name>" };
2192
- const name = requireSkillName(command.name);
2754
+ return handleSkillDeleteByName(command.name, runtime);
2755
+ }
2756
+ async function handleSkillDeleteByName(nameInput, runtime) {
2757
+ const name = requireSkillName(nameInput);
2193
2758
  const skillPath = path.join(runtime.skillWorkspaceRoot, name);
2194
2759
  const existing = await safeLstat(skillPath);
2195
2760
  if (!existing)
@@ -2344,10 +2909,6 @@ function currentModelProvider() {
2344
2909
  function modelEnvKeyForProvider(provider) {
2345
2910
  if (provider === "anthropic")
2346
2911
  return "ANTHROPIC_MODEL";
2347
- if (provider === "deepseek")
2348
- return "DEEPSEEK_MODEL";
2349
- if (provider === "kimi")
2350
- return "KIMI_MODEL";
2351
2912
  return "OPENAI_MODEL";
2352
2913
  }
2353
2914
  function envValueForReasoning(reasoning) {
@@ -2455,6 +3016,8 @@ function renderMessage(message, append, activeAssistantId, options = {}) {
2455
3016
  rendered = true;
2456
3017
  }
2457
3018
  if (block.type === "thinking") {
3019
+ if (options.includeThinkingBlocks === false)
3020
+ continue;
2458
3021
  append(thinkingLine(block.text));
2459
3022
  rendered = true;
2460
3023
  }
@@ -2598,7 +3161,25 @@ async function handleSessionsCommand(runtime, runningSessionIds, setBrowser, app
2598
3161
  append(systemLine("No saved sessions found."));
2599
3162
  return;
2600
3163
  }
2601
- 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 });
2602
3183
  }
2603
3184
  async function handleExportCommand(command, runtime) {
2604
3185
  const snapshot = runtime.engine.snapshot();
@@ -2646,6 +3227,7 @@ async function handleDeleteSessionCommand(sessionId, current, runtime, setBrowse
2646
3227
  const pageLength = nextSessions.slice(pageIndex * current.pageSize, pageIndex * current.pageSize + current.pageSize).length;
2647
3228
  setBrowser({
2648
3229
  ...current,
3230
+ items: nextSessions,
2649
3231
  sessions: nextSessions,
2650
3232
  runningSessionIds: current.runningSessionIds.filter((id) => id !== sessionId),
2651
3233
  pageIndex,
@@ -2683,11 +3265,11 @@ function restoredHistoryLines(runtime) {
2683
3265
  return lines.length;
2684
3266
  };
2685
3267
  for (const message of runtime.engine.getHistoryMessages()) {
2686
- renderMessage(message, append, undefined, { includeToolUseBlocks: true });
3268
+ renderMessage(message, append, undefined, { includeToolUseBlocks: true, includeThinkingBlocks: false });
2687
3269
  }
2688
3270
  return lines;
2689
3271
  }
2690
- const LOGIN_PROVIDERS = ["openai", "anthropic", "deepseek", "kimi"];
3272
+ const LOGIN_PROVIDERS = ["openai", "anthropic"];
2691
3273
  const SHARED_LOGIN_FIELDS = [
2692
3274
  { key: "reasoningEffort", label: "Reasoning effort", envKey: "MODEL_REASONING_EFFORT", scope: "shared", options: ["", "off", "none", "minimal", "low", "medium", "high", "xhigh", "max"] },
2693
3275
  { key: "reasoningSummary", label: "Reasoning summary", envKey: "MODEL_REASONING_SUMMARY", scope: "shared", options: ["", "auto", "concise", "detailed"] },
@@ -2713,20 +3295,6 @@ const LOGIN_FIELD_DEFINITIONS = {
2713
3295
  { key: "version", label: "Anthropic version", envKey: "ANTHROPIC_VERSION", scope: "provider", placeholder: "2023-06-01" },
2714
3296
  ...SHARED_LOGIN_FIELDS,
2715
3297
  ],
2716
- deepseek: [
2717
- { key: "apiKey", label: "API key", envKey: "DEEPSEEK_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
2718
- { key: "baseUrl", label: "Base URL", envKey: "DEEPSEEK_BASE_URL", scope: "provider", placeholder: "https://api.deepseek.com" },
2719
- { key: "model", label: "Model", envKey: "DEEPSEEK_MODEL", scope: "provider", required: true, placeholder: "deepseek-chat" },
2720
- { key: "fallbackModel", label: "Fallback model", envKey: "DEEPSEEK_FALLBACK_MODEL", scope: "provider" },
2721
- ...SHARED_LOGIN_FIELDS,
2722
- ],
2723
- kimi: [
2724
- { key: "apiKey", label: "API key", envKey: "KIMI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
2725
- { key: "baseUrl", label: "Base URL", envKey: "KIMI_BASE_URL", scope: "provider", placeholder: "https://api.moonshot.cn/v1" },
2726
- { key: "model", label: "Model", envKey: "KIMI_MODEL", scope: "provider", required: true, placeholder: "kimi-k2.6" },
2727
- { key: "fallbackModel", label: "Fallback model", envKey: "KIMI_FALLBACK_MODEL", scope: "provider" },
2728
- ...SHARED_LOGIN_FIELDS,
2729
- ],
2730
3298
  };
2731
3299
  const DEPRECATED_MODEL_ENV_KEYS = [
2732
3300
  "MODEL_API_KEY",
@@ -2747,55 +3315,55 @@ const DEPRECATED_MODEL_ENV_KEYS = [
2747
3315
  "ANTHROPIC_TIMEOUT_MS",
2748
3316
  "ANTHROPIC_STREAM_IDLE_TIMEOUT_MS",
2749
3317
  "ANTHROPIC_MAX_RETRIES",
2750
- "DEEPSEEK_REASONING_EFFORT",
2751
- "DEEPSEEK_REASONING_SUMMARY",
2752
- "DEEPSEEK_MAX_OUTPUT_TOKENS",
2753
- "DEEPSEEK_TIMEOUT_MS",
2754
- "DEEPSEEK_STREAM_IDLE_TIMEOUT_MS",
2755
- "DEEPSEEK_MAX_RETRIES",
2756
- "KIMI_REASONING_EFFORT",
2757
- "KIMI_REASONING_SUMMARY",
2758
- "KIMI_MAX_OUTPUT_TOKENS",
2759
- "KIMI_TIMEOUT_MS",
2760
- "KIMI_STREAM_IDLE_TIMEOUT_MS",
2761
- "KIMI_MAX_RETRIES",
2762
- "MOONSHOT_REASONING_EFFORT",
2763
- "MOONSHOT_REASONING_SUMMARY",
2764
- "MOONSHOT_MAX_OUTPUT_TOKENS",
2765
- "MOONSHOT_TIMEOUT_MS",
2766
- "MOONSHOT_STREAM_IDLE_TIMEOUT_MS",
2767
- "MOONSHOT_MAX_RETRIES",
2768
3318
  ];
2769
- function sessionsPageCount(state) {
2770
- return Math.max(1, Math.ceil(state.sessions.length / state.pageSize));
3319
+ function pagedPageCount(state) {
3320
+ return Math.max(1, Math.ceil(state.items.length / state.pageSize));
2771
3321
  }
2772
- function sessionsPageItems(state) {
3322
+ function pagedPageItems(state) {
2773
3323
  const start = state.pageIndex * state.pageSize;
2774
- return state.sessions.slice(start, start + state.pageSize);
3324
+ return state.items.slice(start, start + state.pageSize);
2775
3325
  }
2776
- function sessionAbsoluteIndex(state) {
3326
+ function pagedAbsoluteIndex(state) {
2777
3327
  return state.pageIndex * state.pageSize + state.selectedIndex;
2778
3328
  }
2779
- function moveSessionsSelection(state, delta) {
2780
- const pageLength = sessionsPageItems(state).length;
3329
+ function movePagedSelection(state, delta) {
3330
+ const pageLength = pagedPageItems(state).length;
2781
3331
  if (pageLength <= 0)
2782
3332
  return state;
2783
3333
  const selectedIndex = (state.selectedIndex + delta + pageLength) % pageLength;
2784
3334
  return { ...state, selectedIndex };
2785
3335
  }
2786
- function moveSessionsPage(state, delta) {
2787
- const pageCount = sessionsPageCount(state);
3336
+ function movePagedPage(state, delta) {
3337
+ const pageCount = pagedPageCount(state);
2788
3338
  if (pageCount <= 1)
2789
3339
  return state;
2790
3340
  const pageIndex = (state.pageIndex + delta + pageCount) % pageCount;
2791
- const pageLength = state.sessions.slice(pageIndex * state.pageSize, pageIndex * state.pageSize + state.pageSize).length;
3341
+ const pageLength = state.items.slice(pageIndex * state.pageSize, pageIndex * state.pageSize + state.pageSize).length;
2792
3342
  return { ...state, pageIndex, selectedIndex: Math.min(state.selectedIndex, Math.max(0, pageLength - 1)) };
2793
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
+ }
2794
3356
  function sessionsBrowserViewHeight(state) {
2795
3357
  return sessionsPageItems(state).length + 3;
2796
3358
  }
3359
+ function skillsBrowserViewHeight(state) {
3360
+ return pagedPageItems(state).length + 3;
3361
+ }
3362
+ function secretsBrowserViewHeight(state) {
3363
+ return pagedPageItems(state).length + 3;
3364
+ }
2797
3365
  function SessionsBrowser({ state, width }) {
2798
- const pageCount = sessionsPageCount(state);
3366
+ const pageCount = pagedPageCount(state);
2799
3367
  const pageItems = sessionsPageItems(state);
2800
3368
  const showPagination = pageCount > 1;
2801
3369
  const contentWidth = Math.max(20, width);
@@ -2815,6 +3383,51 @@ function SessionsBrowser({ state, width }) {
2815
3383
  }, row.numberPrefix), row.rest);
2816
3384
  }), e(Text, { color: "gray" }, fitToWidth(footer, contentWidth)));
2817
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
+ }
2818
3431
  function handleLoginFormInput(value, key, state, setLoginFormState, runtime, append, setStatus) {
2819
3432
  if (key.escape) {
2820
3433
  if (state.step === "fields")
@@ -2983,12 +3596,6 @@ function loginValuesForProvider(provider, env) {
2983
3596
  for (const field of LOGIN_FIELD_DEFINITIONS[provider]) {
2984
3597
  values[field.key] = env[field.envKey] ?? "";
2985
3598
  }
2986
- if (provider === "kimi") {
2987
- values.apiKey ||= env.MOONSHOT_API_KEY ?? process.env.MOONSHOT_API_KEY ?? "";
2988
- values.baseUrl ||= env.MOONSHOT_BASE_URL ?? process.env.MOONSHOT_BASE_URL ?? "";
2989
- values.model ||= env.MOONSHOT_MODEL ?? process.env.MOONSHOT_MODEL ?? "";
2990
- values.fallbackModel ||= env.MOONSHOT_FALLBACK_MODEL ?? process.env.MOONSHOT_FALLBACK_MODEL ?? "";
2991
- }
2992
3599
  if (!values.baseUrl)
2993
3600
  values.baseUrl = defaultBaseUrlForLoginProvider(provider);
2994
3601
  if (!values.model)
@@ -2998,15 +3605,11 @@ function loginValuesForProvider(provider, env) {
2998
3605
  return values;
2999
3606
  }
3000
3607
  function parseLoginProvider(value) {
3001
- if (value === "openai" || value === "anthropic" || value === "deepseek" || value === "kimi")
3608
+ if (value === "openai" || value === "anthropic")
3002
3609
  return value;
3003
3610
  return undefined;
3004
3611
  }
3005
3612
  function guessLoginProvider(env) {
3006
- if (env.KIMI_API_KEY ?? env.MOONSHOT_API_KEY ?? process.env.KIMI_API_KEY ?? process.env.MOONSHOT_API_KEY)
3007
- return "kimi";
3008
- if (env.DEEPSEEK_API_KEY ?? process.env.DEEPSEEK_API_KEY)
3009
- return "deepseek";
3010
3613
  if (env.ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY)
3011
3614
  return "anthropic";
3012
3615
  return "openai";
@@ -3014,19 +3617,11 @@ function guessLoginProvider(env) {
3014
3617
  function defaultBaseUrlForLoginProvider(provider) {
3015
3618
  if (provider === "anthropic")
3016
3619
  return "https://api.anthropic.com";
3017
- if (provider === "deepseek")
3018
- return "https://api.deepseek.com";
3019
- if (provider === "kimi")
3020
- return "https://api.moonshot.cn/v1";
3021
3620
  return "https://api.openai.com";
3022
3621
  }
3023
3622
  function defaultModelForLoginProvider(provider) {
3024
3623
  if (provider === "anthropic")
3025
3624
  return "claude-sonnet-4-6";
3026
- if (provider === "deepseek")
3027
- return "deepseek-chat";
3028
- if (provider === "kimi")
3029
- return "kimi-k2.6";
3030
3625
  return "gpt-5.5";
3031
3626
  }
3032
3627
  function loginFormViewHeight(state) {
@@ -3045,7 +3640,7 @@ function LoginFormView({ state, width }) {
3045
3640
  const visibleValue = formatLoginFieldValue(field, rawValue, selected ? state.cursor : undefined);
3046
3641
  const placeholder = rawValue ? "" : (field.placeholder ? ` (${field.placeholder})` : "");
3047
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))));
3048
- }), 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_* / DEEPSEEK_* / KIMI_*; shared runtime fields save as MODEL_*.", contentWidth)));
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)));
3049
3644
  }
3050
3645
  function formatLoginFieldValue(field, value, cursor) {
3051
3646
  const display = field.secret && value ? "•".repeat(Math.min(value.length, 24)) : value;
@@ -3071,12 +3666,6 @@ function envEntriesForLoginForm(state) {
3071
3666
  const value = (state.values[field.key] ?? "").trim();
3072
3667
  entries[field.envKey] = value || undefined;
3073
3668
  }
3074
- if (state.provider === "kimi") {
3075
- entries.MOONSHOT_API_KEY = undefined;
3076
- entries.MOONSHOT_BASE_URL = undefined;
3077
- entries.MOONSHOT_MODEL = undefined;
3078
- entries.MOONSHOT_FALLBACK_MODEL = undefined;
3079
- }
3080
3669
  return entries;
3081
3670
  }
3082
3671
  function updateEnvContent(content, updates, removeKeys = []) {
@@ -3104,8 +3693,6 @@ function updateEnvContent(content, updates, removeKeys = []) {
3104
3693
  appendEnvGroup(updatedLines, "# Neo active provider", grouped.active);
3105
3694
  appendEnvGroup(updatedLines, "# OpenAI provider settings", grouped.openai);
3106
3695
  appendEnvGroup(updatedLines, "# Anthropic provider settings", grouped.anthropic);
3107
- appendEnvGroup(updatedLines, "# DeepSeek provider settings", grouped.deepseek);
3108
- appendEnvGroup(updatedLines, "# Kimi provider settings", grouped.kimi);
3109
3696
  appendEnvGroup(updatedLines, "# Shared model runtime settings", grouped.shared);
3110
3697
  }
3111
3698
  return `${updatedLines.join("\n").replace(/\n*$/u, "")}\n`;
@@ -3115,8 +3702,6 @@ function groupLoginEnvEntries(entries) {
3115
3702
  active: entries.filter(([key]) => key === "MODEL_PROVIDER"),
3116
3703
  openai: entries.filter(([key]) => key.startsWith("OPENAI_")),
3117
3704
  anthropic: entries.filter(([key]) => key.startsWith("ANTHROPIC_")),
3118
- deepseek: entries.filter(([key]) => key.startsWith("DEEPSEEK_")),
3119
- kimi: entries.filter(([key]) => key.startsWith("KIMI_") || key.startsWith("MOONSHOT_")),
3120
3705
  shared: entries.filter(([key]) => key.startsWith("MODEL_") && key !== "MODEL_PROVIDER"),
3121
3706
  };
3122
3707
  }
@@ -3366,12 +3951,18 @@ function execDescriptionFromInput(input) {
3366
3951
  function isPlanToolPayload(value) {
3367
3952
  if (!isRecord(value) || !Array.isArray(value.items))
3368
3953
  return false;
3369
- return value.items.every((item) => {
3370
- if (!isRecord(item))
3371
- return false;
3372
- return (typeof item.description === "string" &&
3373
- (item.status === "pending" || item.status === "in_progress" || item.status === "completed"));
3374
- });
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);
3375
3966
  }
3376
3967
  function planToolBodyTitle(payload) {
3377
3968
  const title = payload.title?.trim();
@@ -3383,16 +3974,25 @@ function formatPlanToolPayload(payload) {
3383
3974
  sections.push(payload.summary.trim());
3384
3975
  if (payload.note?.trim())
3385
3976
  sections.push(payload.note.trim());
3386
- sections.push(payload.items.map(formatPlanItem).join("\n"));
3977
+ sections.push(payload.items.flatMap((item) => formatPlanItem(item)).join("\n"));
3387
3978
  return sections.filter(Boolean).join("\n");
3388
3979
  }
3389
- function formatPlanItem(item) {
3980
+ function formatPlanItem(item, depth = 0) {
3981
+ const indent = " ".repeat(Math.max(0, depth));
3390
3982
  const text = escapePlanMarkdown(item.description.trim());
3391
- if (item.status === "completed")
3392
- return `- ~~${text}~~`;
3393
- if (item.status === "in_progress")
3394
- return `- ${text}`;
3395
- return `- ${text}`;
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 "○";
3396
3996
  }
3397
3997
  function escapePlanMarkdown(text) {
3398
3998
  return text.replace(/([\\`*_{}[\]()#+.!|>~-])/g, "\\$1");
@@ -4070,6 +4670,8 @@ const TERMINAL_TITLE_WORKING_PREFIX = "● ";
4070
4670
  const TERMINAL_TITLE_READY_PREFIX = "✓ ";
4071
4671
  const REPL_ANIMATION_INTERVAL_MS = 420;
4072
4672
  const TOOL_RESULT_REPLACEMENT_DELAY_MS = 2000;
4673
+ const SUBAGENT_ACTIVITY_UPDATE_DEBOUNCE_MS = 180;
4674
+ const SUBAGENT_COMPLETED_LINGER_MS = 8000;
4073
4675
  const TOKEN_PULSE_MS = 900;
4074
4676
  const ANIMATED_NUMBER_INTERVAL_MS = 50;
4075
4677
  const ANIMATED_NUMBER_MIN_DURATION_MS = 180;
@@ -4082,6 +4684,8 @@ const STATUS_SHIMMER_RADIUS = 1;
4082
4684
  const STATUS_SHIMMER_COLOR = "whiteBright";
4083
4685
  const STATUS_SEPARATOR = " · ";
4084
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;
4085
4689
  const BACKGROUND_TASK_STATUS_RENDER_ROWS = 1;
4086
4690
  const QUEUED_INPUT_RENDER_ROWS = 1;
4087
4691
  const EMPTY_CTRL_C_EXIT_PLACEHOLDER = "Press Ctrl+C again to exit";