miii-agent 0.1.11 → 0.1.12
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/README.md +27 -1
- package/dist/cli.js +149 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,7 @@ Once inside the TUI, just type naturally:
|
|
|
92
92
|
| `/models` | Switch active Ollama model |
|
|
93
93
|
| `/clear` | Reset conversation history |
|
|
94
94
|
| `Esc` | Stop current generation or tool run |
|
|
95
|
+
| `Ctrl+O` | Toggle full tool output view |
|
|
95
96
|
| `Ctrl+C` | Quit |
|
|
96
97
|
|
|
97
98
|
---
|
|
@@ -124,7 +125,7 @@ miii ships with a built-in tool suite the agent can invoke autonomously:
|
|
|
124
125
|
|------|-------------|
|
|
125
126
|
| `read_file` | Read any file in your workspace |
|
|
126
127
|
| `write_file` | Create new files |
|
|
127
|
-
| `edit_file` | Precise string-level edits (no rewrites) |
|
|
128
|
+
| `edit_file` | Precise string-level edits with whitespace tolerance (no rewrites) |
|
|
128
129
|
| `glob` | Pattern-match files across the project |
|
|
129
130
|
| `grep` | Regex search across files |
|
|
130
131
|
| `run_bash` | Execute shell commands |
|
|
@@ -133,6 +134,24 @@ Every sensitive operation is gated by a permission system — you approve what t
|
|
|
133
134
|
|
|
134
135
|
---
|
|
135
136
|
|
|
137
|
+
## Lossless output spill
|
|
138
|
+
|
|
139
|
+
Big tool outputs used to get truncated — a 50K-line test log chopped to 32K, the middle gone, the model guessing at what it missed. miii doesn't truncate. It **spills**.
|
|
140
|
+
|
|
141
|
+
When a tool result exceeds the inline budget (~10K bytes), the full output is written to `~/.miii/output/<id>.txt`. Only a head + tail **preview** is inlined into the conversation, followed by a pointer:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
[command output truncated: 5184 lines / 412900 bytes.
|
|
145
|
+
Full output at ~/.miii/output/9f3a1c.txt — read it with
|
|
146
|
+
read_file offset/limit to see the elided middle.]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The head shows where output started; the tail catches the errors and summaries that live at the bottom. If the model needs the elided middle, it pages through it with `read_file` ranged reads — nothing is ever lost. The inline budget becomes "how much to show," not "how much exists."
|
|
150
|
+
|
|
151
|
+
Spill files are confined to the app-owned `~/.miii/output` dir and garbage-collected after 24h. If the spill write fails (e.g. read-only home), miii falls back to a lossy head+tail and says so explicitly, so the context window is never blown.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
136
155
|
## Checking your setup
|
|
137
156
|
|
|
138
157
|
miii is model-agnostic — but not every local model can actually drive an agent. A model that can't emit clean tool calls will chat at you instead of editing files. `miii doctor` tells you which of *your* installed models are up to the job, before you waste time wondering why nothing happens.
|
|
@@ -195,6 +214,10 @@ graph TD
|
|
|
195
214
|
ReadFile -.-> Confine["Path Confinement\n(tools/paths.ts)"]
|
|
196
215
|
WriteFile -.-> Confine
|
|
197
216
|
EditFile -.-> Confine
|
|
217
|
+
RunBash -->|"large output"| SpillMod["Output Spill\n(tools/spill.ts)"]
|
|
218
|
+
Grep -->|"large output"| SpillMod
|
|
219
|
+
Glob -->|"large output"| SpillMod
|
|
220
|
+
SpillMod -.->|"head+tail preview\n+ read pointer"| ToolRegistry
|
|
198
221
|
end
|
|
199
222
|
|
|
200
223
|
Adapter -->|"HTTP streaming"| Ollama["Ollama\n(local LLM server)"]
|
|
@@ -206,10 +229,13 @@ graph TD
|
|
|
206
229
|
subgraph Storage ["Local Storage"]
|
|
207
230
|
Config["~/.miii/config.json\n(model, host, effort)"]
|
|
208
231
|
Rules["~/.miii/permissions.json\n(saved allow rules)"]
|
|
232
|
+
Spill["~/.miii/output/\n(spilled tool output)"]
|
|
209
233
|
end
|
|
210
234
|
|
|
211
235
|
App -.->|"reads"| Config
|
|
212
236
|
Policy -.->|"reads / persists 'always'"| Rules
|
|
237
|
+
SpillMod -.->|"writes full output"| Spill
|
|
238
|
+
ReadFile -.->|"pages elided middle\n(offset/limit)"| Spill
|
|
213
239
|
```
|
|
214
240
|
|
|
215
241
|
---
|
package/dist/cli.js
CHANGED
|
@@ -170,6 +170,39 @@ var init_paths = __esm({
|
|
|
170
170
|
}
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
+
// src/tools/verifyHint.ts
|
|
174
|
+
function verifyHint(path) {
|
|
175
|
+
const ext = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
|
|
176
|
+
const cmds = {
|
|
177
|
+
ts: `npx tsc --noEmit`,
|
|
178
|
+
tsx: `npx tsc --noEmit`,
|
|
179
|
+
js: `node --check ${path}`,
|
|
180
|
+
jsx: `node --check ${path}`,
|
|
181
|
+
mjs: `node --check ${path}`,
|
|
182
|
+
cjs: `node --check ${path}`,
|
|
183
|
+
py: `python -m py_compile ${path}`,
|
|
184
|
+
go: `go build ./...`,
|
|
185
|
+
rs: `cargo check`,
|
|
186
|
+
rb: `ruby -c ${path}`,
|
|
187
|
+
php: `php -l ${path}`,
|
|
188
|
+
sh: `bash -n ${path}`,
|
|
189
|
+
bash: `bash -n ${path}`,
|
|
190
|
+
c: `gcc -fsyntax-only ${path}`,
|
|
191
|
+
h: `gcc -fsyntax-only ${path}`,
|
|
192
|
+
cpp: `g++ -fsyntax-only ${path}`,
|
|
193
|
+
cc: `g++ -fsyntax-only ${path}`,
|
|
194
|
+
java: `javac -d /tmp ${path}`
|
|
195
|
+
};
|
|
196
|
+
const cmd2 = cmds[ext];
|
|
197
|
+
if (!cmd2) return "";
|
|
198
|
+
return ` Now verify via run_bash: ${cmd2} \u2014 fix any errors it reports before continuing.`;
|
|
199
|
+
}
|
|
200
|
+
var init_verifyHint = __esm({
|
|
201
|
+
"src/tools/verifyHint.ts"() {
|
|
202
|
+
"use strict";
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
173
206
|
// src/tools/edit_file.ts
|
|
174
207
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
175
208
|
function similarity(a, b) {
|
|
@@ -182,6 +215,35 @@ function similarity(a, b) {
|
|
|
182
215
|
for (let i = 0; i < Math.min(x.length, y.length); i++) if (x[i] === y[i]) same++;
|
|
183
216
|
return same / len;
|
|
184
217
|
}
|
|
218
|
+
function fuzzyRange(src, old_str) {
|
|
219
|
+
const srcLines = src.split("\n");
|
|
220
|
+
const oldLines = old_str.split("\n");
|
|
221
|
+
const norm = (l) => l.trim();
|
|
222
|
+
const oldNorm = oldLines.map(norm);
|
|
223
|
+
const offsets = new Array(srcLines.length);
|
|
224
|
+
let acc = 0;
|
|
225
|
+
for (let i = 0; i < srcLines.length; i++) {
|
|
226
|
+
offsets[i] = acc;
|
|
227
|
+
acc += srcLines[i].length + 1;
|
|
228
|
+
}
|
|
229
|
+
const matches2 = [];
|
|
230
|
+
const window = oldLines.length;
|
|
231
|
+
for (let i = 0; i + window <= srcLines.length; i++) {
|
|
232
|
+
let ok = true;
|
|
233
|
+
for (let j = 0; j < window; j++) {
|
|
234
|
+
if (norm(srcLines[i + j]) !== oldNorm[j]) {
|
|
235
|
+
ok = false;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (!ok) continue;
|
|
240
|
+
const start = offsets[i];
|
|
241
|
+
const last = i + window - 1;
|
|
242
|
+
const end = offsets[last] + srcLines[last].length;
|
|
243
|
+
matches2.push([start, end]);
|
|
244
|
+
}
|
|
245
|
+
return matches2.length === 1 ? matches2[0] : null;
|
|
246
|
+
}
|
|
185
247
|
function nearMiss(src, old_str) {
|
|
186
248
|
const srcLines = src.split("\n");
|
|
187
249
|
const needle = old_str.split("\n").find((l) => l.trim()) ?? old_str;
|
|
@@ -208,6 +270,7 @@ var init_edit_file = __esm({
|
|
|
208
270
|
"src/tools/edit_file.ts"() {
|
|
209
271
|
"use strict";
|
|
210
272
|
init_paths();
|
|
273
|
+
init_verifyHint();
|
|
211
274
|
edit_file = {
|
|
212
275
|
name: "edit_file",
|
|
213
276
|
description: "Replace an exact string in a file. old_str must be unique unless replace_all is set. On no match, returns the closest text in the file.",
|
|
@@ -230,6 +293,15 @@ var init_edit_file = __esm({
|
|
|
230
293
|
const src = readFileSync3(abs, "utf-8");
|
|
231
294
|
const first = src.indexOf(old_str);
|
|
232
295
|
if (first === -1) {
|
|
296
|
+
if (replace_all !== true) {
|
|
297
|
+
const fuzzy = fuzzyRange(src, old_str);
|
|
298
|
+
if (fuzzy) {
|
|
299
|
+
const [s, e] = fuzzy;
|
|
300
|
+
const out2 = src.slice(0, s) + new_str + src.slice(e);
|
|
301
|
+
writeFileSync3(abs, out2, "utf-8");
|
|
302
|
+
return { content: `Edited ${path} (whitespace-tolerant match).${verifyHint(path)}` };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
233
305
|
return { content: `old_str not found in ${path}.${nearMiss(src, old_str)}`, is_error: true };
|
|
234
306
|
}
|
|
235
307
|
const all = replace_all === true;
|
|
@@ -242,7 +314,7 @@ var init_edit_file = __esm({
|
|
|
242
314
|
const out = all ? src.split(old_str).join(new_str) : src.slice(0, first) + new_str + src.slice(first + old_str.length);
|
|
243
315
|
const n = all ? src.split(old_str).length - 1 : 1;
|
|
244
316
|
writeFileSync3(abs, out, "utf-8");
|
|
245
|
-
return { content: `Edited ${path}${all ? ` (${n} occurrences)` : ""}` };
|
|
317
|
+
return { content: `Edited ${path}${all ? ` (${n} occurrences)` : ""}.${verifyHint(path)}` };
|
|
246
318
|
} catch (err) {
|
|
247
319
|
return { content: err instanceof Error ? err.message : String(err), is_error: true };
|
|
248
320
|
}
|
|
@@ -315,6 +387,7 @@ var init_write_file = __esm({
|
|
|
315
387
|
"src/tools/write_file.ts"() {
|
|
316
388
|
"use strict";
|
|
317
389
|
init_paths();
|
|
390
|
+
init_verifyHint();
|
|
318
391
|
write_file = {
|
|
319
392
|
name: "write_file",
|
|
320
393
|
description: "Create or overwrite a file with the given content. Parent dirs auto-created.",
|
|
@@ -331,7 +404,7 @@ var init_write_file = __esm({
|
|
|
331
404
|
const abs = confinePath(path);
|
|
332
405
|
mkdirSync3(dirname(abs), { recursive: true });
|
|
333
406
|
writeFileSync4(abs, content, "utf-8");
|
|
334
|
-
return { content: `Wrote ${path} (${content.length} bytes)` };
|
|
407
|
+
return { content: `Wrote ${path} (${content.length} bytes).${verifyHint(path)}` };
|
|
335
408
|
} catch (err) {
|
|
336
409
|
return { content: err instanceof Error ? err.message : String(err), is_error: true };
|
|
337
410
|
}
|
|
@@ -1348,7 +1421,7 @@ import { createElement } from "react";
|
|
|
1348
1421
|
|
|
1349
1422
|
// src/ui/App.tsx
|
|
1350
1423
|
init_client();
|
|
1351
|
-
import { useState as
|
|
1424
|
+
import { useState as useState5, useEffect as useEffect4 } from "react";
|
|
1352
1425
|
import { Box as Box10, Text as Text10, useApp } from "ink";
|
|
1353
1426
|
import { homedir as homedir6 } from "os";
|
|
1354
1427
|
import { sep as sep2 } from "path";
|
|
@@ -1790,6 +1863,7 @@ function FilePicker({ matches: matches2, cursor }) {
|
|
|
1790
1863
|
}
|
|
1791
1864
|
|
|
1792
1865
|
// src/ui/ChatView.tsx
|
|
1866
|
+
import { useState as useState3, useEffect as useEffect3 } from "react";
|
|
1793
1867
|
import { Box as Box9, Text as Text9 } from "ink";
|
|
1794
1868
|
|
|
1795
1869
|
// src/ui/ThinkingBlock.tsx
|
|
@@ -1850,6 +1924,24 @@ var EMPTY_STATE_TITLE = "Ask anything, or try:";
|
|
|
1850
1924
|
|
|
1851
1925
|
// src/ui/ChatView.tsx
|
|
1852
1926
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1927
|
+
var COLLAPSED_LINES = 3;
|
|
1928
|
+
var globalToolExpanded = false;
|
|
1929
|
+
var toolExpandListeners = /* @__PURE__ */ new Set();
|
|
1930
|
+
function toggleToolExpanded() {
|
|
1931
|
+
globalToolExpanded = !globalToolExpanded;
|
|
1932
|
+
toolExpandListeners.forEach((fn) => fn());
|
|
1933
|
+
}
|
|
1934
|
+
function useToolExpanded() {
|
|
1935
|
+
const [expanded, setExpanded] = useState3(globalToolExpanded);
|
|
1936
|
+
useEffect3(() => {
|
|
1937
|
+
const handler = () => setExpanded(globalToolExpanded);
|
|
1938
|
+
toolExpandListeners.add(handler);
|
|
1939
|
+
return () => {
|
|
1940
|
+
toolExpandListeners.delete(handler);
|
|
1941
|
+
};
|
|
1942
|
+
}, []);
|
|
1943
|
+
return expanded;
|
|
1944
|
+
}
|
|
1853
1945
|
function formatTokens(n) {
|
|
1854
1946
|
if (n >= 1e3) return (n / 1e3).toFixed(n >= 1e4 ? 0 : 1) + "k";
|
|
1855
1947
|
return String(n);
|
|
@@ -1872,8 +1964,8 @@ function FileEditBlock({
|
|
|
1872
1964
|
removed,
|
|
1873
1965
|
previewLines
|
|
1874
1966
|
}) {
|
|
1875
|
-
const
|
|
1876
|
-
const shown = previewLines.slice(0,
|
|
1967
|
+
const expanded = useToolExpanded();
|
|
1968
|
+
const shown = expanded ? previewLines : previewLines.slice(0, COLLAPSED_LINES);
|
|
1877
1969
|
const extra = previewLines.length - shown.length;
|
|
1878
1970
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
|
|
1879
1971
|
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
@@ -1890,15 +1982,22 @@ function FileEditBlock({
|
|
|
1890
1982
|
"\u23BF ",
|
|
1891
1983
|
removed > 0 ? `Added ${added} lines, removed ${removed} lines` : `Added ${added} lines`
|
|
1892
1984
|
] }) }),
|
|
1893
|
-
shown.map((ln, i) =>
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1985
|
+
shown.map((ln, i) => {
|
|
1986
|
+
const width = (process.stdout.columns ?? 80) - 6;
|
|
1987
|
+
const content = `${ln.sign} ${ln.text}`.padEnd(width);
|
|
1988
|
+
return /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsx9(
|
|
1989
|
+
Text9,
|
|
1990
|
+
{
|
|
1991
|
+
backgroundColor: ln.sign === "+" ? "#13351f" : ln.sign === "-" ? "#3b1414" : void 0,
|
|
1992
|
+
dimColor: ln.sign === " ",
|
|
1993
|
+
children: content
|
|
1994
|
+
}
|
|
1995
|
+
) }, i);
|
|
1996
|
+
}),
|
|
1898
1997
|
extra > 0 && /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
1899
1998
|
"\u2026 ",
|
|
1900
1999
|
extra,
|
|
1901
|
-
" more lines"
|
|
2000
|
+
" more lines \xB7 ctrl+o to expand"
|
|
1902
2001
|
] }) })
|
|
1903
2002
|
] });
|
|
1904
2003
|
}
|
|
@@ -1964,6 +2063,7 @@ function summarizeResult(res, toolName) {
|
|
|
1964
2063
|
return extra > 0 ? `${head} (+${extra} lines)` : head;
|
|
1965
2064
|
}
|
|
1966
2065
|
function ToolResultBlock({ result, toolName }) {
|
|
2066
|
+
const expanded = useToolExpanded();
|
|
1967
2067
|
const content = result.content ?? "";
|
|
1968
2068
|
const lines = content.split("\n");
|
|
1969
2069
|
const showMulti = (toolName === "run_bash" || toolName === "grep" || toolName === "glob" || result.is_error) && lines.length > 1;
|
|
@@ -1973,9 +2073,9 @@ function ToolResultBlock({ result, toolName }) {
|
|
|
1973
2073
|
summarizeResult(result, toolName)
|
|
1974
2074
|
] }) });
|
|
1975
2075
|
}
|
|
1976
|
-
const MAX_LINES = 10;
|
|
1977
2076
|
const MAX_LINE_WIDTH = 200;
|
|
1978
|
-
const
|
|
2077
|
+
const visible = expanded ? lines : lines.slice(0, COLLAPSED_LINES);
|
|
2078
|
+
const shown = visible.map((l) => truncate(l, MAX_LINE_WIDTH));
|
|
1979
2079
|
const extra = lines.length - shown.length;
|
|
1980
2080
|
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginLeft: 2, children: [
|
|
1981
2081
|
/* @__PURE__ */ jsxs9(Text9, { color: result.is_error ? "red" : void 0, dimColor: !result.is_error, children: [
|
|
@@ -1986,7 +2086,7 @@ function ToolResultBlock({ result, toolName }) {
|
|
|
1986
2086
|
extra > 0 && /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
1987
2087
|
"\u2026 ",
|
|
1988
2088
|
extra,
|
|
1989
|
-
" more lines"
|
|
2089
|
+
" more lines \xB7 ctrl+o to expand"
|
|
1990
2090
|
] }) })
|
|
1991
2091
|
] });
|
|
1992
2092
|
}
|
|
@@ -2127,22 +2227,22 @@ function ChatView({
|
|
|
2127
2227
|
|
|
2128
2228
|
// src/ui/hooks/useAgentRunner.ts
|
|
2129
2229
|
init_loop();
|
|
2130
|
-
import { useState as
|
|
2230
|
+
import { useState as useState4, useRef } from "react";
|
|
2131
2231
|
var FLUSH_MS = 100;
|
|
2132
2232
|
function useAgentRunner(model, activeCtx) {
|
|
2133
|
-
const [messages, setMessages] =
|
|
2134
|
-
const [thinking, setThinking] =
|
|
2135
|
-
const [thinkingContent, setThinkingContent] =
|
|
2136
|
-
const [streaming, setStreaming] =
|
|
2137
|
-
const [streamingContent, setStreamingContent] =
|
|
2138
|
-
const [error, setError] =
|
|
2139
|
-
const [busy, setBusy] =
|
|
2140
|
-
const [processingLabel, setProcessingLabel] =
|
|
2141
|
-
const [agentHistory, setAgentHistory] =
|
|
2142
|
-
const [pendingPermission, setPendingPermission] =
|
|
2143
|
-
const [permissionCursor, setPermissionCursor] =
|
|
2144
|
-
const [activeToolUses, setActiveToolUses] =
|
|
2145
|
-
const [activeToolResults, setActiveToolResults] =
|
|
2233
|
+
const [messages, setMessages] = useState4([]);
|
|
2234
|
+
const [thinking, setThinking] = useState4(false);
|
|
2235
|
+
const [thinkingContent, setThinkingContent] = useState4("");
|
|
2236
|
+
const [streaming, setStreaming] = useState4(false);
|
|
2237
|
+
const [streamingContent, setStreamingContent] = useState4("");
|
|
2238
|
+
const [error, setError] = useState4(null);
|
|
2239
|
+
const [busy, setBusy] = useState4(false);
|
|
2240
|
+
const [processingLabel, setProcessingLabel] = useState4(void 0);
|
|
2241
|
+
const [agentHistory, setAgentHistory] = useState4([]);
|
|
2242
|
+
const [pendingPermission, setPendingPermission] = useState4(null);
|
|
2243
|
+
const [permissionCursor, setPermissionCursor] = useState4(0);
|
|
2244
|
+
const [activeToolUses, setActiveToolUses] = useState4([]);
|
|
2245
|
+
const [activeToolResults, setActiveToolResults] = useState4([]);
|
|
2146
2246
|
const busyRef = useRef(false);
|
|
2147
2247
|
const abortRef = useRef(null);
|
|
2148
2248
|
const pendingPermissionRef = useRef(null);
|
|
@@ -2413,6 +2513,10 @@ function useKeyboard(opts) {
|
|
|
2413
2513
|
toggleThinkingVisible();
|
|
2414
2514
|
return;
|
|
2415
2515
|
}
|
|
2516
|
+
if (key.ctrl && char === "o") {
|
|
2517
|
+
toggleToolExpanded();
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2416
2520
|
if (key.escape && busyRef.current && abortRef.current) {
|
|
2417
2521
|
abortRef.current.abort();
|
|
2418
2522
|
return;
|
|
@@ -2638,30 +2742,30 @@ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-r
|
|
|
2638
2742
|
function App() {
|
|
2639
2743
|
const { exit } = useApp();
|
|
2640
2744
|
const cwd = process.cwd().replace(homedir6(), "~").split(sep2).join("/");
|
|
2641
|
-
const [cfg, setCfg] =
|
|
2642
|
-
const [models, setModels] =
|
|
2643
|
-
const [contexts, setContexts] =
|
|
2644
|
-
const [activeCtx, setActiveCtx] =
|
|
2645
|
-
const [state, setState] =
|
|
2646
|
-
const [cursor, setCursor] =
|
|
2647
|
-
const [updateAvailable, setUpdateAvailable] =
|
|
2648
|
-
const [ollamaDown, setOllamaDown] =
|
|
2649
|
-
const [sessionId, setSessionId] =
|
|
2650
|
-
const [sessions, setSessions] =
|
|
2651
|
-
const [notice, setNotice] =
|
|
2652
|
-
const [input, setInput] =
|
|
2653
|
-
const [paletteCursor, setPaletteCursor] =
|
|
2654
|
-
const [filePickerCursor, setFilePickerCursor] =
|
|
2745
|
+
const [cfg, setCfg] = useState5(loadConfig());
|
|
2746
|
+
const [models, setModels] = useState5([]);
|
|
2747
|
+
const [contexts, setContexts] = useState5({});
|
|
2748
|
+
const [activeCtx, setActiveCtx] = useState5(null);
|
|
2749
|
+
const [state, setState] = useState5("loading");
|
|
2750
|
+
const [cursor, setCursor] = useState5(0);
|
|
2751
|
+
const [updateAvailable, setUpdateAvailable] = useState5(null);
|
|
2752
|
+
const [ollamaDown, setOllamaDown] = useState5(false);
|
|
2753
|
+
const [sessionId, setSessionId] = useState5(() => newSessionId());
|
|
2754
|
+
const [sessions, setSessions] = useState5([]);
|
|
2755
|
+
const [notice, setNotice] = useState5(null);
|
|
2756
|
+
const [input, setInput] = useState5("");
|
|
2757
|
+
const [paletteCursor, setPaletteCursor] = useState5(0);
|
|
2758
|
+
const [filePickerCursor, setFilePickerCursor] = useState5(0);
|
|
2655
2759
|
const agent = useAgentRunner(cfg.model, activeCtx);
|
|
2656
|
-
|
|
2760
|
+
useEffect4(() => {
|
|
2657
2761
|
checkForUpdate().then((v) => {
|
|
2658
2762
|
if (v) setUpdateAvailable(v);
|
|
2659
2763
|
});
|
|
2660
2764
|
}, []);
|
|
2661
|
-
|
|
2765
|
+
useEffect4(() => {
|
|
2662
2766
|
if (agent.agentHistory.length) persistSession(sessionId, agent.agentHistory);
|
|
2663
2767
|
}, [agent.agentHistory, sessionId]);
|
|
2664
|
-
|
|
2768
|
+
useEffect4(() => {
|
|
2665
2769
|
listModels().then((m) => {
|
|
2666
2770
|
setModels(m);
|
|
2667
2771
|
setState(cfg.model ? "ready" : "select-model");
|