neoctl 0.2.13 → 0.2.14

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.
@@ -260,7 +260,7 @@ async function createRuntime() {
260
260
  tools.register(createLoadImageTool());
261
261
  tools.register(createImageNoteTool());
262
262
  if (modelConfig?.provider === "openai")
263
- tools.register(createOpenAIImageGenerationTool());
263
+ tools.register(createOpenAIImageGenerationTool({ taskStore, foregroundDetachRegistry: foregroundExecDetach }));
264
264
  tools.register(planTool);
265
265
  for (const tool of createSecretTools())
266
266
  tools.register(tool);
@@ -330,7 +330,7 @@ async function createRuntime() {
330
330
  function syncImageGenerationTool(runtime, provider) {
331
331
  runtime.tools.unregister("image2");
332
332
  if (provider === "openai")
333
- runtime.tools.register(createOpenAIImageGenerationTool());
333
+ runtime.tools.register(createOpenAIImageGenerationTool({ taskStore: runtime.taskStore, foregroundDetachRegistry: runtime.foregroundExecDetach }));
334
334
  }
335
335
  function formatCreatedEnvNotice(path) {
336
336
  return `Created default config file: ${path}\nSet MODEL_PROVIDER and the matching provider section (OPENAI_API_KEY or ANTHROPIC_API_KEY), then restart neo.`;
@@ -1352,7 +1352,7 @@ function InkRepl({ runtime, initialCommandLine }) {
1352
1352
  if (key.ctrl && value.toLowerCase() === "b") {
1353
1353
  const result = runtime.foregroundExecDetach.detachCurrent();
1354
1354
  append(result.ok
1355
- ? systemLine(`Detached foreground exec to background task ${result.taskId ?? "unknown"}.`)
1355
+ ? systemLine(`Detached foreground task to background task ${result.taskId ?? "unknown"}.`)
1356
1356
  : systemLine(result.message));
1357
1357
  return;
1358
1358
  }
@@ -1656,7 +1656,7 @@ function InkRepl({ runtime, initialCommandLine }) {
1656
1656
  return;
1657
1657
  }
1658
1658
  });
1659
- return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: visibleDynamicLines, width, lineIndexOffset: staticLines.length, onMarkdownRenderComplete: markLineRendered }), sessionsBrowser ? e(SessionsBrowser, { state: sessionsBrowser, width }) : null, skillsBrowser ? e(SkillsBrowser, { state: skillsBrowser, width }) : null, secretsBrowser ? e(SecretsBrowser, { state: secretsBrowser, width }) : null, loginForm ? e(LoginFormView, { state: loginForm, width }) : null, e(StatusBar, { status, animationTick, width }), showForegroundExecDetachHint && foregroundExecDetachHandle ? e(ForegroundExecDetachHintLine, { handle: foregroundExecDetachHandle, width }) : null, agentActivities.length > 0 ? e(SubagentLivePanel, { activities: agentActivities, width, terminalRows: terminalSize.rows, compact: compactLiveLayout, 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 }));
1659
+ return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: visibleDynamicLines, width, 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, 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 }));
1660
1660
  }
