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 +2302 -444
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
3153
|
-
const sql = readFileSync3(
|
|
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
|
|
3521
|
-
if (!task.filesTouched.includes(
|
|
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.
|
|
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
|
|
4560
|
+
const path22 = parsed.data.path.trim();
|
|
4554
4561
|
const includeEvidence = parsed.data.include_evidence ?? true;
|
|
4555
|
-
if (!
|
|
4562
|
+
if (!path22) {
|
|
4556
4563
|
return { content: "Error: path must not be empty.", isError: true };
|
|
4557
4564
|
}
|
|
4558
|
-
let topic = ctx.store.getTopicByPath(
|
|
4565
|
+
let topic = ctx.store.getTopicByPath(path22);
|
|
4559
4566
|
if (!topic) {
|
|
4560
|
-
const domain2 =
|
|
4567
|
+
const domain2 = path22.split("/")[0] ?? path22;
|
|
4561
4568
|
const allTopics2 = ctx.store.getTopicsByDomain(domain2);
|
|
4562
|
-
const lower =
|
|
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 "${
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
5770
|
-
const baseSystemPrompt = await this.promptBuilder.build(
|
|
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:
|
|
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:
|
|
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 =
|
|
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(
|
|
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
|
|
6344
|
-
import { Box as
|
|
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 (
|
|
8130
|
-
|
|
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
|
-
|
|
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 (
|
|
8330
|
-
|
|
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
|
|
8877
|
-
|
|
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
|
-
|
|
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 {
|
|
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 (
|
|
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
|
|
9220
|
-
const
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
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
|
-
|
|
9768
|
+
moveBy(-1);
|
|
9523
9769
|
return;
|
|
9524
9770
|
}
|
|
9525
9771
|
if (key.downArrow) {
|
|
9526
|
-
|
|
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
|
|
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
|
-
|
|
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 >
|
|
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 +
|
|
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
|
|
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 =
|
|
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
|
|
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 [
|
|
9693
|
-
const [selectedIds, setSelectedIds] = useState11([]);
|
|
10007
|
+
const [selectedIds, setSelectedIds] = useState12([]);
|
|
9694
10008
|
const selectedCount = selectedIds.length;
|
|
9695
|
-
const selectedSet =
|
|
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
|
-
|
|
10023
|
+
moveBy(-1);
|
|
9699
10024
|
return;
|
|
9700
10025
|
}
|
|
9701
10026
|
if (key.downArrow) {
|
|
9702
|
-
|
|
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 =
|
|
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
|
-
|
|
9728
|
-
const active = index ===
|
|
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:
|
|
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
|
|
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 [
|
|
9768
|
-
const
|
|
9769
|
-
const
|
|
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
|
-
|
|
10132
|
+
moveBy(-1);
|
|
9778
10133
|
return;
|
|
9779
10134
|
}
|
|
9780
10135
|
if (key.downArrow) {
|
|
9781
|
-
|
|
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 =
|
|
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
|
-
|
|
9807
|
-
const active =
|
|
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
|
|
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] =
|
|
9848
|
-
const [revealed, setRevealed] =
|
|
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 =
|
|
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/
|
|
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
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
const [
|
|
9948
|
-
const [
|
|
9949
|
-
const [
|
|
9950
|
-
const [
|
|
9951
|
-
const [
|
|
9952
|
-
const [
|
|
9953
|
-
const
|
|
9954
|
-
const
|
|
9955
|
-
const
|
|
9956
|
-
const
|
|
9957
|
-
const
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
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
|
-
|
|
10017
|
-
|
|
10018
|
-
const
|
|
10019
|
-
if (
|
|
10020
|
-
|
|
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
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
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
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
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
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
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 (
|
|
10061
|
-
|
|
10390
|
+
if (key.downArrow || key.tab) {
|
|
10391
|
+
setReviewCursor((current) => (current + 1) % reviewActions.length);
|
|
10392
|
+
return;
|
|
10062
10393
|
}
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
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
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
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 =
|
|
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
|
|
10189
|
-
abortRef.current =
|
|
11952
|
+
const controller = new AbortController();
|
|
11953
|
+
abortRef.current = controller;
|
|
10190
11954
|
try {
|
|
10191
|
-
const { summaryText, turnsCompacted } = await engine.compact(
|
|
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
|
|
10222
|
-
const
|
|
10223
|
-
const { spawnSync:
|
|
10224
|
-
const tmp =
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
const content =
|
|
10228
|
-
|
|
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
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
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
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
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__ */
|
|
10539
|
-
/* @__PURE__ */
|
|
10540
|
-
/* @__PURE__ */
|
|
10541
|
-
/* @__PURE__ */
|
|
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__ */
|
|
10544
|
-
ev.type === "tool_use" && /* @__PURE__ */
|
|
10545
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
10553
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
12352
|
+
/* @__PURE__ */ jsx21(Box17, { marginLeft: 2, children: streamText ? /* @__PURE__ */ jsx21(Markdown, { children: streamText }) : /* @__PURE__ */ jsx21(ThinkingLabel, {}) })
|
|
10562
12353
|
] }),
|
|
10563
|
-
isOffline && /* @__PURE__ */
|
|
10564
|
-
error && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
10712
|
-
/* @__PURE__ */
|
|
10713
|
-
backgroundJobs.filter((job) => job.status === "running").slice(-3).map((job) => /* @__PURE__ */
|
|
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__ */
|
|
10716
|
-
/* @__PURE__ */
|
|
10717
|
-
actionTasks.filter((task) => task.status === "running").slice(-1).map((task) => /* @__PURE__ */
|
|
10718
|
-
/* @__PURE__ */
|
|
10719
|
-
/* @__PURE__ */
|
|
10720
|
-
/* @__PURE__ */
|
|
10721
|
-
task.detail ? /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
10737
|
-
const
|
|
10738
|
-
const
|
|
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__ */
|
|
10743
|
-
/* @__PURE__ */
|
|
10744
|
-
lines.map((_, i) => /* @__PURE__ */
|
|
10745
|
-
/* @__PURE__ */
|
|
10746
|
-
i === cursorLine ? /* @__PURE__ */
|
|
10747
|
-
/* @__PURE__ */
|
|
10748
|
-
!isStreaming && /* @__PURE__ */
|
|
10749
|
-
isStreaming && /* @__PURE__ */
|
|
10750
|
-
/* @__PURE__ */
|
|
10751
|
-
] }) : /* @__PURE__ */
|
|
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__ */
|
|
10754
|
-
notice && /* @__PURE__ */
|
|
10755
|
-
/* @__PURE__ */
|
|
10756
|
-
/* @__PURE__ */
|
|
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__ */
|
|
10759
|
-
/* @__PURE__ */
|
|
10760
|
-
queuedMessage && /* @__PURE__ */
|
|
10761
|
-
/* @__PURE__ */
|
|
10762
|
-
/* @__PURE__ */
|
|
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__ */
|
|
12602
|
+
!isStreaming && /* @__PURE__ */ jsx21(Text17, { dimColor: true, children: process.cwd() })
|
|
10766
12603
|
] });
|
|
10767
12604
|
})(),
|
|
10768
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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] =
|
|
12656
|
+
const [verb] = useState16(() => pool[Math.floor(Math.random() * pool.length)]);
|
|
10818
12657
|
const startMs = useRef3(Date.now());
|
|
10819
|
-
const [, tick] =
|
|
10820
|
-
|
|
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__ */
|
|
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__ */
|
|
10839
|
-
/* @__PURE__ */
|
|
12677
|
+
return /* @__PURE__ */ jsxs16(Box17, { children: [
|
|
12678
|
+
/* @__PURE__ */ jsxs16(Text17, { color: isStalled ? "yellow" : "green", children: [
|
|
10840
12679
|
spinFrame,
|
|
10841
12680
|
" "
|
|
10842
12681
|
] }),
|
|
10843
|
-
/* @__PURE__ */
|
|
10844
|
-
shimmer ? /* @__PURE__ */
|
|
10845
|
-
/* @__PURE__ */
|
|
10846
|
-
elapsedLabel ? /* @__PURE__ */
|
|
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
|
|
10922
|
-
|
|
10923
|
-
|
|
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 = "\
|
|
10984
|
-
const cmd = `npm
|
|
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}
|
|
10989
|
-
|
|
10990
|
-
${
|
|
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:
|
|
11356
|
-
const result =
|
|
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:
|
|
11364
|
-
const result =
|
|
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) {
|