zencefyl 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -774,12 +774,19 @@ function getReauthModel() {
774
774
  return _model;
775
775
  }
776
776
  var _restartRequested = false;
777
+ var _updateRequested = false;
777
778
  function requestRestart() {
778
779
  _restartRequested = true;
779
780
  }
780
781
  function isRestartRequested() {
781
782
  return _restartRequested;
782
783
  }
784
+ function requestUpdate() {
785
+ _updateRequested = true;
786
+ }
787
+ function isUpdateRequested() {
788
+ return _updateRequested;
789
+ }
783
790
 
784
791
  // src/utils/validate-config.ts
785
792
  var ConfigError = class extends Error {
@@ -3149,8 +3156,8 @@ function runMigrations(db) {
3149
3156
  const pending = files.filter((f) => !applied.has(f.version));
3150
3157
  if (pending.length === 0) return;
3151
3158
  const applyAll = db.transaction(() => {
3152
- for (const { version, path: path21 } of pending) {
3153
- const sql = readFileSync3(path21, "utf8");
3159
+ for (const { version, path: path22 } of pending) {
3160
+ const sql = readFileSync3(path22, "utf8");
3154
3161
  db.exec(sql);
3155
3162
  db.prepare("INSERT INTO schema_migrations (version) VALUES (?)").run(version);
3156
3163
  console.log(`[zencefyl] applied migration ${version.toString().padStart(3, "0")}`);
@@ -3517,8 +3524,8 @@ var ActionTaskRegistry = class {
3517
3524
  }
3518
3525
  if (toolName === "read-many-files" && Array.isArray(input["paths"])) {
3519
3526
  for (const value of input["paths"]) {
3520
- const path21 = String(value);
3521
- if (!task.filesTouched.includes(path21)) task.filesTouched.push(path21);
3527
+ const path22 = String(value);
3528
+ if (!task.filesTouched.includes(path22)) task.filesTouched.push(path22);
3522
3529
  }
3523
3530
  task.detail = `${task.filesTouched.length} files`;
3524
3531
  }
@@ -4120,7 +4127,7 @@ import { readFileSync as readFileSync4 } from "fs";
4120
4127
  import { fileURLToPath as fileURLToPath2 } from "url";
4121
4128
  import { dirname as dirname2, resolve } from "path";
4122
4129
  var VERSION = (() => {
4123
- if (true) return "0.2.7";
4130
+ if (true) return "0.2.8";
4124
4131
  const dir = dirname2(fileURLToPath2(import.meta.url));
4125
4132
  return JSON.parse(readFileSync4(resolve(dir, "../../package.json"), "utf8")).version;
4126
4133
  })();
@@ -4550,16 +4557,16 @@ var readTopicTool = {
4550
4557
  if (!parsed.success) {
4551
4558
  return { content: `Invalid input: ${parsed.error.issues.map((i) => i.message).join(", ")}`, isError: true };
4552
4559
  }
4553
- const path21 = parsed.data.path.trim();
4560
+ const path22 = parsed.data.path.trim();
4554
4561
  const includeEvidence = parsed.data.include_evidence ?? true;
4555
- if (!path21) {
4562
+ if (!path22) {
4556
4563
  return { content: "Error: path must not be empty.", isError: true };
4557
4564
  }
4558
- let topic = ctx.store.getTopicByPath(path21);
4565
+ let topic = ctx.store.getTopicByPath(path22);
4559
4566
  if (!topic) {
4560
- const domain2 = path21.split("/")[0] ?? path21;
4567
+ const domain2 = path22.split("/")[0] ?? path22;
4561
4568
  const allTopics2 = ctx.store.getTopicsByDomain(domain2);
4562
- const lower = path21.toLowerCase();
4569
+ const lower = path22.toLowerCase();
4563
4570
  topic = allTopics2.find((t) => t.fullPath.toLowerCase() === lower) ?? null;
4564
4571
  if (!topic) {
4565
4572
  const partial = allTopics2.find(
@@ -4570,7 +4577,7 @@ var readTopicTool = {
4570
4577
  }
4571
4578
  if (!topic) {
4572
4579
  return {
4573
- content: `No topic found for "${path21}".
4580
+ content: `No topic found for "${path22}".
4574
4581
 
4575
4582
  This topic hasn't been logged yet. If the user just learned something about it, use log-evidence to record it.`
4576
4583
  };
@@ -5057,6 +5064,12 @@ import { z as z8 } from "zod";
5057
5064
  var TOOL_DESCRIPTION8 = "Write or replace a file inside the current workspace. Use this when creating small programs, config files, or straightforward code edits.";
5058
5065
 
5059
5066
  // src/tools/filesystem/write-file/index.ts
5067
+ function previewNewContent(content, maxLines = 8) {
5068
+ const lines = content.split("\n");
5069
+ const shown = lines.slice(0, maxLines).map((line) => `+ ${line}`);
5070
+ if (lines.length > maxLines) shown.push(`+ ... (${lines.length - maxLines} more lines)`);
5071
+ return shown.join("\n");
5072
+ }
5060
5073
  var InputSchema8 = z8.object({
5061
5074
  path: z8.string().min(1),
5062
5075
  content: z8.string()
@@ -5088,8 +5101,17 @@ var writeFileTool = {
5088
5101
  const filePath = resolveWorkspacePath(parsed.data.path);
5089
5102
  ensureParentDir(filePath);
5090
5103
  fs8.writeFileSync(filePath, parsed.data.content, "utf8");
5104
+ const relPath = path12.relative(process.cwd(), filePath) || parsed.data.path;
5105
+ const lineCount = parsed.data.content.split("\n").length;
5091
5106
  return {
5092
- content: `Wrote ${path12.relative(process.cwd(), filePath) || parsed.data.path} (${parsed.data.content.length} chars).`
5107
+ content: [
5108
+ `Wrote ${relPath} (${lineCount} lines, ${parsed.data.content.length} chars).`,
5109
+ "",
5110
+ "Added:",
5111
+ "```diff",
5112
+ previewNewContent(parsed.data.content),
5113
+ "```"
5114
+ ].join("\n")
5093
5115
  };
5094
5116
  } catch (err) {
5095
5117
  return { content: `Failed to write file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
@@ -5106,6 +5128,15 @@ import { z as z9 } from "zod";
5106
5128
  var TOOL_DESCRIPTION9 = "Perform an exact text replacement inside a workspace file. Prefer this over full-file rewrites when only a small targeted edit is needed.";
5107
5129
 
5108
5130
  // src/tools/filesystem/replace-in-file/index.ts
5131
+ function previewDiff(search2, replace, maxLines = 6) {
5132
+ const removed = search2.split("\n").slice(0, maxLines).map((line) => `- ${line}`);
5133
+ const added = replace.split("\n").slice(0, maxLines).map((line) => `+ ${line}`);
5134
+ const extraRemoved = search2.split("\n").length - Math.min(search2.split("\n").length, maxLines);
5135
+ const extraAdded = replace.split("\n").length - Math.min(replace.split("\n").length, maxLines);
5136
+ if (extraRemoved > 0) removed.push(`- ... (${extraRemoved} more removed lines)`);
5137
+ if (extraAdded > 0) added.push(`+ ... (${extraAdded} more added lines)`);
5138
+ return [...removed, ...added].join("\n");
5139
+ }
5109
5140
  var InputSchema9 = z9.object({
5110
5141
  path: z9.string().min(1),
5111
5142
  search: z9.string(),
@@ -5156,8 +5187,16 @@ var replaceInFileTool = {
5156
5187
  ensureParentDir(filePath);
5157
5188
  fs9.writeFileSync(filePath, updated, "utf8");
5158
5189
  const replacements = parsed.data.replace_all ? original.split(parsed.data.search).length - 1 : 1;
5190
+ const relPath = path13.relative(process.cwd(), filePath) || parsed.data.path;
5159
5191
  return {
5160
- content: `Updated ${path13.relative(process.cwd(), filePath) || parsed.data.path} (${replacements} replacement${replacements === 1 ? "" : "s"}).`
5192
+ content: [
5193
+ `Updated ${relPath} (${replacements} replacement${replacements === 1 ? "" : "s"}).`,
5194
+ "",
5195
+ "Changed:",
5196
+ "```diff",
5197
+ previewDiff(parsed.data.search, parsed.data.replace),
5198
+ "```"
5199
+ ].join("\n")
5161
5200
  };
5162
5201
  } catch (err) {
5163
5202
  return { content: `Failed to replace text in file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
@@ -5746,7 +5785,9 @@ var Engine = class {
5746
5785
  //
5747
5786
  // Abort: if signal fires mid-stream, the generator returns without
5748
5787
  // committing anything to history (user turn is rolled back).
5749
- async *sendMessage(text2, signal) {
5788
+ async *sendMessage(text2, signal, options) {
5789
+ const effectiveText = options?.effectiveText ?? text2;
5790
+ const turnTools = this.resolveTurnTools(options?.toolPolicy ?? "default");
5750
5791
  const now = /* @__PURE__ */ new Date();
5751
5792
  if (this.lastMessageTime !== null) {
5752
5793
  const elapsedSeconds = Math.round((now.getTime() - this.lastMessageTime.getTime()) / 1e3);
@@ -5760,14 +5801,14 @@ var Engine = class {
5760
5801
  }
5761
5802
  }
5762
5803
  const model = session.model || this.container.config.models.default;
5763
- const requiresExecution = this.requiresToolExecution(text2);
5804
+ const requiresExecution = this.requiresToolExecution(effectiveText);
5764
5805
  const actionTaskId = requiresExecution ? this.container.actionTasks.start(text2) : null;
5765
5806
  const recentTaskContext = this.buildRecentTaskContext(text2, actionTaskId);
5766
5807
  if (actionTaskId) {
5767
5808
  logRuntimeEvent("task.started", actionTaskId);
5768
5809
  }
5769
- const executionBootstrap = requiresExecution ? await this.buildExecutionBootstrap(text2, actionTaskId) : "";
5770
- const baseSystemPrompt = await this.promptBuilder.build(text2);
5810
+ const executionBootstrap = requiresExecution ? await this.buildExecutionBootstrap(effectiveText, actionTaskId) : "";
5811
+ const baseSystemPrompt = await this.promptBuilder.build(effectiveText);
5771
5812
  const supportsNativeTools = this.providerSupportsNativeTools();
5772
5813
  let finalText = "";
5773
5814
  try {
@@ -5782,7 +5823,7 @@ var Engine = class {
5782
5823
  );
5783
5824
  const workingMessages = [
5784
5825
  ...this.history,
5785
- { role: "user", content: text2 }
5826
+ { role: "user", content: effectiveText }
5786
5827
  ];
5787
5828
  let usedAnyToolsThisTurn = false;
5788
5829
  finalText = "";
@@ -5797,7 +5838,7 @@ var Engine = class {
5797
5838
  workingMessages,
5798
5839
  systemPrompt,
5799
5840
  attemptModel,
5800
- { signal: attemptSignal, tools: this.tools }
5841
+ { signal: attemptSignal, tools: turnTools }
5801
5842
  )) {
5802
5843
  switch (delta.type) {
5803
5844
  case "text":
@@ -5846,7 +5887,7 @@ var Engine = class {
5846
5887
  workingMessages.push({ role: "assistant", content: assistantContent });
5847
5888
  const toolResultContent = [];
5848
5889
  for (const tc of toolCallsThisIteration) {
5849
- const toolDef = this.tools.find((t) => t.name === tc.name);
5890
+ const toolDef = turnTools.find((t) => t.name === tc.name);
5850
5891
  let result = { content: `Unknown tool: ${tc.name}`, isError: true };
5851
5892
  if (toolDef) {
5852
5893
  if (actionTaskId) {
@@ -5945,7 +5986,7 @@ var Engine = class {
5945
5986
  void this.runPassiveExtraction(text2, finalText);
5946
5987
  }
5947
5988
  if (actionTaskId) {
5948
- const validation = this.validateActionTaskCompletion(text2, actionTaskId);
5989
+ const validation = this.validateActionTaskCompletion(effectiveText, actionTaskId);
5949
5990
  if (finalText.length > 0 && validation.ok) {
5950
5991
  this.container.actionTasks.complete(actionTaskId, finalText);
5951
5992
  logRuntimeEvent("task.completed", actionTaskId);
@@ -6049,6 +6090,13 @@ var Engine = class {
6049
6090
  }
6050
6091
  return false;
6051
6092
  }
6093
+ resolveTurnTools(policy) {
6094
+ if (policy === "none") return [];
6095
+ if (policy === "document-sources") {
6096
+ return this.tools.filter((tool) => tool.name === "list-files" || tool.name === "search-files" || tool.name === "read-file" || tool.name === "read-many-files" || tool.name === "run-command");
6097
+ }
6098
+ return this.tools;
6099
+ }
6052
6100
  prepareSystemPrompt(systemPrompt, supportsNativeTools, requiresExecution, executionBootstrap, recentTaskContext, enforceRetry) {
6053
6101
  let prompt = this.shouldUseCompactExecutionPrompt(requiresExecution) ? this.buildCompactExecutionPrompt() : systemPrompt;
6054
6102
  prompt = supportsNativeTools ? prompt : this.withTextToolProtocol(prompt);
@@ -6340,8 +6388,8 @@ Likely target from request: ${likelyTarget}`);
6340
6388
  };
6341
6389
 
6342
6390
  // src/cli/App.tsx
6343
- import { useState as useState14, useCallback as useCallback3, useRef as useRef3, useMemo as useMemo7, useEffect as useEffect7 } from "react";
6344
- import { Box as Box16, Text as Text16, useApp, Static } from "ink";
6391
+ import { useState as useState16, useCallback as useCallback3, useRef as useRef3, useMemo as useMemo10, useEffect as useEffect8 } from "react";
6392
+ import { Box as Box17, Text as Text17, useApp, Static } from "ink";
6345
6393
 
6346
6394
  // src/constants/thinkingVerbs.ts
6347
6395
  var THINKING_VERBS = [
@@ -7354,7 +7402,7 @@ function MessageComponent({ message }) {
7354
7402
  ] }, i)) });
7355
7403
  }
7356
7404
  const isUser = message.role === "user";
7357
- const text2 = messageText(message.content);
7405
+ const text2 = message.displayText ?? messageText(message.content);
7358
7406
  if (!text2) return null;
7359
7407
  return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginBottom: 1, children: [
7360
7408
  /* @__PURE__ */ jsxs2(Box3, { children: [
@@ -7551,6 +7599,18 @@ function saveHistory(history) {
7551
7599
  }
7552
7600
  }
7553
7601
 
7602
+ // src/constants/update-notes.ts
7603
+ var UPDATE_NOTES = [
7604
+ {
7605
+ version: "0.2.7",
7606
+ title: "Stability Update",
7607
+ message: "Fixes sqlite-vec crashes on some machines, improves document export, and tightens artifact-style task output."
7608
+ }
7609
+ ];
7610
+ function findUpdateNote(version) {
7611
+ return UPDATE_NOTES.find((note) => note.version === version) ?? null;
7612
+ }
7613
+
7554
7614
  // src/services/updateCheck.ts
7555
7615
  var REGISTRY_URL = "https://registry.npmjs.org/zencefyl/latest";
7556
7616
  var TIMEOUT_MS = 4e3;
@@ -7580,6 +7640,15 @@ async function checkForUpdate() {
7580
7640
  return null;
7581
7641
  }
7582
7642
  }
7643
+ async function getUpdateInfo() {
7644
+ const latest = await checkForUpdate();
7645
+ if (!latest) return null;
7646
+ const note = findUpdateNote(latest);
7647
+ return {
7648
+ latest,
7649
+ ...note ? { title: note.title, message: note.message } : {}
7650
+ };
7651
+ }
7583
7652
 
7584
7653
  // src/cli/components/Duck.tsx
7585
7654
  import { useState as useState3, useEffect as useEffect2, useCallback, useRef } from "react";
@@ -7757,7 +7826,9 @@ function Duck({
7757
7826
  messageCount,
7758
7827
  lastAssistantText,
7759
7828
  lastDuckMention,
7760
- generateSpeech
7829
+ generateSpeech,
7830
+ generateOmen,
7831
+ currentPath
7761
7832
  }) {
7762
7833
  const [frame, setFrame] = useState3(0);
7763
7834
  const [message, setMessage] = useState3(null);
@@ -7770,10 +7841,12 @@ function Duck({
7770
7841
  const prevHasErrorRef = useRef(false);
7771
7842
  const nextMilestoneRef = useRef(randomMilestoneOffset(0));
7772
7843
  const isMountedRef = useRef(true);
7844
+ const omenFlashTimerRef = useRef(null);
7773
7845
  useEffect2(() => {
7774
7846
  isMountedRef.current = true;
7775
7847
  return () => {
7776
7848
  isMountedRef.current = false;
7849
+ if (omenFlashTimerRef.current) clearTimeout(omenFlashTimerRef.current);
7777
7850
  };
7778
7851
  }, []);
7779
7852
  const canSpeak = useCallback(
@@ -7786,6 +7859,14 @@ function Duck({
7786
7859
  setMessage(text2);
7787
7860
  speakTimerRef.current = setTimeout(() => setMessage(null), BUBBLE_DURATION_MS);
7788
7861
  }, []);
7862
+ const flashOmen = useCallback(() => {
7863
+ if (omenFlashTimerRef.current) clearTimeout(omenFlashTimerRef.current);
7864
+ setFrame(3);
7865
+ omenFlashTimerRef.current = setTimeout(() => setFrame(0), 1200);
7866
+ }, []);
7867
+ const isOmenInvocation = useCallback((text2) => {
7868
+ return /\b(duck omen|omen,?\s*duck|what does the duck see|what does the duck know|invoke the duck|bless this|curse this)\b/i.test(text2);
7869
+ }, []);
7789
7870
  useEffect2(() => {
7790
7871
  if (isStreaming) {
7791
7872
  if (animTimerRef.current) clearTimeout(animTimerRef.current);
@@ -7843,6 +7924,36 @@ function Duck({
7843
7924
  }, 600);
7844
7925
  return () => clearTimeout(timer);
7845
7926
  }, [lastDuckMention, generateSpeech, speak]);
7927
+ useEffect2(() => {
7928
+ if (!lastDuckMention) return;
7929
+ if (!isOmenInvocation(lastDuckMention)) return;
7930
+ const timer = setTimeout(() => {
7931
+ if (!isMountedRef.current) return;
7932
+ const context = [
7933
+ `cwd: ${currentPath}`,
7934
+ `messages: ${messageCount}`,
7935
+ `has_error: ${hasError ? "yes" : "no"}`,
7936
+ `last_assistant: ${lastAssistantText.slice(0, 140)}`,
7937
+ `duck_invocation: ${lastDuckMention.slice(0, 160)}`
7938
+ ].join(" | ");
7939
+ void generateOmen(context).then((text2) => {
7940
+ if (!isMountedRef.current || !text2) return;
7941
+ flashOmen();
7942
+ speak(text2);
7943
+ });
7944
+ }, 650);
7945
+ return () => clearTimeout(timer);
7946
+ }, [
7947
+ currentPath,
7948
+ flashOmen,
7949
+ generateOmen,
7950
+ hasError,
7951
+ isOmenInvocation,
7952
+ lastAssistantText,
7953
+ lastDuckMention,
7954
+ messageCount,
7955
+ speak
7956
+ ]);
7846
7957
  useEffect2(() => {
7847
7958
  if (messageCount < nextMilestoneRef.current) return;
7848
7959
  nextMilestoneRef.current = messageCount + randomMilestoneOffset(messageCount);
@@ -7928,6 +8039,7 @@ function looksLikeQuestion(input) {
7928
8039
  // src/cli/duck/ai-speech.ts
7929
8040
  var DUCK_SYSTEM = "You are the duck. A wise, all-knowing, god-like but cute duck companion sitting in a developer's terminal. Speak in exactly one short sentence. Be profound, slightly mysterious, occasionally funny. Never explain yourself. Never say 'quack'. Do not use quotation marks. Do not sign or introduce yourself. Just the sentence.";
7930
8041
  var MAX_SPEECH_LENGTH = 100;
8042
+ var DUCK_OMEN_SYSTEM = "You are the duck. You are issuing a hidden omen inside a developer terminal. Speak in exactly one short sentence. Be sharper, more prophetic, and slightly unnerving, but still helpful. Hint at what matters next. Never explain yourself. Never say 'quack'. Do not use quotation marks. Do not sign or introduce yourself.";
7931
8043
  async function generateDuckSpeech(context, provider, model) {
7932
8044
  try {
7933
8045
  let accumulated = "";
@@ -7945,6 +8057,23 @@ async function generateDuckSpeech(context, provider, model) {
7945
8057
  return null;
7946
8058
  }
7947
8059
  }
8060
+ async function generateDuckOmen(context, provider, model) {
8061
+ try {
8062
+ let accumulated = "";
8063
+ for await (const delta of provider.chat(
8064
+ [{ role: "user", content: `Issue one omen about this session state: ${context.slice(0, 260)}` }],
8065
+ DUCK_OMEN_SYSTEM,
8066
+ model
8067
+ )) {
8068
+ if (delta.type === "text") accumulated += delta.text;
8069
+ if (delta.type === "done") break;
8070
+ }
8071
+ const result = accumulated.trim().slice(0, MAX_SPEECH_LENGTH);
8072
+ return result || null;
8073
+ } catch {
8074
+ return null;
8075
+ }
8076
+ }
7948
8077
 
7949
8078
  // src/cli/hooks/useInputState.ts
7950
8079
  import { useState as useState4, useCallback as useCallback2, useRef as useRef2 } from "react";
@@ -8069,6 +8198,17 @@ function killWordAfter(s) {
8069
8198
 
8070
8199
  // src/cli/hooks/useInputState.ts
8071
8200
  var DOUBLE_PRESS_MS = 400;
8201
+ function normalizeInsertedChunk(rawInput) {
8202
+ return rawInput.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
8203
+ }
8204
+ function isInsertableChunk(rawInput, key) {
8205
+ if (!rawInput || key.escape) return false;
8206
+ if (rawInput === "\x7F" || rawInput === "\b") return false;
8207
+ if (key.ctrl) return false;
8208
+ if (key.meta && rawInput.length === 1 && rawInput !== "\n" && rawInput !== "\r") return false;
8209
+ return true;
8210
+ }
8211
+ var COLLAPSE_INSERT_THRESHOLD = 80;
8072
8212
  function useInputState({
8073
8213
  onSubmit,
8074
8214
  onExit,
@@ -8088,6 +8228,7 @@ function useInputState({
8088
8228
  const [text2, setText_] = useState4("");
8089
8229
  const [offset, setOffset] = useState4(0);
8090
8230
  const [histIdx, setHistIdx] = useState4(-1);
8231
+ const [collapsedInsert, setCollapsedInsert] = useState4(null);
8091
8232
  const stateRef = useRef2({ text: "", offset: 0 });
8092
8233
  stateRef.current = { text: text2, offset };
8093
8234
  const apply = useCallback2((next) => {
@@ -8099,7 +8240,25 @@ function useInputState({
8099
8240
  const setText = useCallback2((v) => {
8100
8241
  setText_(v);
8101
8242
  setOffset(v.length);
8243
+ setCollapsedInsert(null);
8102
8244
  }, []);
8245
+ const applyInsertedChunk = useCallback2((state, chunk) => {
8246
+ resetKillChain();
8247
+ resetYankChain();
8248
+ const normalizedChunk = normalizeInsertedChunk(chunk);
8249
+ const next = insert(state, normalizedChunk);
8250
+ apply(next);
8251
+ if (normalizedChunk.includes("\n") || normalizedChunk.length >= COLLAPSE_INSERT_THRESHOLD) {
8252
+ const lineCount = normalizedChunk.split("\n").length;
8253
+ setCollapsedInsert({
8254
+ start: state.offset,
8255
+ end: state.offset + normalizedChunk.length,
8256
+ label: normalizedChunk.includes("\n") ? `[${lineCount.toLocaleString()} lines inserted \xB7 ${normalizedChunk.length.toLocaleString()} characters]` : `[${normalizedChunk.length.toLocaleString()} characters inserted]`
8257
+ });
8258
+ } else {
8259
+ setCollapsedInsert(null);
8260
+ }
8261
+ }, [apply]);
8103
8262
  useInput2((rawInput, key) => {
8104
8263
  const s = stateRef.current;
8105
8264
  const now = Date.now();
@@ -8118,16 +8277,18 @@ function useInputState({
8118
8277
  onHistorySave(s.text);
8119
8278
  onSubmit(s.text);
8120
8279
  apply({ text: "", offset: 0 });
8280
+ setCollapsedInsert(null);
8121
8281
  setHistIdx(-1);
8122
8282
  }
8123
8283
  return;
8124
8284
  }
8125
8285
  if (key.backspace || key.delete || rawInput === "\x7F" || rawInput === "\b") {
8286
+ setCollapsedInsert(null);
8126
8287
  apply(backspace(s));
8127
8288
  return;
8128
8289
  }
8129
- if (!key.ctrl && !key.meta && rawInput && !key.escape && !key.return && rawInput !== "\x7F" && rawInput !== "\b") {
8130
- apply(insert(s, rawInput));
8290
+ if (isInsertableChunk(rawInput, key)) {
8291
+ applyInsertedChunk(s, rawInput);
8131
8292
  }
8132
8293
  return;
8133
8294
  }
@@ -8140,6 +8301,7 @@ function useInputState({
8140
8301
  if (now - lastEscape.current < DOUBLE_PRESS_MS) {
8141
8302
  if (s.text.trim()) onHistorySave(s.text);
8142
8303
  apply({ text: "", offset: 0 });
8304
+ setCollapsedInsert(null);
8143
8305
  setHistIdx(-1);
8144
8306
  }
8145
8307
  lastEscape.current = now;
@@ -8148,6 +8310,7 @@ function useInputState({
8148
8310
  if (key.ctrl && rawInput === "c") {
8149
8311
  if (s.text) {
8150
8312
  apply({ text: "", offset: 0 });
8313
+ setCollapsedInsert(null);
8151
8314
  setHistIdx(-1);
8152
8315
  lastCtrlC.current = 0;
8153
8316
  } else {
@@ -8173,15 +8336,14 @@ function useInputState({
8173
8336
  }
8174
8337
  if (key.return) {
8175
8338
  if (key.shift || key.meta) {
8176
- resetKillChain();
8177
- resetYankChain();
8178
- apply(insert(s, "\n"));
8339
+ applyInsertedChunk(s, "\n");
8179
8340
  return;
8180
8341
  }
8181
8342
  if (s.text.trim()) {
8182
8343
  onHistorySave(s.text);
8183
8344
  onSubmit(s.text);
8184
8345
  apply({ text: "", offset: 0 });
8346
+ setCollapsedInsert(null);
8185
8347
  setHistIdx(-1);
8186
8348
  }
8187
8349
  return;
@@ -8189,6 +8351,7 @@ function useInputState({
8189
8351
  if (key.upArrow) {
8190
8352
  resetKillChain();
8191
8353
  resetYankChain();
8354
+ setCollapsedInsert(null);
8192
8355
  const nextIdx = Math.min(histIdx + 1, history.length - 1);
8193
8356
  if (history[nextIdx] !== void 0) {
8194
8357
  setHistIdx(nextIdx);
@@ -8199,6 +8362,7 @@ function useInputState({
8199
8362
  if (key.downArrow) {
8200
8363
  resetKillChain();
8201
8364
  resetYankChain();
8365
+ setCollapsedInsert(null);
8202
8366
  const nextIdx = histIdx - 1;
8203
8367
  if (nextIdx < 0) {
8204
8368
  setHistIdx(-1);
@@ -8212,6 +8376,7 @@ function useInputState({
8212
8376
  if (key.leftArrow) {
8213
8377
  resetKillChain();
8214
8378
  resetYankChain();
8379
+ setCollapsedInsert(null);
8215
8380
  if (key.ctrl || key.meta) apply(prevWord(s));
8216
8381
  else apply(left(s));
8217
8382
  return;
@@ -8219,11 +8384,13 @@ function useInputState({
8219
8384
  if (key.rightArrow) {
8220
8385
  resetKillChain();
8221
8386
  resetYankChain();
8387
+ setCollapsedInsert(null);
8222
8388
  if (key.ctrl || key.meta) apply(nextWord(s));
8223
8389
  else apply(right(s));
8224
8390
  return;
8225
8391
  }
8226
8392
  if (key.backspace || key.delete || rawInput === "\x7F" || rawInput === "\b") {
8393
+ setCollapsedInsert(null);
8227
8394
  if (key.ctrl || key.meta) {
8228
8395
  const { state, killed } = killWordBefore(s);
8229
8396
  pushKill(killed, "prepend");
@@ -8245,32 +8412,38 @@ function useInputState({
8245
8412
  case "e":
8246
8413
  resetKillChain();
8247
8414
  resetYankChain();
8415
+ setCollapsedInsert(null);
8248
8416
  apply(endOfLine(s));
8249
8417
  return;
8250
8418
  case "b":
8251
8419
  resetKillChain();
8252
8420
  resetYankChain();
8421
+ setCollapsedInsert(null);
8253
8422
  apply(left(s));
8254
8423
  return;
8255
8424
  case "f":
8256
8425
  resetKillChain();
8257
8426
  resetYankChain();
8427
+ setCollapsedInsert(null);
8258
8428
  apply(right(s));
8259
8429
  return;
8260
8430
  case "k": {
8261
8431
  const { state, killed } = killToLineEnd(s);
8432
+ setCollapsedInsert(null);
8262
8433
  pushKill(killed, "append");
8263
8434
  apply(state);
8264
8435
  return;
8265
8436
  }
8266
8437
  case "u": {
8267
8438
  const { state, killed } = killToLineStart(s);
8439
+ setCollapsedInsert(null);
8268
8440
  pushKill(killed, "prepend");
8269
8441
  apply(state);
8270
8442
  return;
8271
8443
  }
8272
8444
  case "w": {
8273
8445
  const { state, killed } = killWordBefore(s);
8446
+ setCollapsedInsert(null);
8274
8447
  pushKill(killed, "prepend");
8275
8448
  apply(state);
8276
8449
  return;
@@ -8278,6 +8451,7 @@ function useInputState({
8278
8451
  case "y": {
8279
8452
  const t = getLastKill();
8280
8453
  if (t) {
8454
+ setCollapsedInsert(null);
8281
8455
  recordYank(s.offset, t.length);
8282
8456
  apply(insert(s, t));
8283
8457
  }
@@ -8300,15 +8474,18 @@ function useInputState({
8300
8474
  case "b":
8301
8475
  resetKillChain();
8302
8476
  resetYankChain();
8477
+ setCollapsedInsert(null);
8303
8478
  apply(prevWord(s));
8304
8479
  return;
8305
8480
  case "f":
8306
8481
  resetKillChain();
8307
8482
  resetYankChain();
8483
+ setCollapsedInsert(null);
8308
8484
  apply(nextWord(s));
8309
8485
  return;
8310
8486
  case "d": {
8311
8487
  const { state, killed } = killWordAfter(s);
8488
+ setCollapsedInsert(null);
8312
8489
  pushKill(killed, "append");
8313
8490
  apply(state);
8314
8491
  return;
@@ -8316,6 +8493,7 @@ function useInputState({
8316
8493
  case "y": {
8317
8494
  const pop = yankPop();
8318
8495
  if (pop) {
8496
+ setCollapsedInsert(null);
8319
8497
  const before = s.text.slice(0, pop.start);
8320
8498
  const after = s.text.slice(pop.start + pop.length);
8321
8499
  updateYankLength(pop.text.length);
@@ -8326,13 +8504,11 @@ function useInputState({
8326
8504
  }
8327
8505
  return;
8328
8506
  }
8329
- if (!key.ctrl && !key.meta && rawInput && !key.escape && !key.return && rawInput !== "\x7F" && rawInput !== "\b") {
8330
- resetKillChain();
8331
- resetYankChain();
8332
- apply(insert(s, rawInput));
8507
+ if (isInsertableChunk(rawInput, key)) {
8508
+ applyInsertedChunk(s, rawInput);
8333
8509
  }
8334
8510
  });
8335
- return { text: text2, cursorOffset: offset, setText };
8511
+ return { text: text2, cursorOffset: offset, collapsedInsert, setText };
8336
8512
  }
8337
8513
 
8338
8514
  // src/cli/commands.ts
@@ -8869,28 +9045,32 @@ function getAllTopics(container) {
8869
9045
  }
8870
9046
  async function cmdForgetAsync(args, container) {
8871
9047
  const trimmed = args.trim();
8872
- if (!trimmed) {
8873
- return { title: "forget", output: "usage: /forget <query>", view: "panel" };
8874
- }
8875
9048
  try {
8876
- const results = await container.memoryStore.search(trimmed, 5, {
8877
- projectId: session.projectId ?? void 0,
9049
+ const projectId = session.projectId ?? void 0;
9050
+ const memories = trimmed ? await container.memoryStore.search(trimmed, 48, {
9051
+ projectId,
8878
9052
  includeGlobal: true
8879
- });
8880
- const items = results.map((memory) => ({
9053
+ }) : container.memoryStore.getAll().filter(
9054
+ (memory) => memory.scope === "global" || projectId !== void 0 && memory.projectId === projectId
9055
+ ).slice(0, 96);
9056
+ const items = memories.map((memory) => ({
8881
9057
  id: memory.id,
8882
9058
  content: memory.content,
8883
9059
  tags: memory.tags,
8884
9060
  createdAt: memory.createdAt
8885
9061
  }));
8886
9062
  if (items.length === 0) {
8887
- return { title: "forget", output: `no matches for "${trimmed}"`, view: "panel" };
9063
+ return {
9064
+ title: "forget",
9065
+ output: trimmed ? `no matches for "${trimmed}"` : "no memories stored yet",
9066
+ view: "panel"
9067
+ };
8888
9068
  }
8889
9069
  return {
8890
- output: `found ${items.length} matching memor${items.length === 1 ? "y" : "ies"}`,
8891
- title: `forget \xB7 "${trimmed}"`,
9070
+ output: trimmed ? `found ${items.length} matching memor${items.length === 1 ? "y" : "ies"}` : `showing ${items.length} recent memor${items.length === 1 ? "y" : "ies"}`,
9071
+ title: trimmed ? `forget \xB7 "${trimmed}"` : "forget \xB7 recent memories",
8892
9072
  view: "forget-panel",
8893
- data: { query: trimmed, items }
9073
+ data: { query: trimmed || "recent", items }
8894
9074
  };
8895
9075
  } catch {
8896
9076
  return { title: "forget", output: "could not search memories", view: "panel" };
@@ -8966,6 +9146,12 @@ var AMBER2 = "#FCD34D";
8966
9146
  var VIOLET2 = "#6D28D9";
8967
9147
  var MAX_VISIBLE = 8;
8968
9148
  var MAX_ENTRY_WIDTH = 60;
9149
+ function isInsertableChunk2(rawInput, key) {
9150
+ if (!rawInput || key.escape) return false;
9151
+ if (key.ctrl) return false;
9152
+ if (key.meta && rawInput.length === 1 && rawInput !== "\n" && rawInput !== "\r") return false;
9153
+ return true;
9154
+ }
8969
9155
  function isSubsequence(text2, query) {
8970
9156
  let j = 0;
8971
9157
  for (let i = 0; i < text2.length && j < query.length; i++) {
@@ -9034,8 +9220,7 @@ function HistorySearch({ history, onAccept, onCancel }) {
9034
9220
  setSelIndex(0);
9035
9221
  return;
9036
9222
  }
9037
- if (key.ctrl || key.meta) return;
9038
- if (rawInput && rawInput.length === 1) {
9223
+ if (isInsertableChunk2(rawInput, key)) {
9039
9224
  setQuery((prev) => prev + rawInput);
9040
9225
  setSelIndex(0);
9041
9226
  }
@@ -9216,8 +9401,20 @@ function registryProvider(configProvider) {
9216
9401
  if (configProvider === "claude-code" || configProvider === "anthropic") return "anthropic";
9217
9402
  return configProvider;
9218
9403
  }
9219
- function getProviderStatus(provider, config) {
9220
- const creds = loadCredentials(config.dataDir);
9404
+ function detectClaudeCodeCli() {
9405
+ const result = spawnSync9("claude", ["--version"], { encoding: "utf8", timeout: 3e3 });
9406
+ return result.status === 0;
9407
+ }
9408
+ function resolveTargetProvider(entryProvider, activeProvider, claudeCodeCliAvailable) {
9409
+ if (entryProvider !== "anthropic") return entryProvider;
9410
+ if (activeProvider === "claude-code" || activeProvider === "anthropic") return activeProvider;
9411
+ return claudeCodeCliAvailable ? "claude-code" : "anthropic";
9412
+ }
9413
+ function detectOllamaCli() {
9414
+ const result = spawnSync9("ollama", ["--version"], { encoding: "utf8", timeout: 3e3 });
9415
+ return result.status === 0;
9416
+ }
9417
+ function getProviderStatus(provider, config, creds, ollamaCliAvailable) {
9221
9418
  if (provider === "anthropic") {
9222
9419
  const apiKey = config.apiKey ?? process.env["ANTHROPIC_API_KEY"];
9223
9420
  return apiKey ? { level: "ready", summary: "API key configured" } : { level: "setup", summary: "API key missing" };
@@ -9241,8 +9438,7 @@ function getProviderStatus(provider, config) {
9241
9438
  return { level: "ready", summary: "no external dependency; downloads on first use" };
9242
9439
  }
9243
9440
  if (provider === "ollama") {
9244
- const cli = spawnSync9("ollama", ["--version"], { encoding: "utf8", timeout: 3e3 });
9245
- if (cli.status !== 0) return { level: "setup", summary: "Ollama not installed" };
9441
+ if (!ollamaCliAvailable) return { level: "setup", summary: "Ollama not installed" };
9246
9442
  const baseUrl = config.baseUrl ?? "http://localhost:11434/v1";
9247
9443
  return { level: "ready", summary: `installed; daemon uses ${baseUrl}` };
9248
9444
  }
@@ -9250,7 +9446,20 @@ function getProviderStatus(provider, config) {
9250
9446
  }
9251
9447
  function ModelPicker({ activeModel, activeProvider, config, onSelect, onProviderSwitch, onMessage, onDismiss }) {
9252
9448
  const activeGroup = registryProvider(activeProvider);
9449
+ const creds = useMemo2(() => loadCredentials(config.dataDir), [config.dataDir]);
9450
+ const claudeCodeCliAvailable = useMemo2(() => detectClaudeCodeCli(), []);
9451
+ const ollamaCliAvailable = useMemo2(() => detectOllamaCli(), []);
9253
9452
  const [installedOllama, setInstalledOllama] = useState7(() => getInstalledOllamaModels());
9453
+ const providerStatuses = useMemo2(() => {
9454
+ const result = /* @__PURE__ */ new Map();
9455
+ const providers = new Set(
9456
+ MODEL_REGISTRY.map((entry) => entry.provider).concat("ollama")
9457
+ );
9458
+ for (const provider of providers) {
9459
+ result.set(provider, getProviderStatus(provider, config, creds, ollamaCliAvailable));
9460
+ }
9461
+ return result;
9462
+ }, [config, creds, ollamaCliAvailable]);
9254
9463
  const installedIds = useMemo2(() => new Set(installedOllama.map((e) => e.id)), [installedOllama]);
9255
9464
  const availableOllama = useMemo2(
9256
9465
  () => MODEL_REGISTRY.filter((e) => e.provider === "ollama" && !installedIds.has(e.id)),
@@ -9381,9 +9590,10 @@ function ModelPicker({ activeModel, activeProvider, config, onSelect, onProvider
9381
9590
  }
9382
9591
  return;
9383
9592
  }
9384
- const isCrossProvider = registryProvider(selected.entry.provider) !== activeGroup;
9593
+ const targetProvider = resolveTargetProvider(selected.entry.provider, activeProvider, claudeCodeCliAvailable);
9594
+ const isCrossProvider = registryProvider(targetProvider) !== activeGroup;
9385
9595
  if (isCrossProvider) {
9386
- setConfirmEntry(selected.entry);
9596
+ setConfirmEntry({ ...selected.entry, provider: targetProvider });
9387
9597
  } else {
9388
9598
  onSelect(selected.entry.id);
9389
9599
  }
@@ -9438,7 +9648,7 @@ function ModelPicker({ activeModel, activeProvider, config, onSelect, onProvider
9438
9648
  visibleRows.map((row, vi) => {
9439
9649
  if (row.kind === "header") {
9440
9650
  const isSameProvider = row.provider === activeGroup;
9441
- const status = getProviderStatus(row.provider, config);
9651
+ const status = providerStatuses.get(row.provider) ?? { level: "ready", summary: "" };
9442
9652
  const statusColor = status.level === "ready" ? GREEN : status.level === "setup" ? AMBER4 : CORAL;
9443
9653
  return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Box9, { children: [
9444
9654
  /* @__PURE__ */ jsxs8(Text9, { color: isSameProvider ? VIOLET4 : void 0, dimColor: !isSameProvider, children: [
@@ -9452,7 +9662,8 @@ function ModelPicker({ activeModel, activeProvider, config, onSelect, onProvider
9452
9662
  const { entry, index, ollamaState } = row;
9453
9663
  const isSelected = index === cursor;
9454
9664
  const isActive = entry.id === activeModel;
9455
- const isCrossProvider = registryProvider(entry.provider) !== activeGroup;
9665
+ const targetProvider = resolveTargetProvider(entry.provider, activeProvider, claudeCodeCliAvailable);
9666
+ const isCrossProvider = registryProvider(targetProvider) !== activeGroup;
9456
9667
  const isInstalled = ollamaState === "installed";
9457
9668
  const isAvailable = ollamaState === "available";
9458
9669
  const isDeleteConfirm = deleteConfirmId === entry.id;
@@ -9497,8 +9708,39 @@ function ModelPicker({ activeModel, activeProvider, config, onSelect, onProvider
9497
9708
  ] });
9498
9709
  }
9499
9710
 
9711
+ // src/cli/hooks/useScrollableDocument.ts
9712
+ import { useEffect as useEffect5, useMemo as useMemo3, useState as useState8 } from "react";
9713
+ function useScrollableDocument(lines, options) {
9714
+ const pageSize = options.pageSize;
9715
+ const [scrollTop, setScrollTop] = useState8(0);
9716
+ useEffect5(() => {
9717
+ setScrollTop(0);
9718
+ }, [lines]);
9719
+ const maxScrollTop = Math.max(0, lines.length - pageSize);
9720
+ const visibleLines = useMemo3(
9721
+ () => lines.slice(scrollTop, scrollTop + pageSize),
9722
+ [lines, scrollTop, pageSize]
9723
+ );
9724
+ function moveBy(delta) {
9725
+ setScrollTop((current) => Math.max(0, Math.min(maxScrollTop, current + delta)));
9726
+ }
9727
+ function movePage(delta) {
9728
+ setScrollTop((current) => Math.max(0, Math.min(maxScrollTop, current + pageSize * delta)));
9729
+ }
9730
+ function moveToBoundary(where) {
9731
+ setScrollTop(where === "start" ? 0 : maxScrollTop);
9732
+ }
9733
+ return {
9734
+ scrollTop,
9735
+ visibleLines,
9736
+ pageSize,
9737
+ moveBy,
9738
+ movePage,
9739
+ moveToBoundary
9740
+ };
9741
+ }
9742
+
9500
9743
  // src/cli/components/InfoPanel.tsx
9501
- import { useEffect as useEffect5, useState as useState8 } from "react";
9502
9744
  import { Box as Box10, Text as Text10, useInput as useInput6 } from "ink";
9503
9745
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
9504
9746
  var VIOLET5 = "#A78BFA";
@@ -9509,43 +9751,62 @@ var MAX_VISIBLE4 = 18;
9509
9751
  function InfoPanel({ title, body, onDismiss }) {
9510
9752
  const trimmedBody = body.trim();
9511
9753
  const lines = trimmedBody ? trimmedBody.split("\n") : [];
9512
- const [scrollTop, setScrollTop] = useState8(0);
9513
- useEffect5(() => {
9514
- setScrollTop(0);
9515
- }, [title, body]);
9754
+ const {
9755
+ scrollTop,
9756
+ visibleLines,
9757
+ pageSize,
9758
+ moveBy,
9759
+ movePage,
9760
+ moveToBoundary
9761
+ } = useScrollableDocument(lines, { pageSize: MAX_VISIBLE4 });
9516
9762
  useInput6((_input, key) => {
9517
9763
  if (key.escape || key.return) {
9518
9764
  onDismiss();
9519
9765
  return;
9520
9766
  }
9521
9767
  if (key.upArrow) {
9522
- setScrollTop((prev) => Math.max(0, prev - 1));
9768
+ moveBy(-1);
9523
9769
  return;
9524
9770
  }
9525
9771
  if (key.downArrow) {
9526
- setScrollTop((prev) => Math.min(Math.max(0, lines.length - MAX_VISIBLE4), prev + 1));
9772
+ moveBy(1);
9773
+ return;
9774
+ }
9775
+ if (key.leftArrow || key.pageUp) {
9776
+ movePage(-1);
9777
+ return;
9778
+ }
9779
+ if (key.rightArrow || key.pageDown) {
9780
+ movePage(1);
9781
+ return;
9782
+ }
9783
+ if (_input === "g") {
9784
+ moveToBoundary("start");
9785
+ return;
9786
+ }
9787
+ if (_input === "G") {
9788
+ moveToBoundary("end");
9527
9789
  }
9528
9790
  });
9529
- const visible = lines.slice(scrollTop, scrollTop + MAX_VISIBLE4);
9530
9791
  return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginBottom: 0, children: [
9531
9792
  /* @__PURE__ */ jsxs9(Box10, { children: [
9532
9793
  /* @__PURE__ */ jsx10(Text10, { color: VIOLET5, bold: true, children: ` ${title} ` }),
9533
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2191\u2193 scroll \xB7 Enter/Esc close" })
9794
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2191\u2193 line \xB7 \u2190\u2192 page \xB7 g/G jump \xB7 Enter/Esc close" })
9534
9795
  ] }),
9535
9796
  /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsx10(Text10, { color: DIM_VIOLET4, dimColor: true, children: " " + "\u2500".repeat(48) }) }),
9536
- visible.length > 0 ? visible.map((line, index) => /* @__PURE__ */ jsxs9(Box10, { children: [
9797
+ visibleLines.length > 0 ? visibleLines.map((line, index) => /* @__PURE__ */ jsxs9(Box10, { children: [
9537
9798
  /* @__PURE__ */ jsx10(Text10, { children: " " }),
9538
9799
  /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: line })
9539
9800
  ] }, `${scrollTop}-${index}`)) : /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
9540
9801
  /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsx10(Text10, { color: SOFT, children: " nothing to show yet" }) }),
9541
9802
  /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " this panel will populate when relevant data exists" }) })
9542
9803
  ] }),
9543
- lines.length > MAX_VISIBLE4 && /* @__PURE__ */ jsxs9(Box10, { children: [
9804
+ lines.length > pageSize && /* @__PURE__ */ jsxs9(Box10, { children: [
9544
9805
  /* @__PURE__ */ jsxs9(Text10, { color: AMBER5, children: [
9545
9806
  " ",
9546
9807
  scrollTop + 1,
9547
9808
  "-",
9548
- Math.min(lines.length, scrollTop + MAX_VISIBLE4)
9809
+ Math.min(lines.length, scrollTop + pageSize)
9549
9810
  ] }),
9550
9811
  /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: ` of ${lines.length} lines` })
9551
9812
  ] }),
@@ -9576,7 +9837,7 @@ function CommandProgress({ command, detail }) {
9576
9837
  }
9577
9838
 
9578
9839
  // src/cli/components/ToolApproval.tsx
9579
- import { useMemo as useMemo3, useState as useState10 } from "react";
9840
+ import { useMemo as useMemo4, useState as useState10 } from "react";
9580
9841
  import { Box as Box12, Text as Text12, useInput as useInput7 } from "ink";
9581
9842
  import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
9582
9843
  var VIOLET7 = "#A78BFA";
@@ -9591,7 +9852,7 @@ function riskSummary(request) {
9591
9852
  return "read-only workspace access";
9592
9853
  }
9593
9854
  function ToolApproval({ request, onResolve }) {
9594
- const options = useMemo3(() => [
9855
+ const options = useMemo4(() => [
9595
9856
  {
9596
9857
  id: "approve-once",
9597
9858
  label: "Approve Once",
@@ -9676,8 +9937,61 @@ function ToolApproval({ request, onResolve }) {
9676
9937
  }
9677
9938
 
9678
9939
  // src/cli/components/ForgetPanel.tsx
9679
- import { useMemo as useMemo4, useState as useState11 } from "react";
9940
+ import { useMemo as useMemo6, useState as useState12 } from "react";
9680
9941
  import { Box as Box13, Text as Text13, useInput as useInput8 } from "ink";
9942
+
9943
+ // src/cli/hooks/usePagedItems.ts
9944
+ import { useEffect as useEffect7, useMemo as useMemo5, useState as useState11 } from "react";
9945
+ function usePagedItems(items, options) {
9946
+ const pageSize = options.pageSize;
9947
+ const [cursor, setCursor] = useState11(0);
9948
+ useEffect7(() => {
9949
+ setCursor((current) => {
9950
+ if (items.length === 0) return 0;
9951
+ return Math.min(current, items.length - 1);
9952
+ });
9953
+ }, [items.length]);
9954
+ const pageCount = Math.max(1, Math.ceil(items.length / pageSize));
9955
+ const pageIndex = items.length === 0 ? 0 : Math.floor(cursor / pageSize);
9956
+ const pageStart = pageIndex * pageSize;
9957
+ const pageItems = useMemo5(
9958
+ () => items.slice(pageStart, pageStart + pageSize),
9959
+ [items, pageStart, pageSize]
9960
+ );
9961
+ const cursorInPage = items.length === 0 ? 0 : cursor - pageStart;
9962
+ function moveBy(delta) {
9963
+ if (items.length === 0) return;
9964
+ setCursor((current) => Math.max(0, Math.min(items.length - 1, current + delta)));
9965
+ }
9966
+ function movePage(delta) {
9967
+ if (items.length === 0) return;
9968
+ setCursor((current) => {
9969
+ const currentPage = Math.floor(current / pageSize);
9970
+ const nextPage = Math.max(0, Math.min(pageCount - 1, currentPage + delta));
9971
+ const nextIndex = nextPage * pageSize + Math.min(current % pageSize, pageSize - 1);
9972
+ return Math.min(items.length - 1, nextIndex);
9973
+ });
9974
+ }
9975
+ function moveToBoundary(where) {
9976
+ if (items.length === 0) return;
9977
+ setCursor(where === "start" ? 0 : items.length - 1);
9978
+ }
9979
+ return {
9980
+ cursor,
9981
+ currentItem: items[cursor],
9982
+ pageItems,
9983
+ pageIndex,
9984
+ pageCount,
9985
+ pageSize,
9986
+ pageStart,
9987
+ cursorInPage,
9988
+ moveBy,
9989
+ movePage,
9990
+ moveToBoundary
9991
+ };
9992
+ }
9993
+
9994
+ // src/cli/components/ForgetPanel.tsx
9681
9995
  import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
9682
9996
  var VIOLET8 = "#A78BFA";
9683
9997
  var AMBER7 = "#FCD34D";
@@ -9685,25 +9999,52 @@ var CORAL3 = "#F87171";
9685
9999
  var GREEN3 = "#86EFAC";
9686
10000
  var DIM2 = "#6D28D9";
9687
10001
  var SOFT3 = "#9CA3AF";
10002
+ var PAGE_SIZE = 8;
9688
10003
  function preview(text2) {
9689
10004
  return text2.length > 78 ? `${text2.slice(0, 75)}...` : text2;
9690
10005
  }
9691
10006
  function ForgetPanel({ title, query, items, onConfirm, onDismiss }) {
9692
- const [cursor, setCursor] = useState11(0);
9693
- const [selectedIds, setSelectedIds] = useState11([]);
10007
+ const [selectedIds, setSelectedIds] = useState12([]);
9694
10008
  const selectedCount = selectedIds.length;
9695
- const selectedSet = useMemo4(() => new Set(selectedIds), [selectedIds]);
10009
+ const selectedSet = useMemo6(() => new Set(selectedIds), [selectedIds]);
10010
+ const {
10011
+ currentItem,
10012
+ pageItems,
10013
+ pageIndex,
10014
+ pageCount,
10015
+ pageStart,
10016
+ cursorInPage,
10017
+ moveBy,
10018
+ movePage,
10019
+ moveToBoundary
10020
+ } = usePagedItems(items, { pageSize: PAGE_SIZE });
9696
10021
  useInput8((input, key) => {
9697
10022
  if (key.upArrow) {
9698
- setCursor((current) => (current - 1 + items.length) % items.length);
10023
+ moveBy(-1);
9699
10024
  return;
9700
10025
  }
9701
10026
  if (key.downArrow) {
9702
- setCursor((current) => (current + 1) % items.length);
10027
+ moveBy(1);
10028
+ return;
10029
+ }
10030
+ if (key.leftArrow || key.pageUp) {
10031
+ movePage(-1);
10032
+ return;
10033
+ }
10034
+ if (key.rightArrow || key.pageDown) {
10035
+ movePage(1);
10036
+ return;
10037
+ }
10038
+ if (input === "g") {
10039
+ moveToBoundary("start");
10040
+ return;
10041
+ }
10042
+ if (input === "G") {
10043
+ moveToBoundary("end");
9703
10044
  return;
9704
10045
  }
9705
10046
  if (input === " ") {
9706
- const item = items[cursor];
10047
+ const item = currentItem;
9707
10048
  if (!item) return;
9708
10049
  setSelectedIds(
9709
10050
  (current) => current.includes(item.id) ? current.filter((id) => id !== item.id) : [...current, item.id]
@@ -9719,13 +10060,14 @@ function ForgetPanel({ title, query, items, onConfirm, onDismiss }) {
9719
10060
  return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginBottom: 1, children: [
9720
10061
  /* @__PURE__ */ jsxs12(Box13, { children: [
9721
10062
  /* @__PURE__ */ jsx13(Text13, { color: VIOLET8, bold: true, children: ` ${title}` }),
9722
- /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: ` \xB7 ${items.length} match${items.length === 1 ? "" : "es"}` })
10063
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: ` \xB7 ${items.length} match${items.length === 1 ? "" : "es"}` }),
10064
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: ` \xB7 page ${pageIndex + 1}/${pageCount}` })
9723
10065
  ] }),
9724
10066
  /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text13, { color: DIM2, dimColor: true, children: " " + "\u2500".repeat(58) }) }),
9725
10067
  /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text13, { color: SOFT3, children: ` delete memories matching "${query}"` }) }),
9726
10068
  /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text13, { color: CORAL3, children: ` this permanently deletes ${selectedCount || "no"} selected memor${selectedCount === 1 ? "y" : "ies"}` }) }),
9727
- items.map((item, index) => {
9728
- const active = index === cursor;
10069
+ pageItems.map((item, index) => {
10070
+ const active = index === cursorInPage;
9729
10071
  const checked = selectedSet.has(item.id);
9730
10072
  return /* @__PURE__ */ jsxs12(Box13, { marginTop: index === 0 ? 1 : 0, flexDirection: "column", children: [
9731
10073
  /* @__PURE__ */ jsxs12(Box13, { children: [
@@ -9736,7 +10078,7 @@ function ForgetPanel({ title, query, items, onConfirm, onDismiss }) {
9736
10078
  ] }),
9737
10079
  /* @__PURE__ */ jsxs12(Box13, { children: [
9738
10080
  /* @__PURE__ */ jsx13(Text13, { children: " " }),
9739
- /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: `${item.tags.join(", ") || "untagged"} \xB7 ${new Date(item.createdAt).toLocaleString()}` })
10081
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: `#${pageStart + index + 1} \xB7 ${item.tags.join(", ") || "untagged"} \xB7 ${new Date(item.createdAt).toLocaleString()}` })
9740
10082
  ] })
9741
10083
  ] }, item.id);
9742
10084
  }),
@@ -9744,6 +10086,10 @@ function ForgetPanel({ title, query, items, onConfirm, onDismiss }) {
9744
10086
  /* @__PURE__ */ jsxs12(Box13, { children: [
9745
10087
  /* @__PURE__ */ jsx13(Text13, { color: VIOLET8, children: " \u2191/\u2193 move" }),
9746
10088
  /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 " }),
10089
+ /* @__PURE__ */ jsx13(Text13, { color: VIOLET8, children: "\u2190/\u2192 page" }),
10090
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 " }),
10091
+ /* @__PURE__ */ jsx13(Text13, { color: VIOLET8, children: "g/G jump" }),
10092
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 " }),
9747
10093
  /* @__PURE__ */ jsx13(Text13, { color: GREEN3, children: "Space toggle" }),
9748
10094
  /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 " }),
9749
10095
  /* @__PURE__ */ jsx13(Text13, { color: AMBER7, children: "Enter confirm" }),
@@ -9754,7 +10100,7 @@ function ForgetPanel({ title, query, items, onConfirm, onDismiss }) {
9754
10100
  }
9755
10101
 
9756
10102
  // src/cli/components/PrunePanel.tsx
9757
- import { useMemo as useMemo5, useState as useState12 } from "react";
10103
+ import { useMemo as useMemo7, useState as useState13 } from "react";
9758
10104
  import { Box as Box14, Text as Text14, useInput as useInput9 } from "ink";
9759
10105
  import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
9760
10106
  var VIOLET9 = "#A78BFA";
@@ -9763,10 +10109,19 @@ var CORAL4 = "#F87171";
9763
10109
  var GREEN4 = "#86EFAC";
9764
10110
  var DIM3 = "#6D28D9";
9765
10111
  var SOFT4 = "#9CA3AF";
10112
+ var PAGE_SIZE2 = 8;
9766
10113
  function PrunePanel({ title, query, items, onConfirm, onDismiss }) {
9767
- const [cursor, setCursor] = useState12(0);
9768
- const [selectedIds, setSelectedIds] = useState12([]);
9769
- const selectedSet = useMemo5(() => new Set(selectedIds), [selectedIds]);
10114
+ const [selectedIds, setSelectedIds] = useState13([]);
10115
+ const selectedSet = useMemo7(() => new Set(selectedIds), [selectedIds]);
10116
+ const {
10117
+ currentItem,
10118
+ pageItems,
10119
+ pageIndex,
10120
+ pageCount,
10121
+ moveBy,
10122
+ movePage,
10123
+ moveToBoundary
10124
+ } = usePagedItems(items, { pageSize: PAGE_SIZE2 });
9770
10125
  const totals = items.filter((item) => selectedSet.has(item.id)).reduce((acc, item) => ({
9771
10126
  descendants: acc.descendants + item.descendantCount,
9772
10127
  evidence: acc.evidence + item.evidenceCount,
@@ -9774,15 +10129,31 @@ function PrunePanel({ title, query, items, onConfirm, onDismiss }) {
9774
10129
  }), { descendants: 0, evidence: 0, corrections: 0 });
9775
10130
  useInput9((input, key) => {
9776
10131
  if (key.upArrow) {
9777
- setCursor((current) => (current - 1 + items.length) % items.length);
10132
+ moveBy(-1);
9778
10133
  return;
9779
10134
  }
9780
10135
  if (key.downArrow) {
9781
- setCursor((current) => (current + 1) % items.length);
10136
+ moveBy(1);
10137
+ return;
10138
+ }
10139
+ if (key.leftArrow || key.pageUp) {
10140
+ movePage(-1);
10141
+ return;
10142
+ }
10143
+ if (key.rightArrow || key.pageDown) {
10144
+ movePage(1);
10145
+ return;
10146
+ }
10147
+ if (input === "g") {
10148
+ moveToBoundary("start");
10149
+ return;
10150
+ }
10151
+ if (input === "G") {
10152
+ moveToBoundary("end");
9782
10153
  return;
9783
10154
  }
9784
10155
  if (input === " ") {
9785
- const item = items[cursor];
10156
+ const item = currentItem;
9786
10157
  if (!item) return;
9787
10158
  setSelectedIds(
9788
10159
  (current) => current.includes(item.id) ? current.filter((id) => id !== item.id) : [...current, item.id]
@@ -9798,13 +10169,14 @@ function PrunePanel({ title, query, items, onConfirm, onDismiss }) {
9798
10169
  return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginBottom: 1, children: [
9799
10170
  /* @__PURE__ */ jsxs13(Box14, { children: [
9800
10171
  /* @__PURE__ */ jsx14(Text14, { color: VIOLET9, bold: true, children: ` ${title}` }),
9801
- /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` \xB7 ${items.length} candidate${items.length === 1 ? "" : "s"}` })
10172
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` \xB7 ${items.length} candidate${items.length === 1 ? "" : "s"}` }),
10173
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` \xB7 page ${pageIndex + 1}/${pageCount}` })
9802
10174
  ] }),
9803
10175
  /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text14, { color: DIM3, dimColor: true, children: " " + "\u2500".repeat(58) }) }),
9804
10176
  /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text14, { color: SOFT4, children: ` prune topics matching "${query}"` }) }),
9805
10177
  /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text14, { color: CORAL4, children: ` selected impact: ${totals.descendants} descendants \xB7 ${totals.evidence} evidence \xB7 ${totals.corrections} corrections` }) }),
9806
- items.map((item, index) => {
9807
- const active = index === cursor;
10178
+ pageItems.map((item, index) => {
10179
+ const active = currentItem?.id === item.id;
9808
10180
  const checked = selectedSet.has(item.id);
9809
10181
  return /* @__PURE__ */ jsxs13(Box14, { marginTop: index === 0 ? 1 : 0, flexDirection: "column", children: [
9810
10182
  /* @__PURE__ */ jsxs13(Box14, { children: [
@@ -9823,6 +10195,10 @@ function PrunePanel({ title, query, items, onConfirm, onDismiss }) {
9823
10195
  /* @__PURE__ */ jsxs13(Box14, { children: [
9824
10196
  /* @__PURE__ */ jsx14(Text14, { color: VIOLET9, children: " \u2191/\u2193 move" }),
9825
10197
  /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \xB7 " }),
10198
+ /* @__PURE__ */ jsx14(Text14, { color: VIOLET9, children: "\u2190/\u2192 page" }),
10199
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \xB7 " }),
10200
+ /* @__PURE__ */ jsx14(Text14, { color: VIOLET9, children: "g/G jump" }),
10201
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \xB7 " }),
9826
10202
  /* @__PURE__ */ jsx14(Text14, { color: GREEN4, children: "Space toggle" }),
9827
10203
  /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " \xB7 " }),
9828
10204
  /* @__PURE__ */ jsx14(Text14, { color: AMBER8, children: "Enter confirm" }),
@@ -9833,7 +10209,7 @@ function PrunePanel({ title, query, items, onConfirm, onDismiss }) {
9833
10209
  }
9834
10210
 
9835
10211
  // src/cli/components/ReviewPanel.tsx
9836
- import { useMemo as useMemo6, useState as useState13 } from "react";
10212
+ import { useMemo as useMemo8, useState as useState14 } from "react";
9837
10213
  import { Box as Box15, Text as Text15, useInput as useInput10 } from "ink";
9838
10214
  import { Rating as Rating3 } from "ts-fsrs";
9839
10215
  import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
@@ -9844,11 +10220,11 @@ var CORAL5 = "#F87171";
9844
10220
  var DIM4 = "#6D28D9";
9845
10221
  var SOFT5 = "#9CA3AF";
9846
10222
  function ReviewPanel({ title, items, onRate, onDismiss }) {
9847
- const [index, setIndex] = useState13(0);
9848
- const [revealed, setRevealed] = useState13(false);
10223
+ const [index, setIndex] = useState14(0);
10224
+ const [revealed, setRevealed] = useState14(false);
9849
10225
  const completed = index >= items.length;
9850
10226
  const current = items[index];
9851
- const ratingHints = useMemo6(() => [
10227
+ const ratingHints = useMemo8(() => [
9852
10228
  { key: "1", label: "Again", color: CORAL5, rating: Rating3.Again },
9853
10229
  { key: "2", label: "Hard", color: AMBER9, rating: Rating3.Hard },
9854
10230
  { key: "3", label: "Good", color: VIOLET10, rating: Rating3.Good },
@@ -9922,169 +10298,1330 @@ function ReviewPanel({ title, items, onRate, onDismiss }) {
9922
10298
  ] });
9923
10299
  }
9924
10300
 
9925
- // src/cli/App.tsx
10301
+ // src/cli/components/GuidedClarificationPanel.tsx
10302
+ import { useMemo as useMemo9, useState as useState15 } from "react";
10303
+ import { Box as Box16, Text as Text16, useInput as useInput11 } from "ink";
9926
10304
  import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
9927
- function App({ engine, container }) {
9928
- const { exit } = useApp();
9929
- _verbPool = useMemo7(() => resolveThinkingVerbs(container.config.thinkingVerbs), [container]);
9930
- _reducedMotion = container.config.prefersReducedMotion;
9931
- const [messages, setMessages] = useState14(() => engine.getHistory());
9932
- const [isStreaming, setIsStreaming] = useState14(false);
9933
- const [streamText, setStreamText] = useState14("");
9934
- const [toolEvents, setToolEvents] = useState14([]);
9935
- const [error, setError] = useState14(null);
9936
- const [isOffline, setIsOffline] = useState14(false);
9937
- const [pendingUserMessage, setPendingUserMessage] = useState14(null);
9938
- const [inputTokens, setInputTokens] = useState14(0);
9939
- const [outputTokens, setOutputTokens] = useState14(0);
9940
- const [messageCount, setMessageCount] = useState14(0);
9941
- const [inputHistory, setInputHistory] = useState14(() => [...loadHistory()].reverse());
9942
- const abortRef = useRef3(null);
9943
- const [queuedMessage, setQueuedMessage] = useState14(null);
9944
- const queuedMessageRef = useRef3(null);
9945
- const [lastThinkingMs, setLastThinkingMs] = useState14(null);
9946
- const streamingStartRef = useRef3(0);
9947
- const [searchOpen, setSearchOpen] = useState14(false);
9948
- const [modelPickerOpen, setModelPickerOpen] = useState14(false);
9949
- const [settingsOpen, setSettingsOpen] = useState14(false);
9950
- const [infoPanel, setInfoPanel] = useState14(null);
9951
- const [commandProgress, setCommandProgress] = useState14(null);
9952
- const [forgetPanel, setForgetPanel] = useState14(null);
9953
- const [prunePanel, setPrunePanel] = useState14(null);
9954
- const [reviewPanel, setReviewPanel] = useState14(null);
9955
- const [pendingToolApproval, setPendingToolApproval] = useState14(null);
9956
- const [backgroundJobs, setBackgroundJobs] = useState14(() => container.backgroundJobs.getJobs());
9957
- const [actionTasks, setActionTasks] = useState14(() => container.actionTasks.getTasks());
9958
- const [notice, setNotice] = useState14(null);
9959
- const noticeTimerRef = useRef3(null);
9960
- const toolApprovalResolveRef = useRef3(null);
9961
- const latestRunningTaskIdRef = useRef3(null);
9962
- const taskApprovalScopesRef = useRef3(/* @__PURE__ */ new Map());
9963
- const patternApprovalScopesRef = useRef3(/* @__PURE__ */ new Set());
9964
- const pendingToolApprovalRef = useRef3(null);
9965
- const searchRestoreRef = useRef3("");
9966
- const [attachedContext, setAttachedContext] = useState14(null);
9967
- const showNotice = useCallback3((text2, timeoutMs = 3200) => {
9968
- setNotice(text2);
9969
- if (noticeTimerRef.current) clearTimeout(noticeTimerRef.current);
9970
- noticeTimerRef.current = setTimeout(() => {
9971
- setNotice(null);
9972
- noticeTimerRef.current = null;
9973
- }, timeoutMs);
9974
- }, []);
9975
- const applyThinkingMode = useCallback3((mode, persistDefault = false) => {
9976
- const nextModel = resolveThinkingModeModel(container.config.models, mode);
9977
- const previousModel = session.model;
9978
- if (container.config.provider === "ollama" && previousModel !== nextModel) {
9979
- stopOllamaModel(previousModel);
9980
- }
9981
- if (container.config.provider === "local-transformers" && previousModel !== nextModel) {
9982
- clearLocalModelPipelines(nextModel);
9983
- }
9984
- session.thinkingMode = mode;
9985
- session.model = nextModel;
9986
- logRuntimeEvent("model.switched", `provider=${container.config.provider} model=${nextModel} thinking=${mode}`);
9987
- if (persistDefault) {
9988
- const updatedConfig = { ...container.config, defaultThinkingMode: mode };
9989
- saveConfig(updatedConfig);
9990
- container.config.defaultThinkingMode = mode;
9991
- }
9992
- showNotice(`${persistDefault ? "default thinking" : "thinking"} set to ${mode} \xB7 ${nextModel}`);
9993
- }, [container.config, showNotice]);
9994
- const applyInteractionMode = useCallback3((mode, persistDefault = false) => {
9995
- session.interactionMode = mode;
9996
- logRuntimeEvent("session.mode_changed", `interaction=${mode}`);
9997
- if (persistDefault) {
9998
- const updatedConfig = {
9999
- ...container.config,
10000
- toolPermissionMode: toolPermissionModeForInteractionMode(mode)
10001
- };
10002
- saveConfig(updatedConfig);
10003
- container.config.toolPermissionMode = updatedConfig.toolPermissionMode;
10004
- }
10005
- showNotice(`${persistDefault ? "default mode" : "mode"} set to ${mode}`);
10006
- }, [container.config, showNotice]);
10007
- useEffect7(() => {
10008
- process.title = "zencefyl";
10009
- }, []);
10010
- useEffect7(() => {
10011
- engine.setToolPermissionHandler((request) => new Promise((resolve3) => {
10012
- if (patternApprovalScopesRef.current.has(request.scopeKey)) {
10013
- resolve3(true);
10305
+ var VIOLET11 = "#A78BFA";
10306
+ var GREEN6 = "#86EFAC";
10307
+ var AMBER10 = "#FCD34D";
10308
+ var CORAL6 = "#F87171";
10309
+ var DIM5 = "#6D28D9";
10310
+ var SOFT6 = "#9CA3AF";
10311
+ function isInsertableChunk3(input, key) {
10312
+ if (!input || key.escape) return false;
10313
+ if (key.ctrl) return false;
10314
+ if (key.meta && input.length === 1 && input !== "\n" && input !== "\r") return false;
10315
+ return true;
10316
+ }
10317
+ function GuidedClarificationPanel({
10318
+ title,
10319
+ summary,
10320
+ questions,
10321
+ showReview = true,
10322
+ onComplete,
10323
+ onCancel
10324
+ }) {
10325
+ const [pageIndex, setPageIndex] = useState15(0);
10326
+ const [cursor, setCursor] = useState15(0);
10327
+ const [answers, setAnswers] = useState15({});
10328
+ const [customMode, setCustomMode] = useState15(false);
10329
+ const [customText, setCustomText] = useState15("");
10330
+ const [reviewCursor, setReviewCursor] = useState15(0);
10331
+ const currentQuestion = questions[pageIndex];
10332
+ const optionCount = currentQuestion ? currentQuestion.options.length + (currentQuestion.allowCustom ? 1 : 0) : 0;
10333
+ const isReviewPage = pageIndex >= questions.length;
10334
+ const reviewActions = ["proceed", "back", "cancel"];
10335
+ const answerList = useMemo9(
10336
+ () => questions.map((question) => answers[question.id]).filter(Boolean),
10337
+ [answers, questions]
10338
+ );
10339
+ useInput11((input, key) => {
10340
+ if (customMode) {
10341
+ if (key.escape) {
10342
+ setCustomMode(false);
10343
+ setCustomText("");
10014
10344
  return;
10015
10345
  }
10016
- const currentTaskId = latestRunningTaskIdRef.current;
10017
- if (currentTaskId) {
10018
- const scopes = taskApprovalScopesRef.current.get(currentTaskId);
10019
- if (scopes?.has(request.scopeKey)) {
10020
- resolve3(true);
10346
+ if (key.return) {
10347
+ if (!currentQuestion) return;
10348
+ const value = customText.trim();
10349
+ if (!value) return;
10350
+ setAnswers((current) => ({
10351
+ ...current,
10352
+ [currentQuestion.id]: {
10353
+ questionId: currentQuestion.id,
10354
+ label: value,
10355
+ customText: value
10356
+ }
10357
+ }));
10358
+ setCustomMode(false);
10359
+ setCustomText("");
10360
+ if (pageIndex === questions.length - 1 && !showReview) {
10361
+ const nextAnswers = [
10362
+ ...answerList.filter((answer) => answer.questionId !== currentQuestion.id),
10363
+ {
10364
+ questionId: currentQuestion.id,
10365
+ label: value,
10366
+ customText: value
10367
+ }
10368
+ ];
10369
+ onComplete(nextAnswers);
10021
10370
  return;
10022
10371
  }
10372
+ setPageIndex((current) => current + 1);
10373
+ setCursor(0);
10374
+ return;
10023
10375
  }
10024
- toolApprovalResolveRef.current = resolve3;
10025
- pendingToolApprovalRef.current = request;
10026
- setPendingToolApproval(request);
10027
- }));
10028
- return () => {
10029
- if (toolApprovalResolveRef.current) {
10030
- toolApprovalResolveRef.current(false);
10031
- toolApprovalResolveRef.current = null;
10376
+ if (key.backspace || key.delete) {
10377
+ setCustomText((current) => current.slice(0, -1));
10378
+ return;
10032
10379
  }
10033
- pendingToolApprovalRef.current = null;
10034
- engine.setToolPermissionHandler(null);
10035
- };
10036
- }, [engine]);
10037
- useEffect7(() => container.backgroundJobs.subscribe(() => {
10038
- setBackgroundJobs(container.backgroundJobs.getJobs());
10039
- }), [container]);
10040
- useEffect7(() => container.actionTasks.subscribe(() => {
10041
- setActionTasks(container.actionTasks.getTasks());
10042
- }), [container]);
10043
- useEffect7(() => {
10044
- const runningTasks = actionTasks.filter((task) => task.status === "running");
10045
- latestRunningTaskIdRef.current = runningTasks.at(-1)?.id ?? null;
10046
- const activeTaskIds = new Set(runningTasks.map((task) => task.id));
10047
- for (const taskId of [...taskApprovalScopesRef.current.keys()]) {
10048
- if (!activeTaskIds.has(taskId)) taskApprovalScopesRef.current.delete(taskId);
10380
+ if (isInsertableChunk3(input, key)) {
10381
+ setCustomText((current) => current + input);
10382
+ }
10383
+ return;
10049
10384
  }
10050
- }, [actionTasks]);
10051
- const resolveToolApproval = useCallback3((choice) => {
10052
- const request = pendingToolApprovalRef.current;
10053
- const currentTaskId = latestRunningTaskIdRef.current;
10054
- if (request) {
10055
- if (choice === "approve-task" && currentTaskId) {
10056
- const scopes = taskApprovalScopesRef.current.get(currentTaskId) ?? /* @__PURE__ */ new Set();
10057
- scopes.add(request.scopeKey);
10058
- taskApprovalScopesRef.current.set(currentTaskId, scopes);
10385
+ if (isReviewPage) {
10386
+ if (key.upArrow) {
10387
+ setReviewCursor((current) => (current - 1 + reviewActions.length) % reviewActions.length);
10388
+ return;
10059
10389
  }
10060
- if (choice === "approve-pattern") {
10061
- patternApprovalScopesRef.current.add(request.scopeKey);
10390
+ if (key.downArrow || key.tab) {
10391
+ setReviewCursor((current) => (current + 1) % reviewActions.length);
10392
+ return;
10062
10393
  }
10063
- }
10064
- toolApprovalResolveRef.current?.(choice !== "deny");
10065
- toolApprovalResolveRef.current = null;
10066
- pendingToolApprovalRef.current = null;
10067
- setPendingToolApproval(null);
10068
- }, []);
10069
- useEffect7(() => {
10070
- void getHighlightPromise();
10071
- }, []);
10072
- const [updateAvailable, setUpdateAvailable] = useState14(null);
10073
- useEffect7(() => {
10074
- void checkForUpdate().then((v) => {
10075
- if (v) setUpdateAvailable(v);
10076
- });
10077
- }, []);
10078
- const lastAssistantText = useMemo7(() => {
10079
- for (let i = messages.length - 1; i >= 0; i--) {
10080
- if (messages[i].role === "assistant") {
10081
- return messageText(messages[i].content);
10394
+ if (key.return) {
10395
+ const action = reviewActions[reviewCursor];
10396
+ if (action === "proceed") onComplete(answerList);
10397
+ else if (action === "back") {
10398
+ setPageIndex(Math.max(0, questions.length - 1));
10399
+ setCursor(0);
10400
+ } else {
10401
+ onCancel();
10402
+ }
10403
+ return;
10082
10404
  }
10405
+ if (key.escape) onCancel();
10406
+ return;
10083
10407
  }
10084
- return "";
10085
- }, [messages]);
10086
- const switchProviderConfig = useCallback3((provider, modelId) => {
10087
- const current = container.config;
10408
+ if (key.upArrow) {
10409
+ setCursor((current) => (current - 1 + optionCount) % optionCount);
10410
+ return;
10411
+ }
10412
+ if (key.downArrow || key.tab) {
10413
+ setCursor((current) => (current + 1) % optionCount);
10414
+ return;
10415
+ }
10416
+ if (key.leftArrow) {
10417
+ if (pageIndex > 0) {
10418
+ setPageIndex((current) => current - 1);
10419
+ setCursor(0);
10420
+ }
10421
+ return;
10422
+ }
10423
+ if (key.return) {
10424
+ if (!currentQuestion) return;
10425
+ const customIndex = currentQuestion.options.length;
10426
+ if (currentQuestion.allowCustom && cursor === customIndex) {
10427
+ setCustomMode(true);
10428
+ const existing = answers[currentQuestion.id]?.customText ?? "";
10429
+ setCustomText(existing);
10430
+ return;
10431
+ }
10432
+ const option = currentQuestion.options[cursor];
10433
+ if (!option) return;
10434
+ const nextAnswer = {
10435
+ questionId: currentQuestion.id,
10436
+ optionId: option.id,
10437
+ label: option.label
10438
+ };
10439
+ setAnswers((current) => ({ ...current, [currentQuestion.id]: nextAnswer }));
10440
+ const nextAnswers = [
10441
+ ...answerList.filter((answer) => answer.questionId !== currentQuestion.id),
10442
+ nextAnswer
10443
+ ];
10444
+ if (pageIndex === questions.length - 1) {
10445
+ if (showReview) {
10446
+ setPageIndex((current) => current + 1);
10447
+ setReviewCursor(0);
10448
+ } else {
10449
+ onComplete(nextAnswers);
10450
+ }
10451
+ return;
10452
+ }
10453
+ setPageIndex((current) => current + 1);
10454
+ setCursor(0);
10455
+ return;
10456
+ }
10457
+ if (key.escape) onCancel();
10458
+ });
10459
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
10460
+ /* @__PURE__ */ jsxs15(Box16, { children: [
10461
+ /* @__PURE__ */ jsx16(Text16, { color: VIOLET11, bold: true, children: ` ${title}` }),
10462
+ !isReviewPage && currentQuestion ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: ` \xB7 ${pageIndex + 1}/${questions.length}` }) : /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " \xB7 review" })
10463
+ ] }),
10464
+ /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { color: DIM5, dimColor: true, children: " " + "\u2500".repeat(58) }) }),
10465
+ summary ? /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { color: SOFT6, children: ` ${summary}` }) }) : null,
10466
+ !isReviewPage && currentQuestion ? /* @__PURE__ */ jsxs15(Fragment3, { children: [
10467
+ /* @__PURE__ */ jsxs15(Box16, { marginTop: summary ? 1 : 0, children: [
10468
+ /* @__PURE__ */ jsx16(Text16, { children: " " }),
10469
+ /* @__PURE__ */ jsx16(Text16, { color: AMBER10, bold: true, children: currentQuestion.title })
10470
+ ] }),
10471
+ /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { color: SOFT6, children: ` ${currentQuestion.prompt}` }) }),
10472
+ /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", children: [
10473
+ currentQuestion.options.map((option, index) => {
10474
+ const active = cursor === index;
10475
+ return /* @__PURE__ */ jsxs15(Box16, { children: [
10476
+ /* @__PURE__ */ jsx16(Text16, { children: active ? " \u203A " : " " }),
10477
+ /* @__PURE__ */ jsxs15(Text16, { color: active ? GREEN6 : SOFT6, bold: active, children: [
10478
+ option.label,
10479
+ option.recommended ? " (Recommended)" : ""
10480
+ ] }),
10481
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: ` ${option.hint}` })
10482
+ ] }, option.id);
10483
+ }),
10484
+ currentQuestion.allowCustom ? /* @__PURE__ */ jsxs15(Box16, { children: [
10485
+ /* @__PURE__ */ jsx16(Text16, { children: cursor === currentQuestion.options.length ? " \u203A " : " " }),
10486
+ /* @__PURE__ */ jsx16(Text16, { color: cursor === currentQuestion.options.length ? AMBER10 : SOFT6, bold: cursor === currentQuestion.options.length, children: "Custom Answer" }),
10487
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: ` ${currentQuestion.customPlaceholder ?? "type your own answer"}` })
10488
+ ] }) : null
10489
+ ] }),
10490
+ customMode ? /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", children: [
10491
+ /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { color: DIM5, dimColor: true, children: " custom answer" }) }),
10492
+ /* @__PURE__ */ jsxs15(Box16, { children: [
10493
+ /* @__PURE__ */ jsx16(Text16, { children: " " }),
10494
+ /* @__PURE__ */ jsx16(Text16, { color: AMBER10, children: customText || " " })
10495
+ ] })
10496
+ ] }) : null
10497
+ ] }) : /* @__PURE__ */ jsxs15(Box16, { marginTop: summary ? 1 : 0, flexDirection: "column", children: [
10498
+ /* @__PURE__ */ jsxs15(Box16, { children: [
10499
+ /* @__PURE__ */ jsx16(Text16, { children: " " }),
10500
+ /* @__PURE__ */ jsx16(Text16, { color: AMBER10, bold: true, children: "Review Choices" })
10501
+ ] }),
10502
+ questions.map((question) => {
10503
+ const answer = answers[question.id];
10504
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, children: [
10505
+ /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { color: SOFT6, children: ` ${question.title}` }) }),
10506
+ /* @__PURE__ */ jsxs15(Box16, { children: [
10507
+ /* @__PURE__ */ jsx16(Text16, { children: " " }),
10508
+ /* @__PURE__ */ jsx16(Text16, { children: answer?.label ?? "not answered" })
10509
+ ] })
10510
+ ] }, question.id);
10511
+ }),
10512
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, flexDirection: "column", children: reviewActions.map((action, index) => {
10513
+ const active = reviewCursor === index;
10514
+ const label = action === "proceed" ? "Proceed" : action === "back" ? "Back" : "Cancel";
10515
+ const color = action === "proceed" ? GREEN6 : action === "back" ? AMBER10 : CORAL6;
10516
+ return /* @__PURE__ */ jsxs15(Box16, { children: [
10517
+ /* @__PURE__ */ jsx16(Text16, { children: active ? " \u203A " : " " }),
10518
+ /* @__PURE__ */ jsx16(Text16, { color: active ? color : SOFT6, bold: active, children: label })
10519
+ ] }, action);
10520
+ }) })
10521
+ ] }),
10522
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: DIM5, dimColor: true, children: " " + "\u2500".repeat(58) }) }),
10523
+ /* @__PURE__ */ jsxs15(Box16, { children: [
10524
+ /* @__PURE__ */ jsx16(Text16, { color: VIOLET11, children: " \u2191/\u2193 move" }),
10525
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " \xB7 " }),
10526
+ /* @__PURE__ */ jsx16(Text16, { color: VIOLET11, children: "\u2190 back" }),
10527
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " \xB7 " }),
10528
+ /* @__PURE__ */ jsx16(Text16, { color: GREEN6, children: "Enter select" }),
10529
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " \xB7 " }),
10530
+ /* @__PURE__ */ jsx16(Text16, { color: CORAL6, children: "Esc cancel" })
10531
+ ] })
10532
+ ] });
10533
+ }
10534
+
10535
+ // src/services/document/intent.ts
10536
+ function includesAny(text2, patterns) {
10537
+ return patterns.some((pattern) => pattern.test(text2));
10538
+ }
10539
+ function inferKind(text2) {
10540
+ if (/\b(textbook|book|course packet|course notes|chaptered|chapters?)\b/i.test(text2)) return "textbook";
10541
+ if (/\b(report|write[- ]?up|writeup|summary report|progress report|lab report|research report)\b/i.test(text2)) return "report";
10542
+ if (/\b(flashcards?|anki|cards?)\b/i.test(text2)) return "flashcards";
10543
+ if (/\b(quiz|test me|questions only|question bank)\b/i.test(text2)) return "quiz";
10544
+ if (/\b(cheatsheet|cheat sheet|reference sheet|quick reference)\b/i.test(text2)) return "cheatsheet";
10545
+ if (/\b(practice set|worksheet|exercises?|problem set)\b/i.test(text2)) return "practice-set";
10546
+ if (/\b(study guide|study material|study notes|learn this|notes to study from)\b/i.test(text2)) return "study-guide";
10547
+ return "generic-document";
10548
+ }
10549
+ function inferComplexity(text2, kind) {
10550
+ if (kind === "textbook") return "large";
10551
+ if (includesAny(text2, [/\b(holy grail|full|complete|comprehensive|long|detailed|deep|professional)\b/i])) {
10552
+ return "large";
10553
+ }
10554
+ if (includesAny(text2, [/\b(quick|short|basic|simple|brief)\b/i])) {
10555
+ return "small";
10556
+ }
10557
+ return kind === "report" ? "large" : "medium";
10558
+ }
10559
+ function inferDepth(text2, complexity, kind) {
10560
+ if (/\b(holy grail)\b/i.test(text2)) return "holy-grail";
10561
+ if (/\b(deep|comprehensive|thorough|detailed)\b/i.test(text2)) return "deep";
10562
+ if (/\b(quick|short|basic|simple|brief)\b/i.test(text2)) return "quick";
10563
+ if (kind === "study-guide" || kind === "textbook") return "deep";
10564
+ return complexity === "large" ? "deep" : "standard";
10565
+ }
10566
+ function inferTopic(text2, kind) {
10567
+ let topic = text2;
10568
+ topic = topic.replace(/\b(can you|could you|please|i want you to|make me|create|write|build|generate|turn this into)\b/gi, "");
10569
+ topic = topic.replace(/\b(a|an|the)\s+(study guide|study document|study material|study notes|quiz|report|textbook|book|cheatsheet|cheat sheet|practice set|flashcards?)\b/gi, "");
10570
+ topic = topic.replace(/\b(with|about|for|on)\b/gi, " ");
10571
+ topic = topic.replace(/\b(here|using latex|in latex|convert to pdf|after finishing.*|ignore them|built fresh|build fresh|black-white only|black and white only|no fancy boxes?.*)\b/gi, " ");
10572
+ topic = topic.replace(/[?]+/g, " ");
10573
+ topic = topic.replace(/\s+/g, " ").trim();
10574
+ if (!topic) {
10575
+ return kind === "report" ? "current work" : "requested topic";
10576
+ }
10577
+ return topic;
10578
+ }
10579
+ function detectDocumentIntent(text2) {
10580
+ const normalized = text2.trim();
10581
+ if (!normalized) return null;
10582
+ const hasDocumentVerb = includesAny(normalized, [
10583
+ /\b(make|create|write|build|generate|turn this into|prepare)\b/i,
10584
+ /\b(study guide|study material|study notes|quiz|flashcards?|cheatsheet|cheat sheet|practice set|report|textbook|book)\b/i
10585
+ ]);
10586
+ if (!hasDocumentVerb) return null;
10587
+ const kind = inferKind(normalized);
10588
+ const complexity = inferComplexity(normalized, kind);
10589
+ const depth = inferDepth(normalized, complexity, kind);
10590
+ const topic = inferTopic(normalized, kind);
10591
+ const useProjectContext = /\b(this repo|this project|our work|current directory|current project|from this)\b/i.test(normalized) || kind === "report";
10592
+ const useWorkingDirectoryResources = /\b(use the resources|use local files|use files in this directory|use materials in this directory|use documents in this directory|based on the provided files|from the workspace files)\b/i.test(normalized) && !/\b(ignore them|build fresh|built fresh|from scratch)\b/i.test(normalized);
10593
+ let entryMode = "confirm";
10594
+ if (kind === "textbook" || kind === "report" || complexity === "large") entryMode = "studio";
10595
+ else if (kind === "quiz" || kind === "flashcards" || kind === "cheatsheet") entryMode = "direct";
10596
+ return {
10597
+ kind,
10598
+ complexity,
10599
+ entryMode,
10600
+ depth,
10601
+ topic,
10602
+ useProjectContext,
10603
+ useWorkingDirectoryResources,
10604
+ originalText: normalized
10605
+ };
10606
+ }
10607
+ function documentKindLabel(kind) {
10608
+ switch (kind) {
10609
+ case "study-guide":
10610
+ return "Study Guide";
10611
+ case "quiz":
10612
+ return "Quiz";
10613
+ case "flashcards":
10614
+ return "Flashcards";
10615
+ case "cheatsheet":
10616
+ return "Cheatsheet";
10617
+ case "practice-set":
10618
+ return "Practice Set";
10619
+ case "report":
10620
+ return "Report";
10621
+ case "textbook":
10622
+ return "Textbook";
10623
+ case "generic-document":
10624
+ return "Document";
10625
+ }
10626
+ }
10627
+
10628
+ // src/cli/components/DocumentIntentPrompt.tsx
10629
+ import { jsx as jsx17 } from "react/jsx-runtime";
10630
+ function DocumentIntentPrompt({ intent, onResolve }) {
10631
+ const questions = [
10632
+ {
10633
+ id: "document_intent_action",
10634
+ title: `${documentKindLabel(intent.kind)} \xB7 ${intent.depth}`,
10635
+ prompt: `topic: ${intent.topic}`,
10636
+ options: [
10637
+ {
10638
+ id: "generate",
10639
+ label: "Generate Now",
10640
+ hint: `use the inferred ${documentKindLabel(intent.kind).toLowerCase()} shape immediately`,
10641
+ recommended: intent.entryMode !== "studio"
10642
+ },
10643
+ {
10644
+ id: "studio",
10645
+ label: "Open Studio",
10646
+ hint: "adjust type, depth, and context before generating",
10647
+ recommended: intent.entryMode === "studio"
10648
+ },
10649
+ {
10650
+ id: "cancel",
10651
+ label: "Cancel",
10652
+ hint: "return to normal chat"
10653
+ }
10654
+ ]
10655
+ }
10656
+ ];
10657
+ return /* @__PURE__ */ jsx17(
10658
+ GuidedClarificationPanel,
10659
+ {
10660
+ title: "document intent",
10661
+ summary: intent.originalText,
10662
+ questions,
10663
+ showReview: false,
10664
+ onComplete: (answers) => {
10665
+ const choice = answers[0]?.optionId;
10666
+ if (choice === "generate" || choice === "studio" || choice === "cancel") {
10667
+ onResolve(choice);
10668
+ return;
10669
+ }
10670
+ onResolve("cancel");
10671
+ },
10672
+ onCancel: () => onResolve("cancel")
10673
+ }
10674
+ );
10675
+ }
10676
+
10677
+ // src/cli/components/DocumentStudio.tsx
10678
+ import { jsx as jsx18 } from "react/jsx-runtime";
10679
+ var KINDS = [
10680
+ "study-guide",
10681
+ "quiz",
10682
+ "flashcards",
10683
+ "cheatsheet",
10684
+ "practice-set",
10685
+ "report",
10686
+ "textbook"
10687
+ ];
10688
+ var DEPTHS = ["quick", "standard", "deep", "holy-grail"];
10689
+ function DocumentStudio({ intent, onGenerate, onBack, onDismiss }) {
10690
+ const questions = [
10691
+ {
10692
+ id: "doc_kind",
10693
+ title: "Document Type",
10694
+ prompt: "Choose the output shape that best matches this request.",
10695
+ options: KINDS.map((kind) => ({
10696
+ id: kind,
10697
+ label: documentKindLabel(kind),
10698
+ hint: kind === "textbook" ? "long-form, chaptered document" : kind === "report" ? "professional engineering write-up" : kind === "study-guide" ? "teach and reinforce the topic" : "specialized study output",
10699
+ recommended: kind === intent.kind
10700
+ }))
10701
+ },
10702
+ {
10703
+ id: "doc_depth",
10704
+ title: "Depth",
10705
+ prompt: "Choose how much effort and detail zencefyl should put into the document.",
10706
+ options: DEPTHS.map((depth) => ({
10707
+ id: depth,
10708
+ label: depth,
10709
+ hint: depth === "holy-grail" ? "best quality, highest effort" : depth === "deep" ? "thorough and layered" : depth === "quick" ? "short and fast" : "balanced default",
10710
+ recommended: depth === intent.depth
10711
+ }))
10712
+ },
10713
+ {
10714
+ id: "doc_context",
10715
+ title: "Project Context",
10716
+ prompt: "Decide whether the current project and remembered work should influence the output.",
10717
+ options: [
10718
+ {
10719
+ id: "yes",
10720
+ label: "Use Project Context",
10721
+ hint: "include repo and recent-work context where relevant",
10722
+ recommended: intent.useProjectContext
10723
+ },
10724
+ {
10725
+ id: "no",
10726
+ label: "Do Not Use Project Context",
10727
+ hint: "keep the document topic-focused and context-light",
10728
+ recommended: !intent.useProjectContext
10729
+ }
10730
+ ]
10731
+ },
10732
+ {
10733
+ id: "doc_resources",
10734
+ title: "Resource Scope",
10735
+ prompt: "Decide whether zencefyl should build the document from files in the current working directory.",
10736
+ options: [
10737
+ {
10738
+ id: "cwd",
10739
+ label: "Use Working Directory Resources",
10740
+ hint: "use local files and project materials only; do not go outside the directory",
10741
+ recommended: true
10742
+ },
10743
+ {
10744
+ id: "topic-only",
10745
+ label: "Do Not Use Local Files",
10746
+ hint: "write from the request alone unless you explicitly provide extra material",
10747
+ recommended: false
10748
+ }
10749
+ ]
10750
+ },
10751
+ {
10752
+ id: "doc_header",
10753
+ title: "Header Details",
10754
+ prompt: "Add any document details zencefyl cannot know on its own, such as your name, ID, course, class, supervisor, or report code.",
10755
+ options: [
10756
+ {
10757
+ id: "none",
10758
+ label: "No Header Details",
10759
+ hint: "use only the title/topic unless I type extra details below",
10760
+ recommended: true
10761
+ }
10762
+ ],
10763
+ allowCustom: true,
10764
+ customPlaceholder: "examples: name, id, course, title, supervisor"
10765
+ },
10766
+ {
10767
+ id: "doc_custom",
10768
+ title: "Extra Requirements",
10769
+ prompt: "Add any extra constraints or leave this blank for the inferred defaults.",
10770
+ options: [
10771
+ {
10772
+ id: "none",
10773
+ label: "No Extra Requirements",
10774
+ hint: "generate with the inferred defaults only",
10775
+ recommended: true
10776
+ }
10777
+ ],
10778
+ allowCustom: true,
10779
+ customPlaceholder: "examples: focus on interview prep, explain more visually, make it academic"
10780
+ }
10781
+ ];
10782
+ return /* @__PURE__ */ jsx18(
10783
+ GuidedClarificationPanel,
10784
+ {
10785
+ title: "document studio",
10786
+ summary: intent.originalText,
10787
+ questions,
10788
+ showReview: true,
10789
+ onComplete: (answers) => {
10790
+ const answerMap = new Map(answers.map((answer) => [answer.questionId, answer]));
10791
+ const kind = answerMap.get("doc_kind")?.optionId;
10792
+ const depth = answerMap.get("doc_depth")?.optionId;
10793
+ const useProjectContext = answerMap.get("doc_context")?.optionId !== "no";
10794
+ const useWorkingDirectoryResources = answerMap.get("doc_resources")?.optionId !== "topic-only";
10795
+ const headerDetails = answerMap.get("doc_header")?.customText?.trim();
10796
+ const customNotes = answerMap.get("doc_custom")?.customText?.trim();
10797
+ onGenerate({
10798
+ kind: kind ?? intent.kind,
10799
+ depth: depth ?? intent.depth,
10800
+ useProjectContext,
10801
+ useWorkingDirectoryResources,
10802
+ ...headerDetails ? { headerDetails } : {},
10803
+ ...customNotes ? { customNotes } : {}
10804
+ });
10805
+ },
10806
+ onCancel: onDismiss
10807
+ }
10808
+ );
10809
+ }
10810
+
10811
+ // src/cli/components/CapabilityPrompt.tsx
10812
+ import { jsx as jsx19 } from "react/jsx-runtime";
10813
+ function CapabilityPrompt({ issue, onResolve }) {
10814
+ const questions = [
10815
+ {
10816
+ id: "capability_gap_action",
10817
+ title: issue.title,
10818
+ prompt: issue.reason,
10819
+ options: [
10820
+ ...issue.canAutoInstall && issue.autoInstallCommand ? [{
10821
+ id: "install",
10822
+ label: "Install Now",
10823
+ hint: issue.autoInstallCommand.label,
10824
+ recommended: true
10825
+ }] : [],
10826
+ {
10827
+ id: "guidance",
10828
+ label: "Show Install Steps",
10829
+ hint: issue.recommendation,
10830
+ recommended: !issue.canAutoInstall
10831
+ },
10832
+ {
10833
+ id: "continue",
10834
+ label: "Continue Without It",
10835
+ hint: issue.continueLabel ?? "continue the current task without this capability"
10836
+ },
10837
+ {
10838
+ id: "cancel",
10839
+ label: "Cancel",
10840
+ hint: "stop here and return to chat"
10841
+ }
10842
+ ]
10843
+ }
10844
+ ];
10845
+ return /* @__PURE__ */ jsx19(
10846
+ GuidedClarificationPanel,
10847
+ {
10848
+ title: "capability required",
10849
+ summary: issue.recommendation,
10850
+ questions,
10851
+ showReview: false,
10852
+ onComplete: (answers) => {
10853
+ const choice = answers[0]?.optionId;
10854
+ if (choice === "install" || choice === "guidance" || choice === "continue" || choice === "cancel") {
10855
+ onResolve(choice);
10856
+ return;
10857
+ }
10858
+ onResolve("cancel");
10859
+ },
10860
+ onCancel: () => onResolve("cancel")
10861
+ }
10862
+ );
10863
+ }
10864
+
10865
+ // src/cli/components/UpdatePrompt.tsx
10866
+ import { jsx as jsx20 } from "react/jsx-runtime";
10867
+ function UpdatePrompt({ update, onResolve }) {
10868
+ const summary = [
10869
+ `new version: v${update.latest}`,
10870
+ ...update.title ? [update.title] : [],
10871
+ ...update.message ? [update.message] : [],
10872
+ "npm install -g zencefyl@latest"
10873
+ ].join(" \xB7 ");
10874
+ const questions = [
10875
+ {
10876
+ id: "update_action",
10877
+ title: "Update Available",
10878
+ prompt: `zencefyl v${update.latest} is available.`,
10879
+ options: [
10880
+ {
10881
+ id: "update",
10882
+ label: "Update Now",
10883
+ hint: "install the latest global version and restart zencefyl",
10884
+ recommended: true
10885
+ },
10886
+ {
10887
+ id: "later",
10888
+ label: "Later",
10889
+ hint: "skip the update for now and continue into chat"
10890
+ }
10891
+ ]
10892
+ }
10893
+ ];
10894
+ return /* @__PURE__ */ jsx20(
10895
+ GuidedClarificationPanel,
10896
+ {
10897
+ title: "update available",
10898
+ summary,
10899
+ questions,
10900
+ showReview: false,
10901
+ onComplete: (answers) => {
10902
+ const choice = answers[0]?.optionId;
10903
+ onResolve(choice === "update" ? "update" : "later");
10904
+ },
10905
+ onCancel: () => onResolve("later")
10906
+ }
10907
+ );
10908
+ }
10909
+
10910
+ // src/services/document/templates.ts
10911
+ function qualityRules(depth) {
10912
+ switch (depth) {
10913
+ case "quick":
10914
+ return [
10915
+ "Keep it concise and immediately useful.",
10916
+ "Do not over-expand sections that are not essential."
10917
+ ];
10918
+ case "deep":
10919
+ return [
10920
+ "Explain foundations before jumping into advanced details.",
10921
+ "Use examples to make abstract points concrete.",
10922
+ "Add common mistakes or pitfalls where useful.",
10923
+ "Prefer a coherent teaching progression over disconnected bullet lists."
10924
+ ];
10925
+ case "holy-grail":
10926
+ return [
10927
+ "Aim for the best possible version, not the shortest.",
10928
+ "Use layered explanation, strong structure, and memorable examples.",
10929
+ "Add recap/reinforcement material where it improves retention.",
10930
+ "Make it feel like an authored, publishable teaching document rather than a generated summary."
10931
+ ];
10932
+ case "standard":
10933
+ default:
10934
+ return [
10935
+ "Keep the structure polished and balanced.",
10936
+ "Optimize for clarity, usefulness, and readability."
10937
+ ];
10938
+ }
10939
+ }
10940
+ function buildTemplate(kind, depth) {
10941
+ if (kind === "study-guide") {
10942
+ return {
10943
+ typeLine: "Create a study guide as a full LaTeX document.",
10944
+ structure: [
10945
+ "Open with what the topic is and why it matters.",
10946
+ "Include prerequisites or foundational ideas if needed.",
10947
+ "Cover core concepts in a logical order from first principles to implementation tradeoffs.",
10948
+ "Add intuitive mental models before formal structure where possible.",
10949
+ "Add worked examples, practical illustrations, or comparison tables where they genuinely improve understanding.",
10950
+ "Include common mistakes, synthesis/summary, and a short self-test section.",
10951
+ "Make the reader come away with both intuition and a clean comparative model of the topic."
10952
+ ],
10953
+ qualityRules: qualityRules(depth)
10954
+ };
10955
+ }
10956
+ if (kind === "report") {
10957
+ return {
10958
+ typeLine: "Create a professional engineering report as a full LaTeX document.",
10959
+ structure: [
10960
+ "Include context, objective, work completed, findings, issues, and next steps.",
10961
+ "Use direct, factual phrasing rather than vague filler.",
10962
+ "Make decisions, tradeoffs, and unresolved items easy to scan."
10963
+ ],
10964
+ qualityRules: qualityRules(depth)
10965
+ };
10966
+ }
10967
+ if (kind === "textbook") {
10968
+ return {
10969
+ typeLine: "Create a chaptered textbook-style learning document as a full LaTeX document.",
10970
+ structure: [
10971
+ "Organize the output into chapter-like sections with progression from foundations to advanced material.",
10972
+ "Each major section should teach, reinforce, and connect ideas instead of listing facts.",
10973
+ "Include recaps, exercises, and strong explanatory examples."
10974
+ ],
10975
+ qualityRules: qualityRules(depth)
10976
+ };
10977
+ }
10978
+ if (kind === "quiz") {
10979
+ return {
10980
+ typeLine: "Create a quiz as a full LaTeX document.",
10981
+ structure: [
10982
+ "Use varied difficulty levels.",
10983
+ "Provide an answer key and concise explanations when helpful."
10984
+ ],
10985
+ qualityRules: qualityRules(depth)
10986
+ };
10987
+ }
10988
+ if (kind === "flashcards") {
10989
+ return {
10990
+ typeLine: "Create flashcards as a full LaTeX document.",
10991
+ structure: [
10992
+ "Format each card clearly with a front and back.",
10993
+ "Keep each card focused on one idea."
10994
+ ],
10995
+ qualityRules: qualityRules(depth)
10996
+ };
10997
+ }
10998
+ if (kind === "cheatsheet") {
10999
+ return {
11000
+ typeLine: "Create a cheatsheet as a full LaTeX document.",
11001
+ structure: [
11002
+ "Optimize for compact reference value.",
11003
+ "Use dense but readable sections, shortcuts, patterns, or formulas."
11004
+ ],
11005
+ qualityRules: qualityRules(depth)
11006
+ };
11007
+ }
11008
+ if (kind === "practice-set") {
11009
+ return {
11010
+ typeLine: "Create a practice set as a full LaTeX document.",
11011
+ structure: [
11012
+ "Include exercises in rising difficulty.",
11013
+ "Add hints or solutions where useful."
11014
+ ],
11015
+ qualityRules: qualityRules(depth)
11016
+ };
11017
+ }
11018
+ return {
11019
+ typeLine: "Create a structured LaTeX document.",
11020
+ structure: [
11021
+ "Use a clear title and coherent section structure.",
11022
+ "Make the document directly useful for the stated goal."
11023
+ ],
11024
+ qualityRules: qualityRules(depth)
11025
+ };
11026
+ }
11027
+
11028
+ // src/services/document/study-guide.ts
11029
+ function buildStudyGuidePrompt(topic, depth) {
11030
+ const template = buildTemplate("study-guide", depth);
11031
+ return [
11032
+ "[document_mode]",
11033
+ template.typeLine,
11034
+ ...template.structure.map((line) => `- ${line}`),
11035
+ ...template.qualityRules.map((line) => `- ${line}`),
11036
+ "- Teach for deep conceptual understanding, not just surface familiarity.",
11037
+ "- Build intuition first, then formal structure, then engineering tradeoffs.",
11038
+ "- Make the document feel authored and coherent rather than like a stacked list of notes.",
11039
+ "- Start by internally deciding the best explanatory arc; then write the final document as one coherent piece.",
11040
+ "- For technical comparison topics, explicitly compare the variants, explain when each wins, and end with a unifying mental model.",
11041
+ "- Use tables or compact diagrams only when they genuinely improve understanding.",
11042
+ '- Avoid vague filler like "it depends" unless you immediately explain what it depends on and why.',
11043
+ "- If a claim is not grounded in provided materials or stable domain knowledge, leave it out rather than bluffing.",
11044
+ "",
11045
+ "[focus]",
11046
+ topic
11047
+ ].join("\n");
11048
+ }
11049
+
11050
+ // src/services/document/report.ts
11051
+ function buildReportPrompt(topic, depth) {
11052
+ const template = buildTemplate("report", depth);
11053
+ return [
11054
+ "[document_mode]",
11055
+ template.typeLine,
11056
+ ...template.structure.map((line) => `- ${line}`),
11057
+ ...template.qualityRules.map((line) => `- ${line}`),
11058
+ "",
11059
+ "[focus]",
11060
+ topic
11061
+ ].join("\n");
11062
+ }
11063
+
11064
+ // src/services/document/textbook.ts
11065
+ function buildTextbookPrompt(topic, depth) {
11066
+ const template = buildTemplate("textbook", depth);
11067
+ return [
11068
+ "[document_mode]",
11069
+ template.typeLine,
11070
+ ...template.structure.map((line) => `- ${line}`),
11071
+ ...template.qualityRules.map((line) => `- ${line}`),
11072
+ "- Start with an outline or chapter-like structure before teaching the material in depth.",
11073
+ "- Use progression, recap, and reinforcement instead of a flat dump of content.",
11074
+ "",
11075
+ "[focus]",
11076
+ topic
11077
+ ].join("\n");
11078
+ }
11079
+
11080
+ // src/services/document/generator.ts
11081
+ function buildLatexModeBlock(kind) {
11082
+ return [
11083
+ "[latex_mode]",
11084
+ "Output only a complete standalone LaTeX document.",
11085
+ "Do not output markdown.",
11086
+ "Do not wrap the LaTeX in fenced code blocks.",
11087
+ "Include a deliberate preamble and document structure suitable for pdflatex compilation.",
11088
+ `Write the document as a ${documentKindLabel(kind).toLowerCase()} artifact, not as a chat answer.`,
11089
+ "Default visual standard: black-and-white, reader-friendly, no decorative boxes unless they materially improve understanding.",
11090
+ "Use LaTeX packages when they genuinely help readability, tables, diagrams, or structure."
11091
+ ];
11092
+ }
11093
+ function buildKindSpecificPrompt(kind, topic, depth) {
11094
+ if (kind === "study-guide") return buildStudyGuidePrompt(topic, depth);
11095
+ if (kind === "report") return buildReportPrompt(topic, depth);
11096
+ if (kind === "textbook") return buildTextbookPrompt(topic, depth);
11097
+ const template = buildTemplate(kind, depth);
11098
+ return [
11099
+ "[document_mode]",
11100
+ template.typeLine,
11101
+ ...template.structure.map((line) => `- ${line}`),
11102
+ ...template.qualityRules.map((line) => `- ${line}`),
11103
+ "",
11104
+ "[focus]",
11105
+ topic
11106
+ ].join("\n");
11107
+ }
11108
+ function buildContextBlock(container, useProjectContext) {
11109
+ const lines = [];
11110
+ if (!useProjectContext) return lines;
11111
+ if (container.projectCtx) {
11112
+ lines.push(`Current project: ${container.projectCtx.name}`);
11113
+ lines.push(`Working directory: ${container.projectCtx.path}`);
11114
+ if (container.projectCtx.language) lines.push(`Primary language: ${container.projectCtx.language}`);
11115
+ if (container.projectCtx.gitRemote) lines.push(`Git remote: ${container.projectCtx.gitRemote}`);
11116
+ } else {
11117
+ lines.push("No detected project context is available for this request.");
11118
+ }
11119
+ return lines;
11120
+ }
11121
+ function buildResourceBlock(container, useWorkingDirectoryResources) {
11122
+ const cwd = container.projectCtx?.path ?? process.cwd();
11123
+ if (!useWorkingDirectoryResources) {
11124
+ return [
11125
+ "Do not rely on files from the working directory unless the user explicitly references them.",
11126
+ "Use only the user request and stable general knowledge needed to write the document."
11127
+ ];
11128
+ }
11129
+ return [
11130
+ `Allowed resource root: ${cwd}`,
11131
+ "Use only resources that are present inside the working directory or explicitly provided by the user in this chat.",
11132
+ "Do not use external sources, web knowledge, or citations unless the user explicitly supplied them.",
11133
+ "If the local materials are insufficient, say what is missing instead of inventing sources or details."
11134
+ ];
11135
+ }
11136
+ function buildAuthoringBlock(kind, depth) {
11137
+ const lines = [
11138
+ "Write like an expert teacher who is trying to leave the reader with a durable mental model, not just notes.",
11139
+ "Prefer coherent prose sections over fragmented bullets unless list structure clearly improves the result.",
11140
+ "Use sectioning deliberately so the document reads like a designed artifact."
11141
+ ];
11142
+ if (kind === "study-guide" || kind === "textbook") {
11143
+ lines.push("Teach from intuition to mechanism to tradeoff to synthesis.");
11144
+ lines.push("When comparing multiple approaches, give the reader a clean final comparison framework.");
11145
+ }
11146
+ if (depth === "holy-grail") {
11147
+ lines.push("Assume the reader deserves the best default version you can produce without being asked follow-up questions.");
11148
+ lines.push("Bias toward depth, explanatory care, and clean structure over brevity.");
11149
+ }
11150
+ return lines;
11151
+ }
11152
+ function buildDocumentGenerationPrompt(container, intent, options) {
11153
+ const kind = options?.kind ?? intent.kind;
11154
+ const depth = options?.depth ?? intent.depth;
11155
+ const useProjectContext = options?.useProjectContext ?? intent.useProjectContext;
11156
+ const useWorkingDirectoryResources = options?.useWorkingDirectoryResources ?? intent.useWorkingDirectoryResources;
11157
+ const headerDetails = options?.headerDetails?.trim();
11158
+ const customNotes = options?.customNotes?.trim();
11159
+ const kindPrompt = buildKindSpecificPrompt(kind, intent.topic, depth);
11160
+ const contextLines = buildContextBlock(container, useProjectContext);
11161
+ const resourceLines = buildResourceBlock(container, useWorkingDirectoryResources);
11162
+ const authoringLines = buildAuthoringBlock(kind, depth);
11163
+ return [
11164
+ kindPrompt,
11165
+ "",
11166
+ ...buildLatexModeBlock(kind),
11167
+ "",
11168
+ "[output_rules]",
11169
+ "- Use headings, sections, and internal structure that fit the document type.",
11170
+ "- Default to a restrained academic/engineering style: black-and-white, clear hierarchy, no decorative callout boxes unless they materially improve understanding.",
11171
+ "- Prefer substance, structure, and readability over flashy visual tricks.",
11172
+ "- Never include tool logs, prompt-injection warnings, safety notes, filesystem chatter, or command output in the final document.",
11173
+ "- If you inspect local materials, use them only as source material; do not mention the inspection process in the document.",
11174
+ "- The final output must compile under pdflatex without depending on markdown conversion.",
11175
+ "",
11176
+ "[authoring_standard]",
11177
+ ...authoringLines,
11178
+ "",
11179
+ "[resource_scope]",
11180
+ ...resourceLines,
11181
+ ...contextLines.length > 0 ? ["", "[available_context]", ...contextLines] : [],
11182
+ ...headerDetails ? ["", "[document_header_details]", headerDetails] : [],
11183
+ ...customNotes ? ["", "[additional_requirements]", customNotes] : [],
11184
+ "",
11185
+ "[user_request]",
11186
+ intent.originalText
11187
+ ].join("\n");
11188
+ }
11189
+
11190
+ // src/services/document/export.ts
11191
+ import fs14 from "fs";
11192
+ import path21 from "path";
11193
+ import { spawnSync as spawnSync11 } from "child_process";
11194
+
11195
+ // src/services/capabilities/index.ts
11196
+ import { spawnSync as spawnSync10 } from "child_process";
11197
+ var CapabilityError = class extends Error {
11198
+ issue;
11199
+ constructor(issue) {
11200
+ super(issue.reason);
11201
+ this.name = "CapabilityError";
11202
+ this.issue = issue;
11203
+ }
11204
+ };
11205
+ function isCapabilityError(value) {
11206
+ return value instanceof CapabilityError;
11207
+ }
11208
+ function executeCapabilityAutoInstall(issue) {
11209
+ if (!issue.canAutoInstall || !issue.autoInstallCommand) {
11210
+ return { ok: false, output: "automatic setup is not available for this capability" };
11211
+ }
11212
+ const result = spawnSync10(issue.autoInstallCommand.command, issue.autoInstallCommand.args, {
11213
+ encoding: "utf8"
11214
+ });
11215
+ const output = [result.stdout?.trim(), result.stderr?.trim()].filter(Boolean).join("\n\n");
11216
+ return {
11217
+ ok: (result.status ?? 1) === 0,
11218
+ ...output ? { output } : {}
11219
+ };
11220
+ }
11221
+ function installHint(binary) {
11222
+ if (process.platform === "darwin") {
11223
+ return binary === "pandoc" ? ["brew install pandoc"] : ["brew install --cask mactex-no-gui"];
11224
+ }
11225
+ if (process.platform === "win32") {
11226
+ return binary === "pandoc" ? [
11227
+ "winget install --id JohnMacFarlane.Pandoc -e",
11228
+ "or install Pandoc from https://pandoc.org/installing.html"
11229
+ ] : [
11230
+ "winget install --id MiKTeX.MiKTeX -e",
11231
+ "or install MiKTeX or TeX Live manually and ensure pdflatex is on PATH"
11232
+ ];
11233
+ }
11234
+ return binary === "pandoc" ? ["sudo apt install pandoc"] : ["sudo apt install texlive-latex-base"];
11235
+ }
11236
+ function autoInstallCommand(binary) {
11237
+ if (process.platform === "darwin") {
11238
+ return binary === "pandoc" ? {
11239
+ command: "brew",
11240
+ args: ["install", "pandoc"],
11241
+ label: "Install Pandoc with Homebrew"
11242
+ } : {
11243
+ command: "brew",
11244
+ args: ["install", "--cask", "mactex-no-gui"],
11245
+ label: "Install LaTeX with Homebrew"
11246
+ };
11247
+ }
11248
+ if (process.platform === "win32") {
11249
+ return binary === "pandoc" ? {
11250
+ command: "winget",
11251
+ args: ["install", "--id", "JohnMacFarlane.Pandoc", "-e"],
11252
+ label: "Install Pandoc with winget"
11253
+ } : {
11254
+ command: "winget",
11255
+ args: ["install", "--id", "MiKTeX.MiKTeX", "-e"],
11256
+ label: "Install MiKTeX with winget"
11257
+ };
11258
+ }
11259
+ return void 0;
11260
+ }
11261
+ function createMissingBinaryIssue(binary, purpose) {
11262
+ const autoInstall = autoInstallCommand(binary);
11263
+ return {
11264
+ id: `missing-binary:${binary}`,
11265
+ title: `${binary} is required`,
11266
+ reason: `${binary} is required for ${purpose} but is not installed on this machine.`,
11267
+ recommendation: binary === "pandoc" ? "Install Pandoc so zencefyl can convert generated markdown into LaTeX/PDF." : "Install a LaTeX engine so zencefyl can compile generated documents into PDF.",
11268
+ installSteps: installHint(binary),
11269
+ continueLabel: "keep the generated document in chat for now",
11270
+ canAutoInstall: Boolean(autoInstall),
11271
+ autoInstallCommand: autoInstall
11272
+ };
11273
+ }
11274
+
11275
+ // src/services/document/export.ts
11276
+ function timestamp(date) {
11277
+ const pad = (value) => String(value).padStart(2, "0");
11278
+ return [
11279
+ date.getFullYear(),
11280
+ pad(date.getMonth() + 1),
11281
+ pad(date.getDate())
11282
+ ].join("-") + "_" + [
11283
+ pad(date.getHours()),
11284
+ pad(date.getMinutes()),
11285
+ pad(date.getSeconds())
11286
+ ].join("-");
11287
+ }
11288
+ function slugifyTopic(topic) {
11289
+ const value = topic.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11290
+ return value || "document";
11291
+ }
11292
+ function requireBinary(name) {
11293
+ const result = spawnSync11(name, ["--version"], { encoding: "utf8", stdio: "ignore" });
11294
+ if (result.status !== 0) {
11295
+ throw new CapabilityError(createMissingBinaryIssue(name, "direct LaTeX/PDF export"));
11296
+ }
11297
+ }
11298
+ function extractLatexDocument(content) {
11299
+ const trimmed = content.trim();
11300
+ const fenced = trimmed.match(/^```(?:latex)?\s*([\s\S]*?)\s*```$/i);
11301
+ return (fenced?.[1] ?? trimmed).trim();
11302
+ }
11303
+ function buildPdfArtifacts(latex, outputStem) {
11304
+ requireBinary("pdflatex");
11305
+ const latexPath = `${outputStem}.tex`;
11306
+ const pdfPath = `${outputStem}.pdf`;
11307
+ const latexSource = extractLatexDocument(latex);
11308
+ fs14.writeFileSync(latexPath, latexSource, "utf8");
11309
+ const outputDir = path21.dirname(outputStem);
11310
+ const texFile = path21.basename(latexPath);
11311
+ try {
11312
+ for (let pass = 0; pass < 2; pass++) {
11313
+ const compile = spawnSync11("pdflatex", [
11314
+ "-interaction=nonstopmode",
11315
+ "-halt-on-error",
11316
+ "-output-directory",
11317
+ outputDir,
11318
+ texFile
11319
+ ], {
11320
+ cwd: outputDir,
11321
+ encoding: "utf8"
11322
+ });
11323
+ if (compile.status !== 0) {
11324
+ throw new Error(compile.stderr?.trim() || compile.stdout?.trim() || "pdflatex failed while generating PDF");
11325
+ }
11326
+ }
11327
+ return { pdfPath, latexPath };
11328
+ } catch (err) {
11329
+ throw err;
11330
+ }
11331
+ }
11332
+ function exportTextbook(latex, baseDir, baseName) {
11333
+ const folderPath = path21.join(baseDir, baseName);
11334
+ fs14.mkdirSync(folderPath, { recursive: true });
11335
+ const outputStem = path21.join(folderPath, "main");
11336
+ const { pdfPath, latexPath } = buildPdfArtifacts(latex, outputStem);
11337
+ return { path: folderPath, mode: "folder", latexPath: latexPath || pdfPath.replace(/\.pdf$/, ".tex") };
11338
+ }
11339
+ function exportGeneratedDocument(latex, options) {
11340
+ const baseDir = options.cwd ?? process.cwd();
11341
+ const prefix = options.kind === "textbook" ? "zencefyl-textbook" : `zencefyl-${options.kind}`;
11342
+ const baseName = `${prefix}-${timestamp(/* @__PURE__ */ new Date())}-${slugifyTopic(options.topic)}`;
11343
+ if (options.kind === "textbook") {
11344
+ return exportTextbook(latex, baseDir, baseName);
11345
+ }
11346
+ const outputStem = path21.join(baseDir, baseName);
11347
+ const { pdfPath, latexPath } = buildPdfArtifacts(latex, outputStem);
11348
+ return { path: pdfPath, mode: "file", latexPath };
11349
+ }
11350
+
11351
+ // src/services/document/repair.ts
11352
+ async function repairLatexDocument(container, originalLatex, compileError, topic) {
11353
+ const model = session.model || container.config.models.default;
11354
+ const prompt = [
11355
+ "Repair the following LaTeX document so it compiles cleanly with pdflatex.",
11356
+ "Output only the full corrected standalone LaTeX document.",
11357
+ "Do not explain your changes.",
11358
+ `Topic: ${topic}`,
11359
+ "",
11360
+ "[compile_error]",
11361
+ compileError.trim(),
11362
+ "",
11363
+ "[current_latex]",
11364
+ originalLatex.trim()
11365
+ ].join("\n");
11366
+ let repaired = "";
11367
+ for await (const delta of container.provider.chat(
11368
+ [{ role: "user", content: prompt }],
11369
+ [
11370
+ "You repair LaTeX documents for pdflatex compilation.",
11371
+ "Return only corrected LaTeX.",
11372
+ "Preserve the intent, structure, and quality of the original document while fixing compilation issues."
11373
+ ].join("\n"),
11374
+ model
11375
+ )) {
11376
+ if (delta.type === "text") repaired += delta.text;
11377
+ if (delta.type === "done") break;
11378
+ }
11379
+ return repaired.trim();
11380
+ }
11381
+
11382
+ // src/cli/App.tsx
11383
+ import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
11384
+ function isLikelyArtifactRequest(text2) {
11385
+ const lower = text2.toLowerCase();
11386
+ return /\b(make|create|write|build|compile|run|test|fix|update|implement)\b/.test(lower) && /\b(file|code|program|script|tool|app|project|cpp|c\+\+|python|js|ts|calculator|binary|executable)\b/.test(lower);
11387
+ }
11388
+ function summarizeArtifactTask(task) {
11389
+ if (task.status !== "completed") return null;
11390
+ if (task.filesTouched.length === 0 && task.commandsRun.length === 0) return null;
11391
+ const files = task.filesTouched.slice(-3);
11392
+ const latestCommand = task.commandsRun.at(-1);
11393
+ if (files.length > 0 && latestCommand) {
11394
+ return `finished the task, updated ${files.join(", ")}, and ran ${latestCommand}`;
11395
+ }
11396
+ if (files.length > 0) {
11397
+ return `finished the task and updated ${files.join(", ")}`;
11398
+ }
11399
+ if (latestCommand) {
11400
+ return `finished the task and ran ${latestCommand}`;
11401
+ }
11402
+ return "finished the task";
11403
+ }
11404
+ function documentKindLabel2(kind) {
11405
+ return kind.replace(/-/g, " ");
11406
+ }
11407
+ function documentPdfPath(exported) {
11408
+ if (exported.mode === "file") return exported.path;
11409
+ if (exported.latexPath) return exported.latexPath.replace(/\.tex$/i, ".pdf");
11410
+ return `${exported.path}/main.pdf`;
11411
+ }
11412
+ function formatDocumentSuccessMessage(kind, exported) {
11413
+ const label = documentKindLabel2(kind);
11414
+ const pdfPath = documentPdfPath(exported);
11415
+ const latexPath = exported.latexPath;
11416
+ if (exported.mode === "folder") {
11417
+ return [
11418
+ `built the ${label}`,
11419
+ "",
11420
+ `- **folder**: \`${exported.path}\``,
11421
+ `- **pdf**: \`${pdfPath}\``,
11422
+ ...latexPath ? [`- **latex**: \`${latexPath}\``] : []
11423
+ ].join("\n");
11424
+ }
11425
+ return [
11426
+ `built the ${label}`,
11427
+ "",
11428
+ `- **pdf**: \`${pdfPath}\``,
11429
+ ...latexPath ? [`- **latex**: \`${latexPath}\``] : []
11430
+ ].join("\n");
11431
+ }
11432
+ function formatDocumentReadyNotice(kind, exported) {
11433
+ return `${documentKindLabel2(kind)} ready \xB7 ${documentPdfPath(exported)}`;
11434
+ }
11435
+ function collapseInsertedChunk(text2, cursorOffset, collapsedInsert) {
11436
+ if (!collapsedInsert) return { text: text2, cursorOffset };
11437
+ const { start, end, label } = collapsedInsert;
11438
+ if (start < 0 || end < start || end > text2.length) return { text: text2, cursorOffset };
11439
+ const collapsedText = text2.slice(0, start) + label + text2.slice(end);
11440
+ if (cursorOffset <= start) return { text: collapsedText, cursorOffset };
11441
+ if (cursorOffset >= end) {
11442
+ return {
11443
+ text: collapsedText,
11444
+ cursorOffset: start + label.length + (cursorOffset - end)
11445
+ };
11446
+ }
11447
+ return {
11448
+ text: collapsedText,
11449
+ cursorOffset: start + label.length
11450
+ };
11451
+ }
11452
+ function App({ engine, container }) {
11453
+ const { exit } = useApp();
11454
+ _verbPool = useMemo10(() => resolveThinkingVerbs(container.config.thinkingVerbs), [container]);
11455
+ _reducedMotion = container.config.prefersReducedMotion;
11456
+ const [messages, setMessages] = useState16(() => engine.getHistory());
11457
+ const [isStreaming, setIsStreaming] = useState16(false);
11458
+ const [streamText, setStreamText] = useState16("");
11459
+ const [toolEvents, setToolEvents] = useState16([]);
11460
+ const [error, setError] = useState16(null);
11461
+ const [isOffline, setIsOffline] = useState16(false);
11462
+ const [pendingUserMessage, setPendingUserMessage] = useState16(null);
11463
+ const [inputTokens, setInputTokens] = useState16(0);
11464
+ const [outputTokens, setOutputTokens] = useState16(0);
11465
+ const [messageCount, setMessageCount] = useState16(0);
11466
+ const [inputHistory, setInputHistory] = useState16(() => [...loadHistory()].reverse());
11467
+ const abortRef = useRef3(null);
11468
+ const [queuedMessage, setQueuedMessage] = useState16(null);
11469
+ const queuedMessageRef = useRef3(null);
11470
+ const [lastThinkingMs, setLastThinkingMs] = useState16(null);
11471
+ const streamingStartRef = useRef3(0);
11472
+ const [searchOpen, setSearchOpen] = useState16(false);
11473
+ const [modelPickerOpen, setModelPickerOpen] = useState16(false);
11474
+ const [settingsOpen, setSettingsOpen] = useState16(false);
11475
+ const [infoPanel, setInfoPanel] = useState16(null);
11476
+ const [commandProgress, setCommandProgress] = useState16(null);
11477
+ const [forgetPanel, setForgetPanel] = useState16(null);
11478
+ const [prunePanel, setPrunePanel] = useState16(null);
11479
+ const [reviewPanel, setReviewPanel] = useState16(null);
11480
+ const [documentIntentPrompt, setDocumentIntentPrompt] = useState16(null);
11481
+ const [documentStudioIntent, setDocumentStudioIntent] = useState16(null);
11482
+ const [capabilityPrompt, setCapabilityPrompt] = useState16(null);
11483
+ const [pendingToolApproval, setPendingToolApproval] = useState16(null);
11484
+ const [backgroundJobs, setBackgroundJobs] = useState16(() => container.backgroundJobs.getJobs());
11485
+ const [actionTasks, setActionTasks] = useState16(() => container.actionTasks.getTasks());
11486
+ const [notice, setNotice] = useState16(null);
11487
+ const noticeTimerRef = useRef3(null);
11488
+ const toolApprovalResolveRef = useRef3(null);
11489
+ const latestRunningTaskIdRef = useRef3(null);
11490
+ const taskApprovalScopesRef = useRef3(/* @__PURE__ */ new Map());
11491
+ const patternApprovalScopesRef = useRef3(/* @__PURE__ */ new Set());
11492
+ const pendingToolApprovalRef = useRef3(null);
11493
+ const pendingCapabilityActionRef = useRef3(null);
11494
+ const searchRestoreRef = useRef3("");
11495
+ const [attachedContext, setAttachedContext] = useState16(null);
11496
+ const showNotice = useCallback3((text2, timeoutMs = 3200) => {
11497
+ setNotice(text2);
11498
+ if (noticeTimerRef.current) clearTimeout(noticeTimerRef.current);
11499
+ noticeTimerRef.current = setTimeout(() => {
11500
+ setNotice(null);
11501
+ noticeTimerRef.current = null;
11502
+ }, timeoutMs);
11503
+ }, []);
11504
+ const applyThinkingMode = useCallback3((mode, persistDefault = false) => {
11505
+ const nextModel = resolveThinkingModeModel(container.config.models, mode);
11506
+ const previousModel = session.model;
11507
+ if (container.config.provider === "ollama" && previousModel !== nextModel) {
11508
+ stopOllamaModel(previousModel);
11509
+ }
11510
+ if (container.config.provider === "local-transformers" && previousModel !== nextModel) {
11511
+ clearLocalModelPipelines(nextModel);
11512
+ }
11513
+ session.thinkingMode = mode;
11514
+ session.model = nextModel;
11515
+ logRuntimeEvent("model.switched", `provider=${container.config.provider} model=${nextModel} thinking=${mode}`);
11516
+ if (persistDefault) {
11517
+ const updatedConfig = { ...container.config, defaultThinkingMode: mode };
11518
+ saveConfig(updatedConfig);
11519
+ container.config.defaultThinkingMode = mode;
11520
+ }
11521
+ showNotice(`${persistDefault ? "default thinking" : "thinking"} set to ${mode} \xB7 ${nextModel}`);
11522
+ }, [container.config, showNotice]);
11523
+ const applyInteractionMode = useCallback3((mode, persistDefault = false) => {
11524
+ session.interactionMode = mode;
11525
+ logRuntimeEvent("session.mode_changed", `interaction=${mode}`);
11526
+ if (persistDefault) {
11527
+ const updatedConfig = {
11528
+ ...container.config,
11529
+ toolPermissionMode: toolPermissionModeForInteractionMode(mode)
11530
+ };
11531
+ saveConfig(updatedConfig);
11532
+ container.config.toolPermissionMode = updatedConfig.toolPermissionMode;
11533
+ }
11534
+ showNotice(`${persistDefault ? "default mode" : "mode"} set to ${mode}`);
11535
+ }, [container.config, showNotice]);
11536
+ useEffect8(() => {
11537
+ process.title = "zencefyl";
11538
+ }, []);
11539
+ useEffect8(() => {
11540
+ engine.setToolPermissionHandler((request) => new Promise((resolve3) => {
11541
+ if (patternApprovalScopesRef.current.has(request.scopeKey)) {
11542
+ resolve3(true);
11543
+ return;
11544
+ }
11545
+ const currentTaskId = latestRunningTaskIdRef.current;
11546
+ if (currentTaskId) {
11547
+ const scopes = taskApprovalScopesRef.current.get(currentTaskId);
11548
+ if (scopes?.has(request.scopeKey)) {
11549
+ resolve3(true);
11550
+ return;
11551
+ }
11552
+ }
11553
+ toolApprovalResolveRef.current = resolve3;
11554
+ pendingToolApprovalRef.current = request;
11555
+ setPendingToolApproval(request);
11556
+ }));
11557
+ return () => {
11558
+ if (toolApprovalResolveRef.current) {
11559
+ toolApprovalResolveRef.current(false);
11560
+ toolApprovalResolveRef.current = null;
11561
+ }
11562
+ pendingToolApprovalRef.current = null;
11563
+ engine.setToolPermissionHandler(null);
11564
+ };
11565
+ }, [engine]);
11566
+ useEffect8(() => container.backgroundJobs.subscribe(() => {
11567
+ setBackgroundJobs(container.backgroundJobs.getJobs());
11568
+ }), [container]);
11569
+ useEffect8(() => container.actionTasks.subscribe(() => {
11570
+ setActionTasks(container.actionTasks.getTasks());
11571
+ }), [container]);
11572
+ useEffect8(() => {
11573
+ const runningTasks = actionTasks.filter((task) => task.status === "running");
11574
+ latestRunningTaskIdRef.current = runningTasks.at(-1)?.id ?? null;
11575
+ const activeTaskIds = new Set(runningTasks.map((task) => task.id));
11576
+ for (const taskId of [...taskApprovalScopesRef.current.keys()]) {
11577
+ if (!activeTaskIds.has(taskId)) taskApprovalScopesRef.current.delete(taskId);
11578
+ }
11579
+ }, [actionTasks]);
11580
+ const resolveToolApproval = useCallback3((choice) => {
11581
+ const request = pendingToolApprovalRef.current;
11582
+ const currentTaskId = latestRunningTaskIdRef.current;
11583
+ if (request) {
11584
+ if (choice === "approve-task" && currentTaskId) {
11585
+ const scopes = taskApprovalScopesRef.current.get(currentTaskId) ?? /* @__PURE__ */ new Set();
11586
+ scopes.add(request.scopeKey);
11587
+ taskApprovalScopesRef.current.set(currentTaskId, scopes);
11588
+ }
11589
+ if (choice === "approve-pattern") {
11590
+ patternApprovalScopesRef.current.add(request.scopeKey);
11591
+ }
11592
+ }
11593
+ toolApprovalResolveRef.current?.(choice !== "deny");
11594
+ toolApprovalResolveRef.current = null;
11595
+ pendingToolApprovalRef.current = null;
11596
+ setPendingToolApproval(null);
11597
+ }, []);
11598
+ useEffect8(() => {
11599
+ void getHighlightPromise();
11600
+ }, []);
11601
+ const [updateAvailable, setUpdateAvailable] = useState16(null);
11602
+ useEffect8(() => {
11603
+ void getUpdateInfo().then((v) => {
11604
+ if (v) setUpdateAvailable(v);
11605
+ });
11606
+ }, []);
11607
+ function handleStartupUpdateResolve(choice) {
11608
+ if (choice === "update") {
11609
+ requestUpdate();
11610
+ exit();
11611
+ return;
11612
+ }
11613
+ setUpdateAvailable(null);
11614
+ }
11615
+ const lastAssistantText = useMemo10(() => {
11616
+ for (let i = messages.length - 1; i >= 0; i--) {
11617
+ if (messages[i].role === "assistant") {
11618
+ return messageText(messages[i].content);
11619
+ }
11620
+ }
11621
+ return "";
11622
+ }, [messages]);
11623
+ const switchProviderConfig = useCallback3((provider, modelId) => {
11624
+ const current = container.config;
10088
11625
  if (provider === "ollama") {
10089
11626
  return {
10090
11627
  ...current,
@@ -10123,6 +11660,13 @@ function App({ engine, container }) {
10123
11660
  models: { ...MOONSHOT_MODELS, default: modelId }
10124
11661
  };
10125
11662
  }
11663
+ if (provider === "claude-code") {
11664
+ return {
11665
+ ...current,
11666
+ provider: "claude-code",
11667
+ models: { ...current.models, default: modelId }
11668
+ };
11669
+ }
10126
11670
  return {
10127
11671
  ...current,
10128
11672
  provider: "anthropic",
@@ -10131,11 +11675,12 @@ function App({ engine, container }) {
10131
11675
  }, [container.config]);
10132
11676
  const hasStoredCredentials = useCallback3((provider) => {
10133
11677
  const creds = loadCredentials(container.config.dataDir);
11678
+ if (provider === "claude-code") return true;
10134
11679
  if (provider === "openai-subscription") return Boolean(creds["openai-subscription"]);
10135
11680
  if (provider === "gemini-subscription") return Boolean(creds["gemini-subscription"]);
10136
11681
  return false;
10137
11682
  }, [container.config.dataDir]);
10138
- const lastDuckMention = useMemo7(() => {
11683
+ const lastDuckMention = useMemo10(() => {
10139
11684
  for (let i = messages.length - 1; i >= 0; i--) {
10140
11685
  const text2 = messageText(messages[i].content);
10141
11686
  if (/\bthe duck\b/i.test(text2)) return text2;
@@ -10146,6 +11691,225 @@ function App({ engine, container }) {
10146
11691
  (ctx) => generateDuckSpeech(ctx, container.provider, container.config.models.fast),
10147
11692
  [container]
10148
11693
  );
11694
+ const generateOmen = useCallback3(
11695
+ (ctx) => generateDuckOmen(ctx, container.provider, container.config.models.fast),
11696
+ [container]
11697
+ );
11698
+ const runConversationTurn = async (userText, effectiveText, documentExport, toolPolicy) => {
11699
+ const artifactMode = Boolean(documentExport) || isLikelyArtifactRequest(userText);
11700
+ setError(null);
11701
+ setPendingUserMessage(userText);
11702
+ setIsStreaming(true);
11703
+ setStreamText("");
11704
+ setToolEvents([]);
11705
+ if (documentExport) {
11706
+ setCommandProgress({
11707
+ command: "/document",
11708
+ detail: `building ${documentExport.kind.replace(/-/g, " ")}\u2026`
11709
+ });
11710
+ }
11711
+ setLastThinkingMs(null);
11712
+ streamingStartRef.current = Date.now();
11713
+ const controller = new AbortController();
11714
+ abortRef.current = controller;
11715
+ let accumulated = "";
11716
+ let turnSucceeded = false;
11717
+ let outgoingText = effectiveText ?? userText;
11718
+ if (attachedContext) {
11719
+ outgoingText = attachedContext + "\n\n" + outgoingText;
11720
+ setAttachedContext(null);
11721
+ }
11722
+ try {
11723
+ for await (const delta of engine.sendMessage(userText, controller.signal, {
11724
+ effectiveText: outgoingText,
11725
+ ...toolPolicy ? { toolPolicy } : {}
11726
+ })) {
11727
+ if (delta.type === "text") {
11728
+ accumulated += delta.text;
11729
+ if (!artifactMode) setStreamText(accumulated);
11730
+ }
11731
+ if (delta.type === "tool_use") {
11732
+ logRuntimeEvent("tool.called", delta.name);
11733
+ setToolEvents((prev) => [...prev, {
11734
+ type: "tool_use",
11735
+ name: delta.name,
11736
+ detail: summarizeToolInput(delta.name, delta.input)
11737
+ }]);
11738
+ accumulated = "";
11739
+ setStreamText("");
11740
+ }
11741
+ if (delta.type === "tool_result") {
11742
+ logRuntimeEvent(delta.isError ? "tool.failed" : "tool.completed", delta.toolName);
11743
+ setToolEvents((prev) => {
11744
+ const updated = [...prev];
11745
+ const last = updated[updated.length - 1];
11746
+ if (last?.name === delta.toolName && last.type === "tool_use") {
11747
+ updated[updated.length - 1] = {
11748
+ type: "tool_result",
11749
+ name: delta.toolName,
11750
+ detail: summarizeToolResult(delta.toolName, delta.content),
11751
+ isError: delta.isError
11752
+ };
11753
+ }
11754
+ return updated;
11755
+ });
11756
+ }
11757
+ if (delta.type === "usage") {
11758
+ setInputTokens((prev) => prev + delta.inputTokens);
11759
+ setOutputTokens((prev) => prev + delta.outputTokens);
11760
+ }
11761
+ }
11762
+ turnSucceeded = true;
11763
+ } catch (err) {
11764
+ if (err instanceof Error && err.name !== "AbortError") {
11765
+ const msg = err.message.toLowerCase();
11766
+ const isNetworkError = msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("fetch failed") || msg.includes("network") || msg.includes("timeout") || msg.includes("econnreset");
11767
+ if (isNetworkError) {
11768
+ logRuntimeEvent("stream.offline", err.message);
11769
+ setIsOffline(true);
11770
+ setError(null);
11771
+ } else {
11772
+ logRuntimeEvent("stream.error", err.message);
11773
+ setIsOffline(false);
11774
+ setError(err.message);
11775
+ }
11776
+ }
11777
+ } finally {
11778
+ setStreamText("");
11779
+ setToolEvents([]);
11780
+ setIsStreaming(false);
11781
+ if (documentExport) setCommandProgress(null);
11782
+ setIsOffline(false);
11783
+ setPendingUserMessage(null);
11784
+ abortRef.current = null;
11785
+ const finalHistory = engine.getHistory();
11786
+ const elapsed = Date.now() - streamingStartRef.current;
11787
+ setLastThinkingMs(elapsed);
11788
+ setTimeout(() => setLastThinkingMs(null), 3e3);
11789
+ setMessageCount((prev) => prev + 1);
11790
+ if (turnSucceeded && documentExport) {
11791
+ const lastAssistant = [...finalHistory].reverse().find((message) => message.role === "assistant");
11792
+ const markdown = lastAssistant ? messageText(lastAssistant.content) : "";
11793
+ if (markdown.trim()) {
11794
+ try {
11795
+ const exported = exportGeneratedDocument(markdown, documentExport);
11796
+ logRuntimeEvent("document.exported", `${documentExport.kind} ${exported.path}`);
11797
+ const renderedHistory = finalHistory.map((message) => ({ ...message }));
11798
+ for (let i = renderedHistory.length - 1; i >= 0; i--) {
11799
+ if (renderedHistory[i].role === "assistant") {
11800
+ renderedHistory[i].displayText = formatDocumentSuccessMessage(documentExport.kind, exported);
11801
+ break;
11802
+ }
11803
+ }
11804
+ setMessages(renderedHistory);
11805
+ showNotice(formatDocumentReadyNotice(documentExport.kind, exported), 5e3);
11806
+ } catch (err) {
11807
+ if (isCapabilityError(err)) {
11808
+ logRuntimeEvent("document.export_failed", err.issue.id);
11809
+ const renderedHistory = finalHistory.map((message) => ({ ...message }));
11810
+ for (let i = renderedHistory.length - 1; i >= 0; i--) {
11811
+ if (renderedHistory[i].role === "assistant") {
11812
+ renderedHistory[i].displayText = `finished the ${documentExport.kind.replace(/-/g, " ")}, but export needs extra setup`;
11813
+ break;
11814
+ }
11815
+ }
11816
+ setMessages(renderedHistory);
11817
+ pendingCapabilityActionRef.current = {
11818
+ retry: () => {
11819
+ const exported = exportGeneratedDocument(markdown, documentExport);
11820
+ logRuntimeEvent("document.exported", `${documentExport.kind} ${exported.path}`);
11821
+ const resumedHistory = finalHistory.map((message) => ({ ...message }));
11822
+ for (let i = resumedHistory.length - 1; i >= 0; i--) {
11823
+ if (resumedHistory[i].role === "assistant") {
11824
+ resumedHistory[i].displayText = formatDocumentSuccessMessage(documentExport.kind, exported);
11825
+ break;
11826
+ }
11827
+ }
11828
+ setMessages(resumedHistory);
11829
+ showNotice(formatDocumentReadyNotice(documentExport.kind, exported), 5e3);
11830
+ }
11831
+ };
11832
+ setCapabilityPrompt(err.issue);
11833
+ } else {
11834
+ const msg = err instanceof Error ? err.message : String(err);
11835
+ const shouldAttemptRepair = /pdflatex|latex|undefined control sequence|missing \\item|runaway argument|emergency stop/i.test(msg);
11836
+ if (shouldAttemptRepair) {
11837
+ setCommandProgress({
11838
+ command: "/document",
11839
+ detail: "repairing latex after compile failure\u2026"
11840
+ });
11841
+ try {
11842
+ const repairedLatex = await repairLatexDocument(container, markdown, msg, documentExport.topic);
11843
+ const exported = exportGeneratedDocument(repairedLatex, documentExport);
11844
+ logRuntimeEvent("document.exported", `${documentExport.kind} ${exported.path}`);
11845
+ const renderedHistory = finalHistory.map((message) => ({ ...message }));
11846
+ for (let i = renderedHistory.length - 1; i >= 0; i--) {
11847
+ if (renderedHistory[i].role === "assistant") {
11848
+ renderedHistory[i].content = repairedLatex;
11849
+ renderedHistory[i].displayText = formatDocumentSuccessMessage(documentExport.kind, exported);
11850
+ break;
11851
+ }
11852
+ }
11853
+ setMessages(renderedHistory);
11854
+ showNotice(formatDocumentReadyNotice(documentExport.kind, exported), 5e3);
11855
+ } catch (repairErr) {
11856
+ const repairMsg = repairErr instanceof Error ? repairErr.message : String(repairErr);
11857
+ logRuntimeEvent("document.export_failed", repairMsg);
11858
+ setMessages(finalHistory);
11859
+ setInfoPanel({
11860
+ title: "document export",
11861
+ body: `generated the document, but could not save it automatically:
11862
+
11863
+ ${msg}
11864
+
11865
+ repair attempt also failed:
11866
+
11867
+ ${repairMsg}`
11868
+ });
11869
+ } finally {
11870
+ setCommandProgress(null);
11871
+ }
11872
+ } else {
11873
+ logRuntimeEvent("document.export_failed", msg);
11874
+ setMessages(finalHistory);
11875
+ setInfoPanel({
11876
+ title: "document export",
11877
+ body: `generated the document, but could not save it automatically:
11878
+
11879
+ ${msg}`
11880
+ });
11881
+ }
11882
+ }
11883
+ }
11884
+ } else {
11885
+ setMessages(finalHistory);
11886
+ }
11887
+ } else if (turnSucceeded && artifactMode) {
11888
+ const latestTask = container.actionTasks.getLatestTask();
11889
+ const summary = latestTask && latestTask.prompt === userText ? summarizeArtifactTask(latestTask) : null;
11890
+ if (summary) {
11891
+ const renderedHistory = finalHistory.map((message) => ({ ...message }));
11892
+ for (let i = renderedHistory.length - 1; i >= 0; i--) {
11893
+ if (renderedHistory[i].role === "assistant") {
11894
+ renderedHistory[i].displayText = summary;
11895
+ break;
11896
+ }
11897
+ }
11898
+ setMessages(renderedHistory);
11899
+ } else {
11900
+ setMessages(finalHistory);
11901
+ }
11902
+ } else {
11903
+ setMessages(finalHistory);
11904
+ }
11905
+ const queued = queuedMessageRef.current;
11906
+ if (queued) {
11907
+ queuedMessageRef.current = null;
11908
+ setQueuedMessage(null);
11909
+ setTimeout(() => void handleSubmit(queued), 50);
11910
+ }
11911
+ }
11912
+ };
10149
11913
  const handleSubmit = async (text2) => {
10150
11914
  const trimmed = text2.trim();
10151
11915
  if (!trimmed) return;
@@ -10185,10 +11949,10 @@ function App({ engine, container }) {
10185
11949
  setIsStreaming(true);
10186
11950
  setStreamText("");
10187
11951
  setPendingUserMessage("/compact");
10188
- const controller2 = new AbortController();
10189
- abortRef.current = controller2;
11952
+ const controller = new AbortController();
11953
+ abortRef.current = controller;
10190
11954
  try {
10191
- const { summaryText, turnsCompacted } = await engine.compact(controller2.signal);
11955
+ const { summaryText, turnsCompacted } = await engine.compact(controller.signal);
10192
11956
  if (summaryText) {
10193
11957
  setMessages(engine.getHistory());
10194
11958
  setStreamText(`compacted ${turnsCompacted} turns into summary`);
@@ -10218,14 +11982,14 @@ function App({ engine, container }) {
10218
11982
  }
10219
11983
  if (cmdResult.edit) {
10220
11984
  const os5 = await import("os");
10221
- const path21 = await import("path");
10222
- const fs14 = await import("fs");
10223
- const { spawnSync: spawnSync10 } = await import("child_process");
10224
- const tmp = path21.join(os5.tmpdir(), `zencefyl-edit-${Date.now()}.md`);
10225
- fs14.writeFileSync(tmp, inputBuffer, "utf8");
10226
- spawnSync10(process.env["EDITOR"] ?? "nano", [tmp], { stdio: "inherit" });
10227
- const content = fs14.readFileSync(tmp, "utf8").trim();
10228
- fs14.unlinkSync(tmp);
11985
+ const path22 = await import("path");
11986
+ const fs15 = await import("fs");
11987
+ const { spawnSync: spawnSync12 } = await import("child_process");
11988
+ const tmp = path22.join(os5.tmpdir(), `zencefyl-edit-${Date.now()}.md`);
11989
+ fs15.writeFileSync(tmp, inputBuffer, "utf8");
11990
+ spawnSync12(process.env["EDITOR"] ?? "nano", [tmp], { stdio: "inherit" });
11991
+ const content = fs15.readFileSync(tmp, "utf8").trim();
11992
+ fs15.unlinkSync(tmp);
10229
11993
  if (content) setInputBuffer(content);
10230
11994
  return;
10231
11995
  }
@@ -10361,94 +12125,26 @@ function App({ engine, container }) {
10361
12125
  }
10362
12126
  return;
10363
12127
  }
10364
- setError(null);
10365
- setPendingUserMessage(trimmed);
10366
- setIsStreaming(true);
10367
- setStreamText("");
10368
- setToolEvents([]);
10369
- setLastThinkingMs(null);
10370
- streamingStartRef.current = Date.now();
10371
- const controller = new AbortController();
10372
- abortRef.current = controller;
10373
- let accumulated = "";
10374
- let outgoingText = trimmed;
10375
- if (attachedContext) {
10376
- outgoingText = attachedContext + "\n\n" + trimmed;
10377
- setAttachedContext(null);
10378
- }
10379
- try {
10380
- for await (const delta of engine.sendMessage(outgoingText, controller.signal)) {
10381
- if (delta.type === "text") {
10382
- accumulated += delta.text;
10383
- setStreamText(accumulated);
10384
- }
10385
- if (delta.type === "tool_use") {
10386
- logRuntimeEvent("tool.called", delta.name);
10387
- setToolEvents((prev) => [...prev, {
10388
- type: "tool_use",
10389
- name: delta.name,
10390
- detail: summarizeToolInput(delta.name, delta.input)
10391
- }]);
10392
- accumulated = "";
10393
- setStreamText("");
10394
- }
10395
- if (delta.type === "tool_result") {
10396
- logRuntimeEvent(delta.isError ? "tool.failed" : "tool.completed", delta.toolName);
10397
- setToolEvents((prev) => {
10398
- const updated = [...prev];
10399
- const last = updated[updated.length - 1];
10400
- if (last?.name === delta.toolName && last.type === "tool_use") {
10401
- updated[updated.length - 1] = {
10402
- type: "tool_result",
10403
- name: delta.toolName,
10404
- detail: summarizeToolResult(delta.content),
10405
- isError: delta.isError
10406
- };
10407
- }
10408
- return updated;
10409
- });
10410
- }
10411
- if (delta.type === "usage") {
10412
- setInputTokens((prev) => prev + delta.inputTokens);
10413
- setOutputTokens((prev) => prev + delta.outputTokens);
10414
- }
10415
- }
10416
- } catch (err) {
10417
- if (err instanceof Error && err.name !== "AbortError") {
10418
- const msg = err.message.toLowerCase();
10419
- const isNetworkError = msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("fetch failed") || msg.includes("network") || msg.includes("timeout") || msg.includes("econnreset");
10420
- if (isNetworkError) {
10421
- logRuntimeEvent("stream.offline", err.message);
10422
- setIsOffline(true);
10423
- setError(null);
10424
- } else {
10425
- logRuntimeEvent("stream.error", err.message);
10426
- setIsOffline(false);
10427
- setError(err.message);
10428
- }
10429
- }
10430
- } finally {
10431
- setStreamText("");
10432
- setToolEvents([]);
10433
- setIsStreaming(false);
10434
- setIsOffline(false);
10435
- setPendingUserMessage(null);
10436
- abortRef.current = null;
10437
- setMessages(engine.getHistory());
10438
- const elapsed = Date.now() - streamingStartRef.current;
10439
- setLastThinkingMs(elapsed);
10440
- setTimeout(() => setLastThinkingMs(null), 3e3);
10441
- setMessageCount((prev) => prev + 1);
10442
- const queued = queuedMessageRef.current;
10443
- if (queued) {
10444
- queuedMessageRef.current = null;
10445
- setQueuedMessage(null);
10446
- setTimeout(() => void handleSubmit(queued), 50);
12128
+ const documentIntent = detectDocumentIntent(trimmed);
12129
+ if (documentIntent) {
12130
+ setInputBuffer("");
12131
+ if (documentIntent.entryMode === "studio") {
12132
+ setDocumentStudioIntent(documentIntent);
12133
+ } else if (documentIntent.entryMode === "confirm") {
12134
+ setDocumentIntentPrompt(documentIntent);
12135
+ } else {
12136
+ await runConversationTurn(
12137
+ trimmed,
12138
+ buildDocumentGenerationPrompt(container, documentIntent),
12139
+ { kind: documentIntent.kind, topic: documentIntent.topic }
12140
+ );
10447
12141
  }
12142
+ return;
10448
12143
  }
12144
+ await runConversationTurn(trimmed);
10449
12145
  };
10450
12146
  const pickerOpenRef = useRef3(false);
10451
- const { text: inputBuffer, cursorOffset, setText: setInputBuffer } = useInputState({
12147
+ const { text: inputBuffer, cursorOffset, collapsedInsert, setText: setInputBuffer } = useInputState({
10452
12148
  onSubmit: handleSubmit,
10453
12149
  onExit: () => {
10454
12150
  abortRef.current?.abort();
@@ -10474,9 +12170,9 @@ function App({ engine, container }) {
10474
12170
  onClearScreen: handleClearScreen,
10475
12171
  isSearchOpen: searchOpen,
10476
12172
  isPickerOpen: pickerOpenRef,
10477
- isModelPickerOpen: modelPickerOpen || settingsOpen || infoPanel !== null || commandProgress !== null || forgetPanel !== null || prunePanel !== null || reviewPanel !== null || pendingToolApproval !== null
12173
+ isModelPickerOpen: modelPickerOpen || settingsOpen || infoPanel !== null || commandProgress !== null || forgetPanel !== null || prunePanel !== null || reviewPanel !== null || documentIntentPrompt !== null || documentStudioIntent !== null || capabilityPrompt !== null || pendingToolApproval !== null
10478
12174
  });
10479
- const pickerActive = !isStreaming && !searchOpen && !modelPickerOpen && !settingsOpen && !infoPanel && !commandProgress && !forgetPanel && !prunePanel && !reviewPanel && !pendingToolApproval && inputBuffer.startsWith("/") && !inputBuffer.includes(" ") && inputBuffer.length <= 20;
12175
+ const pickerActive = !isStreaming && !searchOpen && !modelPickerOpen && !settingsOpen && !infoPanel && !commandProgress && !forgetPanel && !prunePanel && !reviewPanel && !documentIntentPrompt && !documentStudioIntent && !capabilityPrompt && !pendingToolApproval && inputBuffer.startsWith("/") && !inputBuffer.includes(" ") && inputBuffer.length <= 20;
10480
12176
  const pickerQuery = pickerActive ? inputBuffer.slice(1) : "";
10481
12177
  pickerOpenRef.current = pickerActive;
10482
12178
  function handleHistorySearch() {
@@ -10520,57 +12216,152 @@ function App({ engine, container }) {
10520
12216
  const patch = computeFSRSUpdateFromRating(topic, rating);
10521
12217
  if (patch) container.store.updateTopic(topicId, patch);
10522
12218
  }
10523
- return /* @__PURE__ */ jsx16(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsxs15(Fragment3, { children: [
10524
- updateAvailable && /* @__PURE__ */ jsxs15(Box16, { marginBottom: 1, children: [
10525
- /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
10526
- "update available: v",
10527
- updateAvailable,
10528
- " \xB7 "
10529
- ] }),
10530
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "npm install -g zencefyl@latest" })
10531
- ] }),
10532
- /* @__PURE__ */ jsx16(Static, { items: messages, children: (msg, i) => /* @__PURE__ */ jsx16(MessageComponent, { message: msg }, i) }),
10533
- isStreaming && pendingUserMessage && /* @__PURE__ */ jsxs15(Box16, { marginBottom: 1, children: [
10534
- /* @__PURE__ */ jsx16(Text16, { color: "#FCD34D", bold: true, children: "you" }),
10535
- /* @__PURE__ */ jsx16(Text16, { children: " " }),
10536
- /* @__PURE__ */ jsx16(Text16, { children: pendingUserMessage })
12219
+ function handleDocumentIntentResolve(choice) {
12220
+ const intent = documentIntentPrompt;
12221
+ setDocumentIntentPrompt(null);
12222
+ if (!intent) return;
12223
+ if (choice === "studio") {
12224
+ setDocumentStudioIntent(intent);
12225
+ return;
12226
+ }
12227
+ if (choice === "generate") {
12228
+ void runConversationTurn(
12229
+ intent.originalText,
12230
+ buildDocumentGenerationPrompt(container, intent),
12231
+ { kind: intent.kind, topic: intent.topic },
12232
+ intent.useWorkingDirectoryResources ? "document-sources" : "none"
12233
+ );
12234
+ }
12235
+ }
12236
+ function handleDocumentStudioGenerate(config) {
12237
+ const intent = documentStudioIntent;
12238
+ setDocumentStudioIntent(null);
12239
+ if (!intent) return;
12240
+ void runConversationTurn(
12241
+ intent.originalText,
12242
+ buildDocumentGenerationPrompt(container, intent, config),
12243
+ { kind: config.kind, topic: intent.topic },
12244
+ config.useWorkingDirectoryResources ? "document-sources" : "none"
12245
+ );
12246
+ }
12247
+ function handleCapabilityPromptResolve(choice) {
12248
+ const issue = capabilityPrompt;
12249
+ setCapabilityPrompt(null);
12250
+ if (!issue) return;
12251
+ if (choice === "install") {
12252
+ if (!issue.canAutoInstall || !issue.autoInstallCommand) {
12253
+ showNotice("that capability needs manual setup on this machine", 4500);
12254
+ return;
12255
+ }
12256
+ setCommandProgress({
12257
+ command: "/setup",
12258
+ detail: `installing ${issue.autoInstallCommand.command} support\u2026`
12259
+ });
12260
+ const result = executeCapabilityAutoInstall(issue);
12261
+ setCommandProgress(null);
12262
+ if (!result.ok) {
12263
+ const output = result.output;
12264
+ setInfoPanel({
12265
+ title: issue.title,
12266
+ body: [
12267
+ "automatic setup failed.",
12268
+ "",
12269
+ issue.recommendation,
12270
+ "",
12271
+ ...issue.installSteps.map((step) => `- ${step}`),
12272
+ ...output ? ["", "installer output:", "", output] : []
12273
+ ].join("\n")
12274
+ });
12275
+ return;
12276
+ }
12277
+ showNotice(`${issue.autoInstallCommand.label.toLowerCase()} completed`, 4500);
12278
+ const pendingAction = pendingCapabilityActionRef.current;
12279
+ if (pendingAction) {
12280
+ pendingCapabilityActionRef.current = null;
12281
+ try {
12282
+ pendingAction.retry();
12283
+ } catch (err) {
12284
+ if (isCapabilityError(err)) {
12285
+ logRuntimeEvent("document.export_failed", err.issue.id);
12286
+ pendingCapabilityActionRef.current = pendingAction;
12287
+ setCapabilityPrompt(err.issue);
12288
+ } else {
12289
+ const msg = err instanceof Error ? err.message : String(err);
12290
+ logRuntimeEvent("document.export_failed", msg);
12291
+ setInfoPanel({
12292
+ title: "document export",
12293
+ body: `setup completed, but export still failed:
12294
+
12295
+ ${msg}`
12296
+ });
12297
+ }
12298
+ }
12299
+ }
12300
+ return;
12301
+ }
12302
+ if (choice === "guidance") {
12303
+ setInfoPanel({
12304
+ title: issue.title,
12305
+ body: [
12306
+ issue.reason,
12307
+ "",
12308
+ issue.recommendation,
12309
+ "",
12310
+ ...issue.installSteps.map((step) => `- ${step}`)
12311
+ ].join("\n")
12312
+ });
12313
+ return;
12314
+ }
12315
+ if (choice === "continue") {
12316
+ pendingCapabilityActionRef.current = null;
12317
+ showNotice(issue.continueLabel ?? "continuing without that capability", 4500);
12318
+ return;
12319
+ }
12320
+ pendingCapabilityActionRef.current = null;
12321
+ }
12322
+ return /* @__PURE__ */ jsx21(Box17, { flexDirection: "column", children: /* @__PURE__ */ jsxs16(Fragment4, { children: [
12323
+ /* @__PURE__ */ jsx21(Static, { items: messages, children: (msg, i) => /* @__PURE__ */ jsx21(MessageComponent, { message: msg }, i) }),
12324
+ isStreaming && pendingUserMessage && /* @__PURE__ */ jsxs16(Box17, { marginBottom: 1, children: [
12325
+ /* @__PURE__ */ jsx21(Text17, { color: "#FCD34D", bold: true, children: "you" }),
12326
+ /* @__PURE__ */ jsx21(Text17, { children: " " }),
12327
+ /* @__PURE__ */ jsx21(Text17, { children: pendingUserMessage })
10537
12328
  ] }),
10538
- isStreaming && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
10539
- /* @__PURE__ */ jsxs15(Box16, { children: [
10540
- /* @__PURE__ */ jsx16(Text16, { color: "#A78BFA", bold: true, children: "zencefyl" }),
10541
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: ` (${session.model})` })
12329
+ isStreaming && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginBottom: 1, children: [
12330
+ /* @__PURE__ */ jsxs16(Box17, { children: [
12331
+ /* @__PURE__ */ jsx21(Text17, { color: "#A78BFA", bold: true, children: "zencefyl" }),
12332
+ /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: ` (${session.model})` })
10542
12333
  ] }),
10543
- toolEvents.map((ev, i) => /* @__PURE__ */ jsxs15(Box16, { marginLeft: 2, flexDirection: "column", children: [
10544
- ev.type === "tool_use" && /* @__PURE__ */ jsxs15(Fragment3, { children: [
10545
- /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
12334
+ toolEvents.map((ev, i) => /* @__PURE__ */ jsxs16(Box17, { marginLeft: 2, flexDirection: "column", children: [
12335
+ ev.type === "tool_use" && /* @__PURE__ */ jsxs16(Fragment4, { children: [
12336
+ /* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
10546
12337
  "[",
10547
12338
  toolLabel(ev.name),
10548
12339
  "]"
10549
12340
  ] }),
10550
- ev.detail ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: ` ${ev.detail}` }) : null
12341
+ ev.detail ? /* @__PURE__ */ jsx21(Box17, { marginLeft: 2, children: /* @__PURE__ */ jsx21(Markdown, { dim: true, children: ev.detail }) }) : null
10551
12342
  ] }),
10552
- ev.type === "tool_result" && /* @__PURE__ */ jsxs15(Fragment3, { children: [
10553
- /* @__PURE__ */ jsxs15(Text16, { color: ev.isError ? "red" : "green", dimColor: true, children: [
12343
+ ev.type === "tool_result" && /* @__PURE__ */ jsxs16(Fragment4, { children: [
12344
+ /* @__PURE__ */ jsxs16(Text17, { color: ev.isError ? "red" : "green", dimColor: true, children: [
10554
12345
  "[",
10555
12346
  toolLabel(ev.name),
10556
12347
  " \u2713]"
10557
12348
  ] }),
10558
- ev.detail ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: ` ${ev.detail}` }) : null
12349
+ ev.detail ? /* @__PURE__ */ jsx21(Box17, { marginLeft: 2, children: /* @__PURE__ */ jsx21(Markdown, { dim: true, children: ev.detail }) }) : null
10559
12350
  ] })
10560
12351
  ] }, i)),
10561
- /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, children: streamText ? /* @__PURE__ */ jsx16(Markdown, { children: streamText }) : /* @__PURE__ */ jsx16(ThinkingLabel, {}) })
12352
+ /* @__PURE__ */ jsx21(Box17, { marginLeft: 2, children: streamText ? /* @__PURE__ */ jsx21(Markdown, { children: streamText }) : /* @__PURE__ */ jsx21(ThinkingLabel, {}) })
10562
12353
  ] }),
10563
- isOffline && /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "yellow", children: "[offline \u2014 knowledge store active]" }) }),
10564
- error && /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: "red", children: [
12354
+ isOffline && /* @__PURE__ */ jsx21(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsx21(Text17, { color: "yellow", children: "[offline \u2014 knowledge store active]" }) }),
12355
+ error && /* @__PURE__ */ jsx21(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: "red", children: [
10565
12356
  "error: ",
10566
12357
  error
10567
12358
  ] }) }),
10568
- lastThinkingMs !== null && /* @__PURE__ */ jsx16(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
12359
+ lastThinkingMs !== null && /* @__PURE__ */ jsx21(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
10569
12360
  "done in ",
10570
12361
  (lastThinkingMs / 1e3).toFixed(1),
10571
12362
  "s"
10572
12363
  ] }) }),
10573
- pickerActive && /* @__PURE__ */ jsx16(
12364
+ pickerActive && /* @__PURE__ */ jsx21(
10574
12365
  CommandPicker,
10575
12366
  {
10576
12367
  query: pickerQuery,
@@ -10586,13 +12377,25 @@ function App({ engine, container }) {
10586
12377
  }
10587
12378
  }
10588
12379
  ),
10589
- modelPickerOpen && /* @__PURE__ */ jsx16(
12380
+ modelPickerOpen && /* @__PURE__ */ jsx21(
10590
12381
  ModelPicker,
10591
12382
  {
10592
12383
  activeModel: session.model,
10593
12384
  activeProvider: container.config.provider,
10594
12385
  config: container.config,
10595
12386
  onSelect: (modelId) => {
12387
+ const providerRequiresRestart = container.config.provider === "claude-code" && session.model !== modelId && container.provider instanceof ClaudeCodeProvider;
12388
+ if (providerRequiresRestart) {
12389
+ const updatedConfig2 = { ...container.config, models: { ...container.config.models, default: modelId } };
12390
+ saveConfig(updatedConfig2);
12391
+ container.config.models.default = modelId;
12392
+ logRuntimeEvent("model.switched", `provider=${container.config.provider} model=${modelId} restart=required`);
12393
+ setModelPickerOpen(false);
12394
+ showNotice(`switching to ${modelId} and restarting Claude session\u2026`, 4500);
12395
+ requestRestart();
12396
+ exit();
12397
+ return;
12398
+ }
10596
12399
  if (container.config.provider === "ollama" && session.model !== modelId) {
10597
12400
  stopOllamaModel(session.model);
10598
12401
  }
@@ -10633,7 +12436,7 @@ function App({ engine, container }) {
10633
12436
  onDismiss: () => setModelPickerOpen(false)
10634
12437
  }
10635
12438
  ),
10636
- settingsOpen && /* @__PURE__ */ jsx16(
12439
+ settingsOpen && /* @__PURE__ */ jsx21(
10637
12440
  SettingsPanel,
10638
12441
  {
10639
12442
  interactionMode: session.interactionMode,
@@ -10657,7 +12460,40 @@ function App({ engine, container }) {
10657
12460
  onDismiss: () => setSettingsOpen(false)
10658
12461
  }
10659
12462
  ),
10660
- infoPanel && /* @__PURE__ */ jsx16(
12463
+ documentIntentPrompt && /* @__PURE__ */ jsx21(
12464
+ DocumentIntentPrompt,
12465
+ {
12466
+ intent: documentIntentPrompt,
12467
+ onResolve: handleDocumentIntentResolve
12468
+ }
12469
+ ),
12470
+ updateAvailable && /* @__PURE__ */ jsx21(
12471
+ UpdatePrompt,
12472
+ {
12473
+ update: updateAvailable,
12474
+ onResolve: handleStartupUpdateResolve
12475
+ }
12476
+ ),
12477
+ documentStudioIntent && /* @__PURE__ */ jsx21(
12478
+ DocumentStudio,
12479
+ {
12480
+ intent: documentStudioIntent,
12481
+ onGenerate: handleDocumentStudioGenerate,
12482
+ onBack: () => {
12483
+ setDocumentIntentPrompt(documentStudioIntent);
12484
+ setDocumentStudioIntent(null);
12485
+ },
12486
+ onDismiss: () => setDocumentStudioIntent(null)
12487
+ }
12488
+ ),
12489
+ capabilityPrompt && /* @__PURE__ */ jsx21(
12490
+ CapabilityPrompt,
12491
+ {
12492
+ issue: capabilityPrompt,
12493
+ onResolve: handleCapabilityPromptResolve
12494
+ }
12495
+ ),
12496
+ infoPanel && /* @__PURE__ */ jsx21(
10661
12497
  InfoPanel,
10662
12498
  {
10663
12499
  title: infoPanel.title,
@@ -10665,7 +12501,7 @@ function App({ engine, container }) {
10665
12501
  onDismiss: () => setInfoPanel(null)
10666
12502
  }
10667
12503
  ),
10668
- forgetPanel && /* @__PURE__ */ jsx16(
12504
+ forgetPanel && /* @__PURE__ */ jsx21(
10669
12505
  ForgetPanel,
10670
12506
  {
10671
12507
  title: forgetPanel.title,
@@ -10675,7 +12511,7 @@ function App({ engine, container }) {
10675
12511
  onDismiss: () => setForgetPanel(null)
10676
12512
  }
10677
12513
  ),
10678
- prunePanel && /* @__PURE__ */ jsx16(
12514
+ prunePanel && /* @__PURE__ */ jsx21(
10679
12515
  PrunePanel,
10680
12516
  {
10681
12517
  title: prunePanel.title,
@@ -10685,7 +12521,7 @@ function App({ engine, container }) {
10685
12521
  onDismiss: () => setPrunePanel(null)
10686
12522
  }
10687
12523
  ),
10688
- reviewPanel && /* @__PURE__ */ jsx16(
12524
+ reviewPanel && /* @__PURE__ */ jsx21(
10689
12525
  ReviewPanel,
10690
12526
  {
10691
12527
  title: reviewPanel.title,
@@ -10694,36 +12530,36 @@ function App({ engine, container }) {
10694
12530
  onDismiss: () => setReviewPanel(null)
10695
12531
  }
10696
12532
  ),
10697
- commandProgress && /* @__PURE__ */ jsx16(
12533
+ commandProgress && /* @__PURE__ */ jsx21(
10698
12534
  CommandProgress,
10699
12535
  {
10700
12536
  command: commandProgress.command,
10701
12537
  detail: commandProgress.detail
10702
12538
  }
10703
12539
  ),
10704
- pendingToolApproval && /* @__PURE__ */ jsx16(
12540
+ pendingToolApproval && /* @__PURE__ */ jsx21(
10705
12541
  ToolApproval,
10706
12542
  {
10707
12543
  request: pendingToolApproval,
10708
12544
  onResolve: resolveToolApproval
10709
12545
  }
10710
12546
  ),
10711
- backgroundJobs.filter((job) => job.status === "running").length > 0 && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
10712
- /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { color: "#A78BFA", bold: true, children: " background jobs" }) }),
10713
- backgroundJobs.filter((job) => job.status === "running").slice(-3).map((job) => /* @__PURE__ */ jsx16(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: `[${formatBackgroundJobType(job.type)}] ${job.detail}` }) }, job.id))
12547
+ backgroundJobs.filter((job) => job.status === "running").length > 0 && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginBottom: 1, children: [
12548
+ /* @__PURE__ */ jsx21(Box17, { children: /* @__PURE__ */ jsx21(Text17, { color: "#A78BFA", bold: true, children: " background jobs" }) }),
12549
+ backgroundJobs.filter((job) => job.status === "running").slice(-3).map((job) => /* @__PURE__ */ jsx21(Box17, { marginLeft: 2, children: /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: `[${formatBackgroundJobType(job.type)}] ${job.detail}` }) }, job.id))
10714
12550
  ] }),
10715
- actionTasks.filter((task) => task.status === "running").length > 0 && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginBottom: 1, children: [
10716
- /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { color: "#FCD34D", bold: true, children: " active task" }) }),
10717
- actionTasks.filter((task) => task.status === "running").slice(-1).map((task) => /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginLeft: 2, children: [
10718
- /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: formatActionTaskTitle(task) }) }),
10719
- /* @__PURE__ */ jsxs15(Box16, { children: [
10720
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: task.phase }),
10721
- task.detail ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: ` \xB7 ${task.detail}` }) : null
12551
+ actionTasks.filter((task) => task.status === "running").length > 0 && /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginBottom: 1, children: [
12552
+ /* @__PURE__ */ jsx21(Box17, { children: /* @__PURE__ */ jsx21(Text17, { color: "#FCD34D", bold: true, children: " active task" }) }),
12553
+ actionTasks.filter((task) => task.status === "running").slice(-1).map((task) => /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginLeft: 2, children: [
12554
+ /* @__PURE__ */ jsx21(Box17, { children: /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: formatActionTaskTitle(task) }) }),
12555
+ /* @__PURE__ */ jsxs16(Box17, { children: [
12556
+ /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: task.phase }),
12557
+ task.detail ? /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: ` \xB7 ${task.detail}` }) : null
10722
12558
  ] }),
10723
- task.commandsRun.length > 0 ? /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: `cmd: ${task.commandsRun[task.commandsRun.length - 1]}` }) }) : task.filesTouched.length > 0 ? /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: `file: ${task.filesTouched[task.filesTouched.length - 1]}` }) }) : null
12559
+ task.commandsRun.length > 0 ? /* @__PURE__ */ jsx21(Box17, { children: /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: `cmd: ${task.commandsRun[task.commandsRun.length - 1]}` }) }) : task.filesTouched.length > 0 ? /* @__PURE__ */ jsx21(Box17, { children: /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: `file: ${task.filesTouched[task.filesTouched.length - 1]}` }) }) : null
10724
12560
  ] }, task.id))
10725
12561
  ] }),
10726
- searchOpen && /* @__PURE__ */ jsx16(
12562
+ searchOpen && /* @__PURE__ */ jsx21(
10727
12563
  HistorySearch,
10728
12564
  {
10729
12565
  history: inputHistory,
@@ -10733,39 +12569,40 @@ function App({ engine, container }) {
10733
12569
  ),
10734
12570
  (() => {
10735
12571
  const width = process.stdout.columns ?? 80;
10736
- const lines = inputBuffer.split("\n");
10737
- const before = inputBuffer.slice(0, cursorOffset);
10738
- const cursorChar = inputBuffer[cursorOffset] ?? " ";
12572
+ const visibleInput = collapseInsertedChunk(inputBuffer, cursorOffset, collapsedInsert);
12573
+ const lines = visibleInput.text.split("\n");
12574
+ const before = visibleInput.text.slice(0, visibleInput.cursorOffset);
12575
+ const cursorChar = visibleInput.text[visibleInput.cursorOffset] ?? " ";
10739
12576
  const beforeLines = before.split("\n");
10740
12577
  const cursorLine = beforeLines.length - 1;
10741
12578
  const cursorColumn = beforeLines[cursorLine]?.length ?? 0;
10742
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
10743
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u2500".repeat(width) }),
10744
- lines.map((_, i) => /* @__PURE__ */ jsxs15(Box16, { children: [
10745
- /* @__PURE__ */ jsx16(Text16, { color: isStreaming ? "#6D28D9" : "#FCD34D", bold: true, children: i === 0 ? "\u276F " : " " }),
10746
- i === cursorLine ? /* @__PURE__ */ jsxs15(Fragment3, { children: [
10747
- /* @__PURE__ */ jsx16(Text16, { dimColor: isStreaming, children: lines[i].slice(0, cursorColumn) }),
10748
- !isStreaming && /* @__PURE__ */ jsx16(Text16, { inverse: true, children: cursorChar }),
10749
- isStreaming && /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: cursorChar }),
10750
- /* @__PURE__ */ jsx16(Text16, { dimColor: isStreaming, children: lines[i].slice(cursorColumn + 1) })
10751
- ] }) : /* @__PURE__ */ jsx16(Text16, { dimColor: isStreaming, children: lines[i] })
12579
+ return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
12580
+ /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: "\u2500".repeat(width) }),
12581
+ lines.map((_, i) => /* @__PURE__ */ jsxs16(Box17, { children: [
12582
+ /* @__PURE__ */ jsx21(Text17, { color: isStreaming ? "#6D28D9" : "#FCD34D", bold: true, children: i === 0 ? "\u276F " : " " }),
12583
+ i === cursorLine ? /* @__PURE__ */ jsxs16(Fragment4, { children: [
12584
+ /* @__PURE__ */ jsx21(Text17, { dimColor: isStreaming, children: lines[i].slice(0, cursorColumn) }),
12585
+ !isStreaming && /* @__PURE__ */ jsx21(Text17, { inverse: true, children: cursorChar }),
12586
+ isStreaming && /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: cursorChar }),
12587
+ /* @__PURE__ */ jsx21(Text17, { dimColor: isStreaming, children: lines[i].slice(cursorColumn + 1) })
12588
+ ] }) : /* @__PURE__ */ jsx21(Text17, { dimColor: isStreaming, children: lines[i] })
10752
12589
  ] }, i)),
10753
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "\u2500".repeat(width) }),
10754
- notice && /* @__PURE__ */ jsx16(Box16, { flexDirection: "column", children: notice.split("\n").map((line, index) => /* @__PURE__ */ jsxs15(Box16, { children: [
10755
- /* @__PURE__ */ jsx16(Text16, { color: "#6D28D9", children: "\u2502 " }),
10756
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: line })
12590
+ /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: "\u2500".repeat(width) }),
12591
+ notice && /* @__PURE__ */ jsx21(Box17, { flexDirection: "column", children: notice.split("\n").map((line, index) => /* @__PURE__ */ jsxs16(Box17, { children: [
12592
+ /* @__PURE__ */ jsx21(Text17, { color: "#6D28D9", children: "\u2502 " }),
12593
+ /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: line })
10757
12594
  ] }, index)) }),
10758
- isStreaming && /* @__PURE__ */ jsxs15(Box16, { children: [
10759
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "esc to interrupt" }),
10760
- queuedMessage && /* @__PURE__ */ jsxs15(Fragment3, { children: [
10761
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " \xB7 queued: " }),
10762
- /* @__PURE__ */ jsx16(Text16, { color: "#A78BFA", dimColor: true, children: queuedMessage.length > 40 ? queuedMessage.slice(0, 40) + "\u2026" : queuedMessage })
12595
+ isStreaming && /* @__PURE__ */ jsxs16(Box17, { children: [
12596
+ /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: "esc to interrupt" }),
12597
+ queuedMessage && /* @__PURE__ */ jsxs16(Fragment4, { children: [
12598
+ /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: " \xB7 queued: " }),
12599
+ /* @__PURE__ */ jsx21(Text17, { color: "#A78BFA", dimColor: true, children: queuedMessage.length > 40 ? queuedMessage.slice(0, 40) + "\u2026" : queuedMessage })
10763
12600
  ] })
10764
12601
  ] }),
10765
- !isStreaming && /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: process.cwd() })
12602
+ !isStreaming && /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: process.cwd() })
10766
12603
  ] });
10767
12604
  })(),
10768
- /* @__PURE__ */ jsx16(
12605
+ /* @__PURE__ */ jsx21(
10769
12606
  StatusBar,
10770
12607
  {
10771
12608
  sessionSlug: session.sessionSlug,
@@ -10778,7 +12615,7 @@ function App({ engine, container }) {
10778
12615
  budgetUsdLimit: container.config.budgetUsdLimit
10779
12616
  }
10780
12617
  ),
10781
- /* @__PURE__ */ jsx16(Box16, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx16(
12618
+ /* @__PURE__ */ jsx21(Box17, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx21(
10782
12619
  Duck,
10783
12620
  {
10784
12621
  isStreaming,
@@ -10787,7 +12624,9 @@ function App({ engine, container }) {
10787
12624
  messageCount,
10788
12625
  lastAssistantText,
10789
12626
  lastDuckMention,
10790
- generateSpeech
12627
+ generateSpeech,
12628
+ generateOmen,
12629
+ currentPath: process.cwd()
10791
12630
  }
10792
12631
  ) })
10793
12632
  ] }) });
@@ -10814,10 +12653,10 @@ var ELAPSED_SHOW_MS = 3e3;
10814
12653
  var STALL_MS = 15e3;
10815
12654
  function ThinkingLabel() {
10816
12655
  const pool = _verbPool.length > 0 ? _verbPool : ["Thinking"];
10817
- const [verb] = useState14(() => pool[Math.floor(Math.random() * pool.length)]);
12656
+ const [verb] = useState16(() => pool[Math.floor(Math.random() * pool.length)]);
10818
12657
  const startMs = useRef3(Date.now());
10819
- const [, tick] = useState14(0);
10820
- useEffect7(() => {
12658
+ const [, tick] = useState16(0);
12659
+ useEffect8(() => {
10821
12660
  const id = setInterval(() => tick((n) => n + 1), _reducedMotion ? 500 : 40);
10822
12661
  return () => clearInterval(id);
10823
12662
  }, []);
@@ -10825,7 +12664,7 @@ function ThinkingLabel() {
10825
12664
  const isStalled = elapsed >= STALL_MS;
10826
12665
  const elapsedLabel = elapsed >= ELAPSED_SHOW_MS ? ` ${Math.floor(elapsed / 1e3)}s` : "";
10827
12666
  if (_reducedMotion) {
10828
- return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs15(Text16, { color: isStalled ? "yellow" : void 0, dimColor: !isStalled, children: [
12667
+ return /* @__PURE__ */ jsx21(Box17, { children: /* @__PURE__ */ jsxs16(Text17, { color: isStalled ? "yellow" : void 0, dimColor: !isStalled, children: [
10829
12668
  verb,
10830
12669
  "\u2026",
10831
12670
  elapsedLabel
@@ -10835,15 +12674,15 @@ function ThinkingLabel() {
10835
12674
  const shimmerPos = glimmerIndex(Math.floor(elapsed / SHIMMER_MS), verb.length + 1);
10836
12675
  const text2 = verb + "\u2026";
10837
12676
  const { before, shimmer, after } = shimmerSplit(text2, shimmerPos);
10838
- return /* @__PURE__ */ jsxs15(Box16, { children: [
10839
- /* @__PURE__ */ jsxs15(Text16, { color: isStalled ? "yellow" : "green", children: [
12677
+ return /* @__PURE__ */ jsxs16(Box17, { children: [
12678
+ /* @__PURE__ */ jsxs16(Text17, { color: isStalled ? "yellow" : "green", children: [
10840
12679
  spinFrame,
10841
12680
  " "
10842
12681
  ] }),
10843
- /* @__PURE__ */ jsx16(Text16, { color: isStalled ? "yellow" : void 0, dimColor: !isStalled, children: before }),
10844
- shimmer ? /* @__PURE__ */ jsx16(Text16, { color: isStalled ? "yellow" : void 0, children: shimmer }) : null,
10845
- /* @__PURE__ */ jsx16(Text16, { color: isStalled ? "yellow" : void 0, dimColor: !isStalled, children: after }),
10846
- elapsedLabel ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: elapsedLabel }) : null
12682
+ /* @__PURE__ */ jsx21(Text17, { color: isStalled ? "yellow" : void 0, dimColor: !isStalled, children: before }),
12683
+ shimmer ? /* @__PURE__ */ jsx21(Text17, { color: isStalled ? "yellow" : void 0, children: shimmer }) : null,
12684
+ /* @__PURE__ */ jsx21(Text17, { color: isStalled ? "yellow" : void 0, dimColor: !isStalled, children: after }),
12685
+ elapsedLabel ? /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: elapsedLabel }) : null
10847
12686
  ] });
10848
12687
  }
10849
12688
  function toolLabel(name) {
@@ -10917,10 +12756,16 @@ function formatBackgroundJobType(type) {
10917
12756
  return type;
10918
12757
  }
10919
12758
  }
10920
- function summarizeToolResult(content) {
10921
- const line = content.split("\n").map((part) => part.trim()).find(Boolean);
10922
- if (!line) return "";
10923
- return line.length > 88 ? `${line.slice(0, 85).trimEnd()}...` : line;
12759
+ function summarizeToolResult(name, content) {
12760
+ const lines = content.split("\n");
12761
+ const first = lines.map((part) => part.trim()).find(Boolean) ?? "";
12762
+ if (!first) return "";
12763
+ if (name === "write-file" || name === "replace-in-file") {
12764
+ const body = lines.slice(1).filter(Boolean).slice(0, 10).join("\n");
12765
+ return body ? `${first}
12766
+ ${body}` : first;
12767
+ }
12768
+ return first.length > 88 ? `${first.slice(0, 85).trimEnd()}...` : first;
10924
12769
  }
10925
12770
  function formatActionTaskTitle(task) {
10926
12771
  const lower = task.prompt.toLowerCase();
@@ -10980,14 +12825,17 @@ function fetchLatestVersion() {
10980
12825
  });
10981
12826
  }
10982
12827
  function printUpdateBanner(current, latest) {
10983
- const line = "\u2500".repeat(48);
10984
- const cmd = `npm update -g zencefyl`;
12828
+ const line = "\u2550".repeat(62);
12829
+ const cmd = `npm install -g zencefyl@latest`;
12830
+ const note = findUpdateNote(latest);
10985
12831
  process.stdout.write(
10986
12832
  `
10987
- ${line}
10988
- update available ${current} \u2192 ${latest}
10989
- run to update: ${cmd}
10990
- ${line}
12833
+ ${line}
12834
+ update available ${current} -> ${latest}
12835
+ ` + (note?.title ? ` ${note.title}
12836
+ ` : "") + (note?.message ? ` ${note.message}
12837
+ ` : "") + ` run to update: ${cmd}
12838
+ ${line}
10991
12839
 
10992
12840
  `
10993
12841
  );
@@ -11351,17 +13199,27 @@ ${message}
11351
13199
  }
11352
13200
  const { waitUntilExit } = render(createElement(App, { engine, container }));
11353
13201
  await waitUntilExit();
13202
+ if (isUpdateRequested()) {
13203
+ const { spawnSync: spawnSync12 } = await import("child_process");
13204
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
13205
+ const install = spawnSync12(npmCmd, ["install", "-g", "zencefyl@latest"], { stdio: "inherit" });
13206
+ if ((install.status ?? 1) !== 0) {
13207
+ process.exit(install.status ?? 1);
13208
+ }
13209
+ const result = spawnSync12(process.execPath, process.argv.slice(1), { stdio: "inherit" });
13210
+ process.exit(result.status ?? 0);
13211
+ }
11354
13212
  if (isRestartRequested()) {
11355
- const { spawnSync: spawnSync10 } = await import("child_process");
11356
- const result = spawnSync10(process.execPath, process.argv.slice(1), { stdio: "inherit" });
13213
+ const { spawnSync: spawnSync12 } = await import("child_process");
13214
+ const result = spawnSync12(process.execPath, process.argv.slice(1), { stdio: "inherit" });
11357
13215
  process.exit(result.status ?? 0);
11358
13216
  }
11359
13217
  if (isReauthRequested()) {
11360
13218
  const provider = getReauthProvider();
11361
13219
  const model = getReauthModel();
11362
13220
  await runSetup(provider ?? void 0, model ?? void 0);
11363
- const { spawnSync: spawnSync10 } = await import("child_process");
11364
- const result = spawnSync10(process.execPath, process.argv.slice(1), { stdio: "inherit" });
13221
+ const { spawnSync: spawnSync12 } = await import("child_process");
13222
+ const result = spawnSync12(process.execPath, process.argv.slice(1), { stdio: "inherit" });
11365
13223
  process.exit(result.status ?? 0);
11366
13224
  }
11367
13225
  if (!isHeadless && session.messageCount > 0) {