1661
1661
  const MessageList = React.memo(function MessageList({ lines, width, lineIndexOffset = 0, onMarkdownRenderComplete }) {
1662
1662
  const contentWidth = messageContentWidth(width);
@@ -1994,37 +1994,26 @@ function backgroundTaskStatusRenderRows(taskCount) {
1994
1994
  }
1995
1995
  function ForegroundExecDetachHintLine({ handle, width: terminalWidth }) {
1996
1996
  const width = statusBarWidth(terminalWidth);
1997
+ const toolName = handle.toolName?.trim() || "exec";
1997
1998
  const label = handle.description?.trim() || handle.command;
1998
- const text = `↳ exec still running · Ctrl+B to detach · ${truncateMiddle(label, Math.max(12, width - 38))}`;
1999
+ const text = `↳ ${toolName} still running · Ctrl+B to detach · ${truncateMiddle(label, Math.max(12, width - toolName.length - 33))}`;
1999
2000
  return e(Text, { color: "yellow" }, fitToWidth(text, width));
2000
2001
  }
2001
- function SubagentLivePanel({ activities, width: terminalWidth, terminalRows, compact, animationTick }) {
2002
+ function SubagentLivePanel({ activities, width: terminalWidth, animationTick }) {
2002
2003
  const width = statusBarWidth(terminalWidth);
2003
- const rows = subagentLivePanelRenderRows(activities, terminalRows, compact);
2004
- if (rows <= 0)
2005
- return null;
2006
2004
  const sorted = sortAgentActivitiesForPanel(activities);
2007
2005
  const selected = sorted[0];
2008
2006
  if (!selected)
2009
2007
  return null;
2010
2008
  const activeCount = activities.filter((activity) => activity.status === "running" || activity.status === "pending").length;
2011
- const header = `◆ subagents: ${activeCount} active${activities.length > activeCount ? ` · ${activities.length - activeCount} recent` : ""}`;
2012
- if (rows <= 1) {
2013
- return e(Text, { color: "yellow" }, fitToWidth(`${header} · ${compactAgentSummary(selected, width - header.length - 3)}`, width));
2014
- }
2015
- const detailLines = buildSubagentDetailLines(selected, sorted, animationTick);
2016
- return e(Box, { flexDirection: "column", width, overflow: "hidden" }, e(Text, { color: "yellow" }, fitToWidth(header, width)), ...detailLines.map((line, index) => e(Text, {
2017
- key: `agent-detail-${selected.agentId}-${index}`,
2018
- color: line.color,
2019
- }, fitToWidth(line.text, width))));
2020
- }
2021
- const SUBAGENT_DETAIL_ROWS = 3;
2022
- function subagentLivePanelRenderRows(activities, terminalRows, compact = false) {
2023
- if (activities.length === 0)
2024
- return 0;
2025
- if (compact || terminalRows < 22 || activities.length > 1)
2026
- return 1;
2027
- return 1 + SUBAGENT_DETAIL_ROWS;
2009
+ const recentCount = Math.max(0, activities.length - activeCount);
2010
+ const countText = activeCount > 1
2011
+ ? `${activeCount} active${recentCount ? ` · ${recentCount} recent` : ""}`
2012
+ : recentCount && activeCount === 0
2013
+ ? `${recentCount} recent`
2014
+ : "";
2015
+ const text = `↳ subagent ${subagentStatusText(selected.status, animationTick)}${countText ? ` · ${countText}` : ""} · ${compactAgentSummary(selected, width)}`;
2016
+ return e(Text, { color: statusColor(selected.status) }, fitToWidth(text, width));
2028
2017
  }
2029
2018
  function sortAgentActivitiesForPanel(activities) {
2030
2019
  const rank = (status) => {
@@ -2038,76 +2027,35 @@ function sortAgentActivitiesForPanel(activities) {
2038
2027
  };
2039
2028
  return [...activities].sort((left, right) => rank(left.status) - rank(right.status) || right.updatedAt.localeCompare(left.updatedAt));
2040
2029
  }
2041
- function buildSubagentDetailLines(selected, sorted, animationTick) {
2042
- const spinner = selected.status === "running" ? spinnerFrame(animationTick) : statusGlyph(selected.status);
2043
- const elapsed = formatElapsed(Date.now() - new Date(selected.startedAt).getTime());
2044
- const headerLine = `${spinner} ${selected.description || selected.agentId} · ${elapsed}`;
2045
- const currentLine = selected.currentTool
2046
- ? `→ ${selected.currentTool.name}${selected.currentTool.inputPreview ? ` · ${selected.currentTool.inputPreview}` : ""}`
2047
- : selected.error
2048
- ? `✖ ${selected.error}`
2049
- : selected.resultPreview
2050
- ? `✓ ${selected.resultPreview}`
2051
- : selected.lastText
2052
- ? `• ${selected.lastText}`
2053
- : `• ${selected.prompt}`;
2054
- const recent = selected.timeline.slice(-2).map((entry) => `${timelinePrefix(entry)} ${formatTimelineEntry(entry, 240)}`);
2055
- const otherRunning = sorted
2056
- .filter((activity) => activity.agentId !== selected.agentId && (activity.status === "running" || activity.status === "pending"))
2057
- .slice(0, 2)
2058
- .map((activity) => compactAgentSummary(activity, 180));
2059
- const tail = [...recent, ...otherRunning.map((summary) => `· ${summary}`)].find((line) => line.trim()) ?? `tools:${selected.totalToolUseCount}`;
2060
- return [
2061
- { text: headerLine, color: statusColor(selected.status) },
2062
- { text: currentLine, color: selected.error ? "red" : selected.currentTool ? "#d4b04c" : "yellow" },
2063
- { text: tail, color: "gray" },
2064
- ];
2065
- }
2066
2030
  function compactAgentSummary(activity, maxLength) {
2067
2031
  const current = activity.currentTool
2068
2032
  ? `${activity.currentTool.name}${activity.currentTool.inputPreview ? ` ${activity.currentTool.inputPreview}` : ""}`
2069
- : activity.lastText ?? activity.resultPreview ?? activity.error ?? activity.prompt;
2033
+ : firstSafeSubagentPreview(activity.resultPreview, activity.error, activity.lastText, activity.prompt);
2070
2034
  const elapsed = formatElapsed(Date.now() - new Date(activity.startedAt).getTime());
2071
2035
  return truncateMiddle(`${activity.description || activity.agentId} · ${elapsed} · tools:${activity.totalToolUseCount} · ${current.replace(/\s+/g, " ")}`, Math.max(8, maxLength));
2072
2036
  }
2073
- function formatTimelineEntry(entry, maxLength) {
2074
- const detail = entry.detail ? ` · ${entry.detail.replace(/\s+/g, " ")}` : "";
2075
- return truncateMiddle(`${entry.title}${detail}`, Math.max(8, maxLength));
2076
- }
2077
- function timelinePrefix(entry) {
2078
- if (entry.kind === "tool_start")
2079
- return "";
2080
- if (entry.kind === "tool_result")
2081
- return entry.status === "failed" ? "✖" : "←";
2082
- if (entry.kind === "thinking")
2083
- return "◆";
2084
- if (entry.kind === "error")
2085
- return "✖";
2086
- if (entry.kind === "status")
2087
- return "•";
2088
- return "assistant:";
2089
- }
2090
- function timelineColor(entry) {
2091
- if (entry.status === "failed" || entry.kind === "error")
2092
- return "red";
2093
- if (entry.kind === "tool_start" || entry.kind === "tool_result")
2094
- return "#d4b04c";
2095
- if (entry.kind === "thinking")
2096
- return THINKING_COLOR;
2097
- if (entry.kind === "status")
2098
- return "gray";
2099
- return "green";
2037
+ function firstSafeSubagentPreview(...values) {
2038
+ return values.find((value) => value && !isInternalContinuationPreview(value)) ?? "working";
2039
+ }
2040
+ function isInternalContinuationPreview(value) {
2041
+ const normalized = value.toLowerCase();
2042
+ return normalized.includes("<compact_state>")
2043
+ || normalized.includes("internal continuation state")
2044
+ || normalized.includes("auto compact")
2045
+ || normalized.includes("conversation summary generated by compact");
2100
2046
  }
2101
- function statusGlyph(status) {
2047
+ function subagentStatusText(status, animationTick) {
2048
+ if (status === "running")
2049
+ return `still running ${spinnerFrame(animationTick)}`;
2050
+ if (status === "pending")
2051
+ return "pending";
2102
2052
  if (status === "completed")
2103
- return "";
2053
+ return "completed";
2104
2054
  if (status === "failed")
2105
- return "";
2055
+ return "failed";
2106
2056
  if (status === "killed")
2107
- return "";
2108
- if (status === "pending")
2109
- return "…";
2110
- return "●";
2057
+ return "killed";
2058
+ return status;
2111
2059
  }
2112
2060
  function statusColor(status) {
2113
2061
  if (status === "completed")