neoctl 0.1.3 → 0.1.5

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.
@@ -19,6 +19,7 @@ import { createExecTool } from "../tools/builtins/exec-tool.js";
19
19
  import { listDirectoryTool, readFileTool } from "../tools/builtins/filesystem-tools.js";
20
20
  import { grepTool } from "../tools/builtins/grep-tool.js";
21
21
  import { searchTool } from "../tools/builtins/search-tool.js";
22
+ import { planTool } from "../tools/builtins/plan-tool.js";
22
23
  import { createAgentTool, resumeAgentTask } from "../agents/agent-tool.js";
23
24
  import { createTaskTools } from "../tasks/task-tools.js";
24
25
  import { TaskStore } from "../tasks/task-store.js";
@@ -121,6 +122,7 @@ async function createRuntime() {
121
122
  tools.register(readFileTool);
122
123
  tools.register(grepTool);
123
124
  tools.register(searchTool);
125
+ tools.register(planTool);
124
126
  const agentRuntime = { modelGateway, tools, taskStore };
125
127
  tools.register(createAgentTool(agentRuntime));
126
128
  const resumeHandler = async (taskId, directive) => {
@@ -737,6 +739,62 @@ function InkRepl({ runtime }) {
737
739
  append({ kind: "system", text: formatUsageTotals(runtime.usage.snapshot()) });
738
740
  return;
739
741
  }
742
+ if (command.type === "compact") {
743
+ const abortController = new AbortController();
744
+ activeAbortController.current = abortController;
745
+ interruptArmed.current = false;
746
+ setBusyState(true);
747
+ setStatus((current) => ({ ...current, phase: "compacting", detail: "manual compact", activityTick: current.activityTick + 1 }));
748
+ try {
749
+ const result = await runtime.engine.compact({ abortSignal: abortController.signal });
750
+ const metrics = await runtime.engine.contextMetrics();
751
+ append(systemLine(formatManualCompaction(result)));
752
+ setStatus((current) => reduceStatus(current, { type: "context.metrics", metrics }));
753
+ }
754
+ catch (error) {
755
+ append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
756
+ }
757
+ finally {
758
+ if (activeAbortController.current === abortController)
759
+ activeAbortController.current = undefined;
760
+ interruptArmed.current = false;
761
+ setBusyState(false);
762
+ setStatus((current) => ({ ...current, phase: "ready", detail: undefined, activityTick: current.activityTick + 1 }));
763
+ const queued = takeQueuedPromptState();
764
+ if (queued !== undefined) {
765
+ void submitLine(queued.text, queued.attachments);
766
+ }
767
+ }
768
+ return;
769
+ }
770
+ if (command.type === "pure") {
771
+ const abortController = new AbortController();
772
+ activeAbortController.current = abortController;
773
+ interruptArmed.current = false;
774
+ setBusyState(true);
775
+ setStatus((current) => ({ ...current, phase: "compacting", detail: "pure compact", activityTick: current.activityTick + 1 }));
776
+ try {
777
+ const result = await runtime.engine.pureCompact({ abortSignal: abortController.signal });
778
+ const metrics = await runtime.engine.contextMetrics();
779
+ append(systemLine(formatPureCompaction(result)));
780
+ setStatus((current) => reduceStatus(current, { type: "context.metrics", metrics }));
781
+ }
782
+ catch (error) {
783
+ append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
784
+ }
785
+ finally {
786
+ if (activeAbortController.current === abortController)
787
+ activeAbortController.current = undefined;
788
+ interruptArmed.current = false;
789
+ setBusyState(false);
790
+ setStatus((current) => ({ ...current, phase: "ready", detail: undefined, activityTick: current.activityTick + 1 }));
791
+ const queued = takeQueuedPromptState();
792
+ if (queued !== undefined) {
793
+ void submitLine(queued.text, queued.attachments);
794
+ }
795
+ }
796
+ return;
797
+ }
740
798
  if (command.type === "reset") {
741
799
  runtime.engine.reset();
742
800
  runtime.usage.reset();
@@ -2028,6 +2086,16 @@ function formatUsageTotals(totals) {
2028
2086
  lines.push(` Cached input tokens: ${formatNumber(totals.cachedTokens)}`);
2029
2087
  return lines.join("\n");
2030
2088
  }
2089
+ function formatManualCompaction(result) {
2090
+ if (!result.changed)
2091
+ return "No earlier context available to compact.";
2092
+ return `manual context compacted: ${result.messages.length} messages retained, ${formatNumber(result.tokensFreed ?? 0)} chars freed`;
2093
+ }
2094
+ function formatPureCompaction(result) {
2095
+ if (!result.changed)
2096
+ return "No context available to purify.";
2097
+ return `pure context compacted: ${result.messages.length} sanitized message(s) retained, ${formatNumber(result.tokensFreed ?? 0)} chars removed; raw command/log/code details omitted`;
2098
+ }
2031
2099
  function colorForKind(kind) {
2032
2100
  if (kind === "user")
2033
2101
  return "cyan";
@@ -2118,6 +2186,13 @@ function metaLine(text) {
2118
2186
  };
2119
2187
  }
2120
2188
  function formatToolUse(toolUse) {
2189
+ if (toolUse.name === "plan" && isPlanToolPayload(toolUse.input)) {
2190
+ return {
2191
+ kind: "tool",
2192
+ title: toolTitle(toolUse.name, "running"),
2193
+ text: formatPlanToolPayload(toolUse.input),
2194
+ };
2195
+ }
2121
2196
  return {
2122
2197
  kind: "tool",
2123
2198
  title: toolTitle(toolUse.name, "running"),
@@ -2157,8 +2232,42 @@ function formatToolFinishedWithoutResult(toolUse, ok) {
2157
2232
  };
2158
2233
  }
2159
2234
  function toolTitle(toolName, phase) {
2235
+ if (toolName === "plan")
2236
+ return `${phase === "running" ? "◇" : "◆"} plan`;
2160
2237
  return `${phase === "running" ? "◇" : "◆"} ${toolName}`;
2161
2238
  }
2239
+ function isPlanToolPayload(value) {
2240
+ if (!isRecord(value) || !Array.isArray(value.items))
2241
+ return false;
2242
+ return value.items.every((item) => {
2243
+ if (!isRecord(item))
2244
+ return false;
2245
+ return (typeof item.description === "string" &&
2246
+ (item.status === "pending" || item.status === "in_progress" || item.status === "completed"));
2247
+ });
2248
+ }
2249
+ function formatPlanToolPayload(payload) {
2250
+ const sections = [];
2251
+ if (payload.title?.trim())
2252
+ sections.push(`**${payload.title.trim()}**`);
2253
+ if (payload.summary?.trim())
2254
+ sections.push(payload.summary.trim());
2255
+ if (payload.note?.trim())
2256
+ sections.push(payload.note.trim());
2257
+ sections.push(payload.items.map(formatPlanItem).join("\n"));
2258
+ return sections.filter(Boolean).join("\n");
2259
+ }
2260
+ function formatPlanItem(item) {
2261
+ const text = escapePlanMarkdown(item.description.trim());
2262
+ if (item.status === "completed")
2263
+ return `- ~~${text}~~`;
2264
+ if (item.status === "in_progress")
2265
+ return `- ▶ ${text}`;
2266
+ return `- ${text}`;
2267
+ }
2268
+ function escapePlanMarkdown(text) {
2269
+ return text.replace(/([\\`*_{}[\]()#+.!|>~-])/g, "\\$1");
2270
+ }
2162
2271
  function formatJson(value, maxLength) {
2163
2272
  return formatReplData(value, maxLength);
2164
2273
  }
@@ -2264,6 +2373,9 @@ function formatToolResult(toolName, output, ok) {
2264
2373
  if (toolName === "search" && isRecord(output)) {
2265
2374
  return { text: formatWebSearchToolResult(output, ok), summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
2266
2375
  }
2376
+ if (toolName === "plan" && isPlanToolPayload(output)) {
2377
+ return { text: formatPlanToolPayload(output), full: true };
2378
+ }
2267
2379
  return { text: `${ok ? "ok" : "failed"}\n${formatJson(output, 6000)}`, summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
2268
2380
  }
2269
2381
  function isEditToolOutput(value) {