@jx-grxf/patchpilot 0.2.1 → 0.3.1-beta
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 +70 -10
- package/SECURITY.md +20 -0
- package/dist/cli.js +52 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/agent.d.ts +6 -2
- package/dist/core/agent.js +197 -27
- package/dist/core/agent.js.map +1 -1
- package/dist/core/codex.js +1 -1
- package/dist/core/codex.js.map +1 -1
- package/dist/core/gemini.js +8 -21
- package/dist/core/gemini.js.map +1 -1
- package/dist/core/http.d.ts +6 -0
- package/dist/core/http.js +45 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/json.js +9 -0
- package/dist/core/json.js.map +1 -1
- package/dist/core/nvidia.js +9 -2
- package/dist/core/nvidia.js.map +1 -1
- package/dist/core/ollama.js +8 -1
- package/dist/core/ollama.js.map +1 -1
- package/dist/core/openrouter.js +13 -8
- package/dist/core/openrouter.js.map +1 -1
- package/dist/core/reasoning.d.ts +12 -0
- package/dist/core/reasoning.js +108 -0
- package/dist/core/reasoning.js.map +1 -0
- package/dist/core/session.d.ts +31 -0
- package/dist/core/session.js +154 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/types.d.ts +103 -2
- package/dist/core/workspace.d.ts +17 -1
- package/dist/core/workspace.js +498 -16
- package/dist/core/workspace.js.map +1 -1
- package/dist/tui/App.js +368 -109
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/commands.js +45 -4
- package/dist/tui/commands.js.map +1 -1
- package/dist/tui/components/Composer.js +1 -1
- package/dist/tui/components/Composer.js.map +1 -1
- package/dist/tui/components/Header.d.ts +2 -2
- package/dist/tui/components/Header.js +32 -53
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/OnboardingPanel.d.ts +5 -0
- package/dist/tui/components/OnboardingPanel.js +11 -13
- package/dist/tui/components/OnboardingPanel.js.map +1 -1
- package/dist/tui/components/Sidebar.d.ts +6 -1
- package/dist/tui/components/Sidebar.js +33 -7
- package/dist/tui/components/Sidebar.js.map +1 -1
- package/dist/tui/components/Transcript.js +57 -8
- package/dist/tui/components/Transcript.js.map +1 -1
- package/dist/tui/hosts.js +7 -1
- package/dist/tui/hosts.js.map +1 -1
- package/dist/tui/modelSelection.d.ts +1 -0
- package/dist/tui/modelSelection.js +29 -0
- package/dist/tui/modelSelection.js.map +1 -0
- package/dist/tui/modes.d.ts +10 -0
- package/dist/tui/modes.js +37 -0
- package/dist/tui/modes.js.map +1 -0
- package/dist/tui/types.d.ts +13 -3
- package/dist/tui/types.js.map +1 -1
- package/docs/releases/v0.1.0.md +26 -0
- package/docs/releases/v0.2.0.md +21 -0
- package/docs/releases/v0.2.1.md +26 -0
- package/docs/releases/v0.3.0.md +26 -0
- package/docs/releases/v0.3.1-beta.md +19 -0
- package/docs/showcase/patchpilot-showcase.svg +83 -38
- package/package.json +5 -2
- package/dist/tui/inputRouting.d.ts +0 -8
- package/dist/tui/inputRouting.js +0 -94
- package/dist/tui/inputRouting.js.map +0 -1
package/dist/tui/App.js
CHANGED
|
@@ -11,7 +11,10 @@ import { createModelClient } from "../core/modelClient.js";
|
|
|
11
11
|
import { defaultNvidiaModel, readNvidiaApiKey } from "../core/nvidia.js";
|
|
12
12
|
import { defaultOllamaModel, OllamaClient } from "../core/ollama.js";
|
|
13
13
|
import { defaultOpenRouterModel, isOpenRouterFreeModel, readOpenRouterApiKey } from "../core/openrouter.js";
|
|
14
|
+
import { formatReasoningSupport } from "../core/reasoning.js";
|
|
15
|
+
import { listWorkspaceSessions, loadSessionSummary, SessionStore } from "../core/session.js";
|
|
14
16
|
import { addTelemetryToSession, emptySessionTelemetry, estimateTokens } from "../core/tokenAccounting.js";
|
|
17
|
+
import { WorkspaceTools } from "../core/workspace.js";
|
|
15
18
|
import { CommandSuggestions } from "./components/CommandSuggestions.js";
|
|
16
19
|
import { Composer, FooterHints } from "./components/Composer.js";
|
|
17
20
|
import { Header } from "./components/Header.js";
|
|
@@ -21,6 +24,8 @@ import { Transcript } from "./components/Transcript.js";
|
|
|
21
24
|
import { filterSlashCommands, formatCommandDetail, formatCommandHelp } from "./commands.js";
|
|
22
25
|
import { formatCost, formatSessionTokens, formatTokens, normalizeModelAlias, readToggle } from "./format.js";
|
|
23
26
|
import { checkOllamaHost, discoverOllamaHosts, normalizeOllamaUrl, readOllamaHostDetails, startLocalOllamaAppAndWait } from "./hosts.js";
|
|
27
|
+
import { initialAgentMode, modeDescription, modePermissionLabel, nextAgentMode, permissionsForMode } from "./modes.js";
|
|
28
|
+
import { selectableModels } from "./modelSelection.js";
|
|
24
29
|
import { readGpuStats, readSystemStats } from "./systemStats.js";
|
|
25
30
|
import { maxTranscriptLines } from "./types.js";
|
|
26
31
|
const modelCacheTtlMs = 5 * 60_000;
|
|
@@ -32,16 +37,27 @@ export function App(props) {
|
|
|
32
37
|
const didRunInitialTask = useRef(false);
|
|
33
38
|
const didOpenDefaultOnboarding = useRef(false);
|
|
34
39
|
const abortControllerRef = useRef(null);
|
|
40
|
+
const sessionStoreRef = useRef(new SessionStore({ workspace: props.workspace }));
|
|
41
|
+
const approvalResolverRef = useRef(null);
|
|
42
|
+
const grantedPermissionsRef = useRef({
|
|
43
|
+
allowWrite: props.allowWrite,
|
|
44
|
+
allowShell: props.allowShell
|
|
45
|
+
});
|
|
46
|
+
const activeHostSyncInFlightRef = useRef(false);
|
|
47
|
+
const autoLoadKeysRef = useRef(new Set());
|
|
35
48
|
const usedOllamaModelsRef = useRef(new Set());
|
|
36
49
|
const [lines, setLines] = useState([]);
|
|
37
50
|
const [advisorNotes, setAdvisorNotes] = useState([]);
|
|
38
51
|
const [isRunning, setIsRunning] = useState(false);
|
|
39
52
|
const [status, setStatus] = useState("idle");
|
|
53
|
+
const [workState, setWorkState] = useState("idle");
|
|
54
|
+
const [pendingApproval, setPendingApproval] = useState(null);
|
|
40
55
|
const [telemetry, setTelemetry] = useState(null);
|
|
41
56
|
const [sessionTelemetry, setSessionTelemetry] = useState(() => emptySessionTelemetry());
|
|
42
57
|
const [systemStats, setSystemStats] = useState(() => readSystemStats().stats);
|
|
43
58
|
const [gpuStats, setGpuStats] = useState(null);
|
|
44
|
-
const [agentMode, setAgentMode] = useState(props.allowWrite
|
|
59
|
+
const [agentMode, setAgentMode] = useState(() => initialAgentMode({ allowWrite: props.allowWrite, allowShell: props.allowShell }));
|
|
60
|
+
const [bypassConfirmation, setBypassConfirmation] = useState(false);
|
|
45
61
|
const [hostOptions, setHostOptions] = useState([]);
|
|
46
62
|
const [activeHost, setActiveHost] = useState(null);
|
|
47
63
|
const [isLoadingHosts, setIsLoadingHosts] = useState(false);
|
|
@@ -51,6 +67,7 @@ export function App(props) {
|
|
|
51
67
|
const [onboardingIndex, setOnboardingIndex] = useState(0);
|
|
52
68
|
const [onboardingInput, setOnboardingInput] = useState("");
|
|
53
69
|
const [onboardingBusyMessage, setOnboardingBusyMessage] = useState(null);
|
|
70
|
+
const [onboardingNotice, setOnboardingNotice] = useState(null);
|
|
54
71
|
const [paletteIndex, setPaletteIndex] = useState(0);
|
|
55
72
|
const [activeScrollPane, setActiveScrollPane] = useState("transcript");
|
|
56
73
|
const [transcriptScrollOffset, setTranscriptScrollOffset] = useState(0);
|
|
@@ -82,7 +99,7 @@ export function App(props) {
|
|
|
82
99
|
})
|
|
83
100
|
: [];
|
|
84
101
|
const rootHeight = Math.max(24, terminalRows);
|
|
85
|
-
const headerReservedHeight =
|
|
102
|
+
const headerReservedHeight = 5;
|
|
86
103
|
const paletteReservedHeight = !onboarding && paletteItems.length > 0 ? Math.min(8, paletteItems.length) + 4 : 0;
|
|
87
104
|
const composerReservedHeight = onboarding ? 0 : 2;
|
|
88
105
|
const footerReservedHeight = onboarding ? 0 : 1;
|
|
@@ -94,28 +111,81 @@ export function App(props) {
|
|
|
94
111
|
...currentLines.slice(-maxTranscriptLines),
|
|
95
112
|
{
|
|
96
113
|
...line,
|
|
114
|
+
kind: line.kind ?? defaultLogKind(line),
|
|
97
115
|
id: Date.now() + Math.random()
|
|
98
116
|
}
|
|
99
117
|
]);
|
|
100
118
|
}, []);
|
|
119
|
+
const resolveApproval = useCallback((decision) => {
|
|
120
|
+
if (!pendingApproval || !approvalResolverRef.current) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
approvalResolverRef.current(decision);
|
|
124
|
+
approvalResolverRef.current = null;
|
|
125
|
+
appendLine({
|
|
126
|
+
kind: "approval",
|
|
127
|
+
tone: decision === "deny" ? "warning" : "success",
|
|
128
|
+
label: "approval",
|
|
129
|
+
text: `${pendingApproval.tool} ${decision.replace("_", " ")}`,
|
|
130
|
+
detail: pendingApproval.preview,
|
|
131
|
+
workState: "waiting_approval",
|
|
132
|
+
tool: pendingApproval.tool
|
|
133
|
+
});
|
|
134
|
+
setPendingApproval(null);
|
|
135
|
+
}, [appendLine, pendingApproval]);
|
|
101
136
|
const applyMode = useCallback((nextMode, announce = true) => {
|
|
137
|
+
const permissions = permissionsForMode(nextMode);
|
|
102
138
|
setAgentMode(nextMode);
|
|
139
|
+
setBypassConfirmation(false);
|
|
103
140
|
setSettings((currentSettings) => ({
|
|
104
141
|
...currentSettings,
|
|
105
|
-
allowWrite:
|
|
106
|
-
allowShell:
|
|
142
|
+
allowWrite: permissions.allowWrite,
|
|
143
|
+
allowShell: permissions.allowShell
|
|
107
144
|
}));
|
|
108
145
|
if (announce) {
|
|
109
146
|
appendLine({
|
|
110
|
-
tone: "success",
|
|
147
|
+
tone: nextMode === "bypass" ? "warning" : "success",
|
|
111
148
|
label: "mode",
|
|
112
|
-
text:
|
|
149
|
+
text: modeDescription(nextMode),
|
|
150
|
+
detail: nextMode === "plan"
|
|
151
|
+
? "Read/search/status tools can still run. Writes, tests, scripts, and shell are denied."
|
|
152
|
+
: nextMode === "build"
|
|
153
|
+
? "Risky tools can run only after allow once/session approval."
|
|
154
|
+
: "Use only in a trusted workspace. Path guards and destructive shell guards still apply."
|
|
113
155
|
});
|
|
114
156
|
}
|
|
115
157
|
}, [appendLine]);
|
|
158
|
+
const requestBypassMode = useCallback(() => {
|
|
159
|
+
setBypassConfirmation(true);
|
|
160
|
+
appendLine({
|
|
161
|
+
kind: "approval",
|
|
162
|
+
tone: "danger",
|
|
163
|
+
label: "bypass",
|
|
164
|
+
text: "Build + bypass will enable write and shell permissions without per-tool prompts.",
|
|
165
|
+
detail: "Press y to accept for this session, or n/Esc to stay in build mode. Use this only in a trusted workspace."
|
|
166
|
+
});
|
|
167
|
+
}, [appendLine]);
|
|
168
|
+
const confirmBypassMode = useCallback(() => {
|
|
169
|
+
applyMode("bypass");
|
|
170
|
+
}, [applyMode]);
|
|
171
|
+
const cancelBypassMode = useCallback(() => {
|
|
172
|
+
setBypassConfirmation(false);
|
|
173
|
+
applyMode("build", false);
|
|
174
|
+
appendLine({
|
|
175
|
+
kind: "approval",
|
|
176
|
+
tone: "warning",
|
|
177
|
+
label: "bypass",
|
|
178
|
+
text: "Bypass cancelled. Build mode still uses approvals."
|
|
179
|
+
});
|
|
180
|
+
}, [appendLine, applyMode]);
|
|
116
181
|
const toggleMode = useCallback(() => {
|
|
117
|
-
|
|
118
|
-
|
|
182
|
+
const mode = nextAgentMode(agentMode);
|
|
183
|
+
if (mode === "bypass") {
|
|
184
|
+
requestBypassMode();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
applyMode(mode);
|
|
188
|
+
}, [agentMode, applyMode, requestBypassMode]);
|
|
119
189
|
const loadHostSuggestions = useCallback(async (refresh = false, announce = false) => {
|
|
120
190
|
if (isLoadingHosts) {
|
|
121
191
|
return hostOptions;
|
|
@@ -217,6 +287,8 @@ export function App(props) {
|
|
|
217
287
|
}, [appendLine, settings.model]);
|
|
218
288
|
const openModelSelection = useCallback(async (provider, options = {}) => {
|
|
219
289
|
setTelemetry(null);
|
|
290
|
+
setOnboardingInput("");
|
|
291
|
+
setOnboardingNotice(null);
|
|
220
292
|
setOnboardingBusyMessage(`Loading ${provider} models...`);
|
|
221
293
|
const nextModel = defaultModelForProvider(provider, options.currentModel ?? settings.model);
|
|
222
294
|
setSettings((currentSettings) => ({
|
|
@@ -227,16 +299,16 @@ export function App(props) {
|
|
|
227
299
|
try {
|
|
228
300
|
const models = await loadAvailableModels(provider, options.ollamaUrl ?? settings.ollamaUrl, setModelOptions, true);
|
|
229
301
|
if (models.length === 0) {
|
|
230
|
-
|
|
302
|
+
setOnboardingNotice({
|
|
231
303
|
tone: "warning",
|
|
232
|
-
label: "onboarding",
|
|
233
304
|
text: provider === "ollama"
|
|
234
305
|
? "No Ollama models found on that host."
|
|
235
306
|
: provider === "gemini"
|
|
236
307
|
? "No Gemini models listed. Check the API key."
|
|
237
308
|
: provider === "openrouter"
|
|
238
309
|
? "No OpenRouter models listed. Check the API key."
|
|
239
|
-
: "No Codex OAuth models listed."
|
|
310
|
+
: "No Codex OAuth models listed.",
|
|
311
|
+
detail: "Use the back key to choose another provider or retry after fixing the provider setup."
|
|
240
312
|
});
|
|
241
313
|
return;
|
|
242
314
|
}
|
|
@@ -249,21 +321,22 @@ export function App(props) {
|
|
|
249
321
|
setOnboardingIndex(0);
|
|
250
322
|
}
|
|
251
323
|
catch (error) {
|
|
252
|
-
|
|
324
|
+
setOnboardingNotice({
|
|
253
325
|
tone: "danger",
|
|
254
|
-
|
|
255
|
-
|
|
326
|
+
text: error instanceof Error ? error.message : String(error),
|
|
327
|
+
detail: "Fix the provider setup, then press Enter or go back and retry."
|
|
256
328
|
});
|
|
257
329
|
}
|
|
258
330
|
finally {
|
|
259
331
|
setOnboardingBusyMessage(null);
|
|
260
332
|
}
|
|
261
|
-
}, [
|
|
333
|
+
}, [settings.model, settings.ollamaUrl]);
|
|
262
334
|
const closeOnboarding = useCallback(() => {
|
|
263
335
|
setOnboarding(null);
|
|
264
336
|
setOnboardingIndex(0);
|
|
265
337
|
setOnboardingInput("");
|
|
266
338
|
setOnboardingBusyMessage(null);
|
|
339
|
+
setOnboardingNotice(null);
|
|
267
340
|
}, []);
|
|
268
341
|
const goBackOnboarding = useCallback(() => {
|
|
269
342
|
if (!onboarding) {
|
|
@@ -271,6 +344,7 @@ export function App(props) {
|
|
|
271
344
|
}
|
|
272
345
|
setOnboardingBusyMessage(null);
|
|
273
346
|
setOnboardingInput("");
|
|
347
|
+
setOnboardingNotice(null);
|
|
274
348
|
setOnboardingIndex(0);
|
|
275
349
|
switch (onboarding.step) {
|
|
276
350
|
case "entry":
|
|
@@ -327,6 +401,10 @@ export function App(props) {
|
|
|
327
401
|
if (!onboarding) {
|
|
328
402
|
return;
|
|
329
403
|
}
|
|
404
|
+
if (onboardingBusyMessage) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
setOnboardingNotice(null);
|
|
330
408
|
if (onboarding.step === "entry") {
|
|
331
409
|
const selection = readEntrySelection(value, onboardingIndex);
|
|
332
410
|
if (!selection) {
|
|
@@ -343,7 +421,12 @@ export function App(props) {
|
|
|
343
421
|
details = startedHost ? await connectToHost(startedHost, { announce: false }) : null;
|
|
344
422
|
}
|
|
345
423
|
if (!details) {
|
|
346
|
-
setOnboardingBusyMessage(
|
|
424
|
+
setOnboardingBusyMessage(null);
|
|
425
|
+
setOnboardingNotice({
|
|
426
|
+
tone: "warning",
|
|
427
|
+
text: "Local Ollama is not reachable.",
|
|
428
|
+
detail: "Start Ollama.app or run `ollama serve`, then press Enter to retry."
|
|
429
|
+
});
|
|
347
430
|
return;
|
|
348
431
|
}
|
|
349
432
|
await openModelSelection("ollama", {
|
|
@@ -394,9 +477,8 @@ export function App(props) {
|
|
|
394
477
|
}
|
|
395
478
|
const selectedHost = onboarding.hosts[selectionIndex - 1];
|
|
396
479
|
if (!selectedHost) {
|
|
397
|
-
|
|
480
|
+
setOnboardingNotice({
|
|
398
481
|
tone: "warning",
|
|
399
|
-
label: "onboarding",
|
|
400
482
|
text: "Unknown host selection."
|
|
401
483
|
});
|
|
402
484
|
return;
|
|
@@ -407,6 +489,11 @@ export function App(props) {
|
|
|
407
489
|
});
|
|
408
490
|
if (!details) {
|
|
409
491
|
setOnboardingBusyMessage(null);
|
|
492
|
+
setOnboardingNotice({
|
|
493
|
+
tone: "warning",
|
|
494
|
+
text: `No Ollama server answered at ${selectedHost.url}.`,
|
|
495
|
+
detail: "Check firewall, MagicDNS/IP, and whether Ollama is listening on that machine."
|
|
496
|
+
});
|
|
410
497
|
return;
|
|
411
498
|
}
|
|
412
499
|
await openModelSelection("ollama", {
|
|
@@ -418,9 +505,8 @@ export function App(props) {
|
|
|
418
505
|
if (onboarding.step === "host-input") {
|
|
419
506
|
const hostValue = value.trim();
|
|
420
507
|
if (!hostValue) {
|
|
421
|
-
|
|
508
|
+
setOnboardingNotice({
|
|
422
509
|
tone: "warning",
|
|
423
|
-
label: "onboarding",
|
|
424
510
|
text: "Host cannot be empty."
|
|
425
511
|
});
|
|
426
512
|
return;
|
|
@@ -431,6 +517,11 @@ export function App(props) {
|
|
|
431
517
|
});
|
|
432
518
|
if (!details) {
|
|
433
519
|
setOnboardingBusyMessage(null);
|
|
520
|
+
setOnboardingNotice({
|
|
521
|
+
tone: "warning",
|
|
522
|
+
text: `No Ollama server answered at ${hostValue}.`,
|
|
523
|
+
detail: "Check the IP, MagicDNS name, firewall rules, and whether Ollama is running."
|
|
524
|
+
});
|
|
434
525
|
return;
|
|
435
526
|
}
|
|
436
527
|
await openModelSelection("ollama", {
|
|
@@ -460,9 +551,8 @@ export function App(props) {
|
|
|
460
551
|
if (onboarding.step === "gemini-key") {
|
|
461
552
|
const apiKey = value.trim();
|
|
462
553
|
if (!apiKey) {
|
|
463
|
-
|
|
554
|
+
setOnboardingNotice({
|
|
464
555
|
tone: "warning",
|
|
465
|
-
label: "onboarding",
|
|
466
556
|
text: "Gemini API key cannot be empty."
|
|
467
557
|
});
|
|
468
558
|
return;
|
|
@@ -473,9 +563,8 @@ export function App(props) {
|
|
|
473
563
|
PATCHPILOT_MODEL: defaultGeminiModel,
|
|
474
564
|
GEMINI_API_KEY: apiKey
|
|
475
565
|
});
|
|
476
|
-
|
|
566
|
+
setOnboardingNotice({
|
|
477
567
|
tone: "success",
|
|
478
|
-
label: "onboarding",
|
|
479
568
|
text: "Gemini API key saved to PatchPilot config."
|
|
480
569
|
});
|
|
481
570
|
await openModelSelection("gemini", {
|
|
@@ -486,9 +575,8 @@ export function App(props) {
|
|
|
486
575
|
if (onboarding.step === "openrouter-key") {
|
|
487
576
|
const apiKey = value.trim();
|
|
488
577
|
if (!apiKey) {
|
|
489
|
-
|
|
578
|
+
setOnboardingNotice({
|
|
490
579
|
tone: "warning",
|
|
491
|
-
label: "onboarding",
|
|
492
580
|
text: "OpenRouter API key cannot be empty."
|
|
493
581
|
});
|
|
494
582
|
return;
|
|
@@ -499,9 +587,8 @@ export function App(props) {
|
|
|
499
587
|
PATCHPILOT_MODEL: defaultOpenRouterModel,
|
|
500
588
|
OPENROUTER_API_KEY: apiKey
|
|
501
589
|
});
|
|
502
|
-
|
|
590
|
+
setOnboardingNotice({
|
|
503
591
|
tone: "success",
|
|
504
|
-
label: "onboarding",
|
|
505
592
|
text: "OpenRouter API key saved to PatchPilot config."
|
|
506
593
|
});
|
|
507
594
|
await openModelSelection("openrouter", {
|
|
@@ -512,9 +599,8 @@ export function App(props) {
|
|
|
512
599
|
if (onboarding.step === "nvidia-key") {
|
|
513
600
|
const apiKey = value.trim();
|
|
514
601
|
if (!apiKey) {
|
|
515
|
-
|
|
602
|
+
setOnboardingNotice({
|
|
516
603
|
tone: "warning",
|
|
517
|
-
label: "onboarding",
|
|
518
604
|
text: "NVIDIA API key cannot be empty."
|
|
519
605
|
});
|
|
520
606
|
return;
|
|
@@ -525,9 +611,8 @@ export function App(props) {
|
|
|
525
611
|
PATCHPILOT_MODEL: defaultNvidiaModel,
|
|
526
612
|
NVIDIA_API_KEY: apiKey
|
|
527
613
|
});
|
|
528
|
-
|
|
614
|
+
setOnboardingNotice({
|
|
529
615
|
tone: "success",
|
|
530
|
-
label: "onboarding",
|
|
531
616
|
text: "NVIDIA API key saved to PatchPilot config."
|
|
532
617
|
});
|
|
533
618
|
await openModelSelection("nvidia", {
|
|
@@ -537,10 +622,10 @@ export function App(props) {
|
|
|
537
622
|
}
|
|
538
623
|
if (onboarding.step === "codex-login") {
|
|
539
624
|
if (!hasCodexCliOAuth()) {
|
|
540
|
-
|
|
625
|
+
setOnboardingNotice({
|
|
541
626
|
tone: "warning",
|
|
542
|
-
|
|
543
|
-
|
|
627
|
+
text: "Codex OAuth is still missing.",
|
|
628
|
+
detail: "Run `codex login` in another terminal, then press Enter to retry."
|
|
544
629
|
});
|
|
545
630
|
return;
|
|
546
631
|
}
|
|
@@ -549,14 +634,13 @@ export function App(props) {
|
|
|
549
634
|
});
|
|
550
635
|
return;
|
|
551
636
|
}
|
|
552
|
-
const
|
|
553
|
-
const selectedModel = selectModelFromInput(value,
|
|
637
|
+
const visibleModels = selectableModels(onboardingInput, onboarding.models);
|
|
638
|
+
const selectedModel = selectModelFromInput(value, visibleModels, onboardingIndex, {
|
|
554
639
|
allowManual: onboarding.provider !== "ollama"
|
|
555
640
|
});
|
|
556
641
|
if (!selectedModel) {
|
|
557
|
-
|
|
642
|
+
setOnboardingNotice({
|
|
558
643
|
tone: "warning",
|
|
559
|
-
label: "onboarding",
|
|
560
644
|
text: "Unknown model selection. Pick a listed model."
|
|
561
645
|
});
|
|
562
646
|
return;
|
|
@@ -587,7 +671,7 @@ export function App(props) {
|
|
|
587
671
|
});
|
|
588
672
|
}
|
|
589
673
|
closeOnboarding();
|
|
590
|
-
}, [activeHost?.host.url, appendLine, closeOnboarding, connectToHost, loadHostSuggestions, onboarding, onboardingIndex, openModelSelection, settings.ollamaUrl]);
|
|
674
|
+
}, [activeHost?.host.url, appendLine, closeOnboarding, connectToHost, loadHostSuggestions, onboarding, onboardingBusyMessage, onboardingIndex, openModelSelection, settings.ollamaUrl]);
|
|
591
675
|
const runTask = useCallback(async (task) => {
|
|
592
676
|
if (!task.trim() || isRunning) {
|
|
593
677
|
return;
|
|
@@ -596,6 +680,7 @@ export function App(props) {
|
|
|
596
680
|
setTranscriptScrollOffset(0);
|
|
597
681
|
setIsRunning(true);
|
|
598
682
|
appendLine({
|
|
683
|
+
kind: "user",
|
|
599
684
|
tone: "normal",
|
|
600
685
|
label: "you",
|
|
601
686
|
text: task
|
|
@@ -609,9 +694,46 @@ export function App(props) {
|
|
|
609
694
|
abortControllerRef.current = abortController;
|
|
610
695
|
const taskRunner = new AgentRunner({
|
|
611
696
|
...runnableSettings,
|
|
612
|
-
|
|
697
|
+
mode: agentMode,
|
|
698
|
+
signal: abortController.signal,
|
|
699
|
+
sessionStore: sessionStoreRef.current,
|
|
700
|
+
approvalHandler: (request) => new Promise((resolve) => {
|
|
701
|
+
if (agentMode === "plan") {
|
|
702
|
+
appendLine({
|
|
703
|
+
kind: "approval",
|
|
704
|
+
tone: "warning",
|
|
705
|
+
label: "approval",
|
|
706
|
+
text: `${request.tool} blocked in plan mode`,
|
|
707
|
+
detail: "Switch to /mode build before approving write, script, test, or shell tools.",
|
|
708
|
+
workState: "waiting_approval",
|
|
709
|
+
tool: request.tool,
|
|
710
|
+
preview: request.preview
|
|
711
|
+
});
|
|
712
|
+
resolve("deny");
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (agentMode === "bypass") {
|
|
716
|
+
resolve("allow_session");
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
setPendingApproval(request);
|
|
720
|
+
setWorkState("waiting_approval");
|
|
721
|
+
setStatus(`approval needed for ${request.tool}`);
|
|
722
|
+
appendLine({
|
|
723
|
+
kind: "approval",
|
|
724
|
+
tone: "warning",
|
|
725
|
+
label: "approval",
|
|
726
|
+
text: `${request.tool} needs ${request.permission} approval`,
|
|
727
|
+
detail: `${request.preview} Press y once, a session, or n deny.`,
|
|
728
|
+
workState: "waiting_approval",
|
|
729
|
+
tool: request.tool,
|
|
730
|
+
preview: request.preview
|
|
731
|
+
});
|
|
732
|
+
approvalResolverRef.current = resolve;
|
|
733
|
+
})
|
|
613
734
|
});
|
|
614
735
|
for await (const event of taskRunner.run(task)) {
|
|
736
|
+
setWorkState(event.workState);
|
|
615
737
|
if (event.type === "metrics") {
|
|
616
738
|
if (runnableSettings.provider === "ollama") {
|
|
617
739
|
usedOllamaModelsRef.current.add(`${runnableSettings.ollamaUrl}|${runnableSettings.model}`);
|
|
@@ -634,17 +756,20 @@ export function App(props) {
|
|
|
634
756
|
}
|
|
635
757
|
catch (error) {
|
|
636
758
|
appendLine({
|
|
759
|
+
kind: "error",
|
|
637
760
|
tone: "danger",
|
|
638
761
|
label: "error",
|
|
639
|
-
text: error instanceof Error ? error.message : String(error)
|
|
762
|
+
text: error instanceof Error ? error.message : String(error),
|
|
763
|
+
workState: "error"
|
|
640
764
|
});
|
|
641
765
|
}
|
|
642
766
|
finally {
|
|
643
767
|
abortControllerRef.current = null;
|
|
644
768
|
setStatus("idle");
|
|
769
|
+
setWorkState("idle");
|
|
645
770
|
setIsRunning(false);
|
|
646
771
|
}
|
|
647
|
-
}, [appendLine, isRunning, modelOptions, settings]);
|
|
772
|
+
}, [agentMode, appendLine, isRunning, modelOptions, settings]);
|
|
648
773
|
const handleSlashCommand = useCallback(async (rawCommand) => {
|
|
649
774
|
const [commandName = "", ...args] = rawCommand.slice(1).trim().split(/\s+/);
|
|
650
775
|
const command = commandName.toLowerCase();
|
|
@@ -664,16 +789,21 @@ export function App(props) {
|
|
|
664
789
|
return;
|
|
665
790
|
case "build":
|
|
666
791
|
case "plan":
|
|
792
|
+
case "bypass":
|
|
667
793
|
case "mode": {
|
|
668
794
|
const nextMode = command === "mode" ? args[0]?.toLowerCase() : command;
|
|
669
|
-
if (nextMode !== "plan" && nextMode !== "build") {
|
|
795
|
+
if (nextMode !== "plan" && nextMode !== "build" && nextMode !== "bypass") {
|
|
670
796
|
appendLine({
|
|
671
797
|
tone: "accent",
|
|
672
798
|
label: "mode",
|
|
673
|
-
text: `current ${agentMode}. Use /mode plan, /mode build, or press tab.`
|
|
799
|
+
text: `current ${agentMode}. Use /mode plan, /mode build, /mode bypass, or press tab.`
|
|
674
800
|
});
|
|
675
801
|
return;
|
|
676
802
|
}
|
|
803
|
+
if (nextMode === "bypass" && agentMode !== "bypass") {
|
|
804
|
+
requestBypassMode();
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
677
807
|
applyMode(nextMode);
|
|
678
808
|
return;
|
|
679
809
|
}
|
|
@@ -682,7 +812,8 @@ export function App(props) {
|
|
|
682
812
|
appendLine({
|
|
683
813
|
tone: "accent",
|
|
684
814
|
label: "permissions",
|
|
685
|
-
text: `
|
|
815
|
+
text: `mode ${agentMode} | write ${modePermissionLabel(agentMode, "write")} | shell ${modePermissionLabel(agentMode, "shell")} | subagents ${settings.subagents ? "on" : "off"}`,
|
|
816
|
+
detail: modeDescription(agentMode)
|
|
686
817
|
});
|
|
687
818
|
return;
|
|
688
819
|
case "provider": {
|
|
@@ -769,7 +900,7 @@ export function App(props) {
|
|
|
769
900
|
appendLine({
|
|
770
901
|
tone: "accent",
|
|
771
902
|
label: "reasoning",
|
|
772
|
-
text: `current ${settings.reasoningEffort}. Use /reasoning low, medium, high, xhigh, or adaptive.`
|
|
903
|
+
text: `current ${settings.reasoningEffort}. Use /reasoning none, low, medium, high, xhigh, or adaptive.`
|
|
773
904
|
});
|
|
774
905
|
return;
|
|
775
906
|
}
|
|
@@ -783,7 +914,7 @@ export function App(props) {
|
|
|
783
914
|
appendLine({
|
|
784
915
|
tone: "success",
|
|
785
916
|
label: "reasoning",
|
|
786
|
-
text:
|
|
917
|
+
text: formatReasoningSupport(settings.provider, settings.model, nextEffort === "adaptive" ? undefined : nextEffort)
|
|
787
918
|
});
|
|
788
919
|
return;
|
|
789
920
|
}
|
|
@@ -791,32 +922,30 @@ export function App(props) {
|
|
|
791
922
|
case "apply": {
|
|
792
923
|
const writeEnabled = readToggle(args[0], !settings.allowWrite);
|
|
793
924
|
if (writeEnabled) {
|
|
794
|
-
|
|
925
|
+
requestBypassMode();
|
|
926
|
+
return;
|
|
795
927
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
allowWrite: writeEnabled
|
|
799
|
-
}));
|
|
928
|
+
grantedPermissionsRef.current.allowWrite = writeEnabled;
|
|
929
|
+
applyMode("build", false);
|
|
800
930
|
appendLine({
|
|
801
931
|
tone: "success",
|
|
802
932
|
label: "write",
|
|
803
|
-
text:
|
|
933
|
+
text: "workspace writes require approval in build mode"
|
|
804
934
|
});
|
|
805
935
|
return;
|
|
806
936
|
}
|
|
807
937
|
case "shell": {
|
|
808
938
|
const shellEnabled = readToggle(args[0], !settings.allowShell);
|
|
809
939
|
if (shellEnabled) {
|
|
810
|
-
|
|
940
|
+
requestBypassMode();
|
|
941
|
+
return;
|
|
811
942
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
allowShell: shellEnabled
|
|
815
|
-
}));
|
|
943
|
+
grantedPermissionsRef.current.allowShell = shellEnabled;
|
|
944
|
+
applyMode("build", false);
|
|
816
945
|
appendLine({
|
|
817
946
|
tone: "success",
|
|
818
947
|
label: "shell",
|
|
819
|
-
text:
|
|
948
|
+
text: "shell commands require approval in build mode"
|
|
820
949
|
});
|
|
821
950
|
return;
|
|
822
951
|
}
|
|
@@ -848,7 +977,7 @@ export function App(props) {
|
|
|
848
977
|
tone: "warning",
|
|
849
978
|
label: "model",
|
|
850
979
|
text: `No unique model match for "${requestedModel}".`,
|
|
851
|
-
detail: formatModelOptions(
|
|
980
|
+
detail: formatModelOptions(selectableModels(requestedModel, models).slice(0, 12), settings.model)
|
|
852
981
|
});
|
|
853
982
|
return;
|
|
854
983
|
}
|
|
@@ -897,6 +1026,8 @@ export function App(props) {
|
|
|
897
1026
|
});
|
|
898
1027
|
return;
|
|
899
1028
|
}
|
|
1029
|
+
setInput("/models ");
|
|
1030
|
+
setPaletteIndex(0);
|
|
900
1031
|
appendLine({
|
|
901
1032
|
tone: "accent",
|
|
902
1033
|
label: "models",
|
|
@@ -918,9 +1049,65 @@ export function App(props) {
|
|
|
918
1049
|
tone: "accent",
|
|
919
1050
|
label: "status",
|
|
920
1051
|
text: settings.provider === "ollama"
|
|
921
|
-
? `provider ollama | model ${settings.model} | host ${activeHost?.host.deviceName ?? settings.ollamaUrl} | route ${activeHost?.host.url ?? settings.ollamaUrl} | compute ${describeComputeTarget(settings.ollamaUrl).kind} | tools local | agents ${settings.subagents ? "on" : "off"} |
|
|
922
|
-
: `provider ${settings.provider} | model ${settings.model} | host ${settings.provider} api | compute cloud | agents ${settings.subagents ? "on" : "off"} | think ${settings.thinkingMode} | reasoning ${settings.
|
|
1052
|
+
? `provider ollama | model ${settings.model} | host ${activeHost?.host.deviceName ?? settings.ollamaUrl} | route ${activeHost?.host.url ?? settings.ollamaUrl} | compute ${describeComputeTarget(settings.ollamaUrl).kind} | tools local | agents ${settings.subagents ? "on" : "off"} | mode ${agentMode} | write ${modePermissionLabel(agentMode, "write")} | shell ${modePermissionLabel(agentMode, "shell")} | draft ${draftTokens} tok | last ${formatTokens(telemetry)} | session ${formatSessionTokens(sessionTelemetry)} | cost ${formatCost(sessionTelemetry.estimatedCostUsd)}`
|
|
1053
|
+
: `provider ${settings.provider} | model ${settings.model} | host ${settings.provider} api | compute cloud | agents ${settings.subagents ? "on" : "off"} | think ${settings.thinkingMode} | reasoning ${formatReasoningSupport(settings.provider, settings.model, settings.reasoningEffort === "adaptive" ? undefined : settings.reasoningEffort)} | mode ${agentMode} | write ${modePermissionLabel(agentMode, "write")} | shell ${modePermissionLabel(agentMode, "shell")} | draft ${draftTokens} tok | last ${formatTokens(telemetry)} | session ${formatSessionTokens(sessionTelemetry)} | cost ${formatCost(sessionTelemetry.estimatedCostUsd)}`
|
|
1054
|
+
});
|
|
1055
|
+
return;
|
|
1056
|
+
case "sessions": {
|
|
1057
|
+
const sessions = await listWorkspaceSessions(settings.workspace);
|
|
1058
|
+
appendLine({
|
|
1059
|
+
kind: "status",
|
|
1060
|
+
tone: sessions.length > 0 ? "accent" : "muted",
|
|
1061
|
+
label: "sessions",
|
|
1062
|
+
text: sessions.length > 0 ? `Found ${sessions.length} workspace session${sessions.length === 1 ? "" : "s"}.` : "No workspace sessions yet.",
|
|
1063
|
+
detail: sessions
|
|
1064
|
+
.slice(0, 8)
|
|
1065
|
+
.map((session, index) => `${index + 1}. ${session.sessionId} ${session.updatedAt} ${session.lastTask ?? "no task"}`)
|
|
1066
|
+
.join("\n")
|
|
1067
|
+
});
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
case "resume": {
|
|
1071
|
+
const sessionId = args[0] ?? "";
|
|
1072
|
+
const sessions = await listWorkspaceSessions(settings.workspace);
|
|
1073
|
+
const selectedSession = sessionId ? await loadSessionSummary(settings.workspace, sessionId) : sessions[0] ?? null;
|
|
1074
|
+
appendLine({
|
|
1075
|
+
kind: "status",
|
|
1076
|
+
tone: selectedSession ? "accent" : "warning",
|
|
1077
|
+
label: "resume",
|
|
1078
|
+
text: selectedSession ? `Loaded session ${selectedSession.sessionId}` : "No session available to resume.",
|
|
1079
|
+
detail: selectedSession
|
|
1080
|
+
? `workspace ${selectedSession.workspace}\nupdated ${selectedSession.updatedAt}\nmodel ${selectedSession.provider ?? "-"} ${selectedSession.model ?? "-"}\nlast task ${selectedSession.lastTask ?? "-"}`
|
|
1081
|
+
: "Run /sessions after at least one PatchPilot run."
|
|
1082
|
+
});
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
case "diff": {
|
|
1086
|
+
const result = await new WorkspaceTools({
|
|
1087
|
+
root: settings.workspace,
|
|
1088
|
+
allowWrite: false,
|
|
1089
|
+
allowShell: false
|
|
1090
|
+
}).execute({
|
|
1091
|
+
name: "git_diff",
|
|
1092
|
+
arguments: {}
|
|
923
1093
|
});
|
|
1094
|
+
appendLine({
|
|
1095
|
+
kind: "diff",
|
|
1096
|
+
tone: result.ok ? "accent" : "warning",
|
|
1097
|
+
label: "diff",
|
|
1098
|
+
text: result.summary,
|
|
1099
|
+
detail: result.content,
|
|
1100
|
+
tool: "git_diff"
|
|
1101
|
+
});
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
case "approve": {
|
|
1105
|
+
const decision = args[0] === "session" ? "allow_session" : "allow_once";
|
|
1106
|
+
resolveApproval(decision);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
case "deny":
|
|
1110
|
+
resolveApproval("deny");
|
|
924
1111
|
return;
|
|
925
1112
|
case "connect":
|
|
926
1113
|
case "host":
|
|
@@ -940,6 +1127,8 @@ export function App(props) {
|
|
|
940
1127
|
text: "Scanning LAN and Tailscale for Ollama hosts..."
|
|
941
1128
|
});
|
|
942
1129
|
await loadHostSuggestions(true, true);
|
|
1130
|
+
setInput("/connect ");
|
|
1131
|
+
setPaletteIndex(0);
|
|
943
1132
|
return;
|
|
944
1133
|
}
|
|
945
1134
|
if (args.join(" ").trim().toLowerCase() === "local") {
|
|
@@ -965,6 +1154,8 @@ export function App(props) {
|
|
|
965
1154
|
text: "Scanning LAN and Tailscale for Ollama hosts..."
|
|
966
1155
|
});
|
|
967
1156
|
await loadHostSuggestions(true, true);
|
|
1157
|
+
setInput("/connect ");
|
|
1158
|
+
setPaletteIndex(0);
|
|
968
1159
|
return;
|
|
969
1160
|
case "eject": {
|
|
970
1161
|
if (settings.provider !== "ollama") {
|
|
@@ -1044,12 +1235,14 @@ export function App(props) {
|
|
|
1044
1235
|
appendLine,
|
|
1045
1236
|
applyMode,
|
|
1046
1237
|
connectToHost,
|
|
1238
|
+
requestBypassMode,
|
|
1047
1239
|
draftTokens,
|
|
1048
1240
|
exit,
|
|
1049
1241
|
hostOptions,
|
|
1050
1242
|
loadHostSuggestions,
|
|
1051
1243
|
loadProviderModels,
|
|
1052
1244
|
modelOptions,
|
|
1245
|
+
resolveApproval,
|
|
1053
1246
|
sessionTelemetry,
|
|
1054
1247
|
settings,
|
|
1055
1248
|
telemetry
|
|
@@ -1081,7 +1274,10 @@ export function App(props) {
|
|
|
1081
1274
|
await runTask(nextValue);
|
|
1082
1275
|
}, [handleOnboardingSubmit, handleSlashCommand, isRunning, onboarding, paletteIndex, paletteItems, runTask]);
|
|
1083
1276
|
useEffect(() => {
|
|
1084
|
-
|
|
1277
|
+
void sessionStoreRef.current.create();
|
|
1278
|
+
}, []);
|
|
1279
|
+
useEffect(() => {
|
|
1280
|
+
if (!props.initialTask || didRunInitialTask.current || onboarding || process.env.PATCHPILOT_ONBOARDING_COMPLETE !== "1") {
|
|
1085
1281
|
return;
|
|
1086
1282
|
}
|
|
1087
1283
|
didRunInitialTask.current = true;
|
|
@@ -1091,7 +1287,7 @@ export function App(props) {
|
|
|
1091
1287
|
setPaletteIndex(0);
|
|
1092
1288
|
}, [hostOptions, input, modelOptions, onboarding, settings.model, settings.provider]);
|
|
1093
1289
|
useEffect(() => {
|
|
1094
|
-
if (didOpenDefaultOnboarding.current ||
|
|
1290
|
+
if (didOpenDefaultOnboarding.current || onboarding || process.env.PATCHPILOT_ONBOARDING_COMPLETE === "1") {
|
|
1095
1291
|
return;
|
|
1096
1292
|
}
|
|
1097
1293
|
didOpenDefaultOnboarding.current = true;
|
|
@@ -1109,6 +1305,10 @@ export function App(props) {
|
|
|
1109
1305
|
}
|
|
1110
1306
|
let cancelled = false;
|
|
1111
1307
|
async function syncActiveHost() {
|
|
1308
|
+
if (activeHostSyncInFlightRef.current) {
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
activeHostSyncInFlightRef.current = true;
|
|
1112
1312
|
const verifiedHost = await checkOllamaHost(settings.ollamaUrl, {
|
|
1113
1313
|
timeoutMs: 800
|
|
1114
1314
|
});
|
|
@@ -1116,6 +1316,7 @@ export function App(props) {
|
|
|
1116
1316
|
if (!cancelled) {
|
|
1117
1317
|
setActiveHost((currentHost) => (currentHost?.host.url === settings.ollamaUrl ? currentHost : null));
|
|
1118
1318
|
}
|
|
1319
|
+
activeHostSyncInFlightRef.current = false;
|
|
1119
1320
|
return;
|
|
1120
1321
|
}
|
|
1121
1322
|
const details = await readOllamaHostDetails(verifiedHost).catch(() => ({
|
|
@@ -1124,6 +1325,7 @@ export function App(props) {
|
|
|
1124
1325
|
runningModels: [],
|
|
1125
1326
|
fetchedAt: Date.now()
|
|
1126
1327
|
}));
|
|
1328
|
+
activeHostSyncInFlightRef.current = false;
|
|
1127
1329
|
if (cancelled) {
|
|
1128
1330
|
return;
|
|
1129
1331
|
}
|
|
@@ -1147,16 +1349,51 @@ export function App(props) {
|
|
|
1147
1349
|
}
|
|
1148
1350
|
const trimmedInput = input.trim();
|
|
1149
1351
|
if (settings.provider === "ollama" && (trimmedInput === "/connect" || trimmedInput === "/hosts") && hostOptions.length === 0 && !isLoadingHosts) {
|
|
1150
|
-
|
|
1352
|
+
const key = `${settings.provider}:${settings.ollamaUrl}:${trimmedInput}:hosts`;
|
|
1353
|
+
if (!autoLoadKeysRef.current.has(key)) {
|
|
1354
|
+
autoLoadKeysRef.current.add(key);
|
|
1355
|
+
void loadHostSuggestions(false, false);
|
|
1356
|
+
}
|
|
1151
1357
|
}
|
|
1152
1358
|
if ((trimmedInput === "/models" || trimmedInput === "/model") && modelOptions.length === 0 && !isLoadingModels) {
|
|
1153
|
-
|
|
1359
|
+
const key = `${settings.provider}:${settings.ollamaUrl}:${trimmedInput}:models`;
|
|
1360
|
+
if (!autoLoadKeysRef.current.has(key)) {
|
|
1361
|
+
autoLoadKeysRef.current.add(key);
|
|
1362
|
+
void loadProviderModels(false);
|
|
1363
|
+
}
|
|
1154
1364
|
}
|
|
1155
1365
|
}, [hostOptions.length, input, isLoadingHosts, isLoadingModels, isRunning, loadHostSuggestions, loadProviderModels, modelOptions.length, onboarding, settings.provider]);
|
|
1156
1366
|
useInput((inputValue, key) => {
|
|
1367
|
+
if (bypassConfirmation) {
|
|
1368
|
+
const normalizedInput = inputValue.toLowerCase();
|
|
1369
|
+
if (normalizedInput === "y") {
|
|
1370
|
+
confirmBypassMode();
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
if (normalizedInput === "n" || key.escape) {
|
|
1374
|
+
cancelBypassMode();
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
if (pendingApproval) {
|
|
1379
|
+
const normalizedInput = inputValue.toLowerCase();
|
|
1380
|
+
if (normalizedInput === "y") {
|
|
1381
|
+
resolveApproval("allow_once");
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (normalizedInput === "a") {
|
|
1385
|
+
resolveApproval("allow_session");
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
if (normalizedInput === "n" || key.escape) {
|
|
1389
|
+
resolveApproval("deny");
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1157
1393
|
if (isRunning && key.escape) {
|
|
1158
1394
|
abortControllerRef.current?.abort();
|
|
1159
1395
|
appendLine({
|
|
1396
|
+
kind: "status",
|
|
1160
1397
|
tone: "warning",
|
|
1161
1398
|
label: "stop",
|
|
1162
1399
|
text: "Stopping current task..."
|
|
@@ -1169,7 +1406,10 @@ export function App(props) {
|
|
|
1169
1406
|
goBackOnboarding();
|
|
1170
1407
|
return;
|
|
1171
1408
|
}
|
|
1172
|
-
|
|
1409
|
+
if (onboardingBusyMessage) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
const optionCount = onboarding.step === "model" ? selectableModels(onboardingInput, onboarding.models).length : getOnboardingOptionCount(onboarding);
|
|
1173
1413
|
if (optionCount > 0 && key.upArrow) {
|
|
1174
1414
|
setOnboardingIndex((currentIndex) => (currentIndex - 1 + optionCount) % optionCount);
|
|
1175
1415
|
return;
|
|
@@ -1277,7 +1517,7 @@ export function App(props) {
|
|
|
1277
1517
|
clearInterval(timer);
|
|
1278
1518
|
};
|
|
1279
1519
|
}, []);
|
|
1280
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: rootHeight, overflowY: "hidden", children: [_jsx(Header, { model: settings.model, provider: settings.provider, workspace: settings.workspace, status: status, allowWrite: settings.allowWrite, allowShell: settings.allowShell, agentMode: agentMode, subagents: settings.subagents, thinkingMode: settings.thinkingMode, reasoningEffort: settings.reasoningEffort, ollamaUrl: settings.ollamaUrl, telemetry: telemetry, sessionTelemetry: sessionTelemetry, draftTokens: draftTokens, systemStats: systemStats, gpuStats: gpuStats, activeHost: activeHost }), onboarding ? (_jsx(OnboardingPanel, { state: onboarding, height: panelHeight, selectedIndex: onboardingIndex, input: onboardingInput, busyMessage: onboardingBusyMessage, onInputChange: setOnboardingInput, onInputSubmit: (value) => void handleOnboardingSubmit(value) })) : (_jsxs(Box, { flexDirection: "row", height: panelHeight + composerReservedHeight + paletteReservedHeight + footerReservedHeight, overflowY: "hidden", children: [_jsx(Sidebar, { workspace: settings.workspace, model: settings.model, provider: settings.provider, ollamaUrl: settings.ollamaUrl, agentMode: agentMode, allowWrite: settings.allowWrite, allowShell: settings.allowShell, subagents: settings.subagents, telemetry: telemetry, sessionTelemetry: sessionTelemetry, draftTokens: draftTokens, height: panelHeight, scrollOffset: sessionScrollOffset, advisors: advisorNotes, isActive: activeScrollPane === "session", activeHost: activeHost }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, height: panelHeight + composerReservedHeight + paletteReservedHeight + footerReservedHeight, overflowY: "hidden", children: [_jsx(Transcript, { lines: lines, isRunning: isRunning, isActive: activeScrollPane === "transcript", height: panelHeight, width: transcriptWidth, scrollOffset: transcriptScrollOffset }), _jsx(Composer, { input: input, isRunning: isRunning, status: status, draftTokens: draftTokens, onChange: setInput, onSubmit: (value) => void handleSubmit(value) }), paletteItems.length > 0 ? _jsx(CommandSuggestions, { items: paletteItems, selectedIndex: paletteIndex }) : null, _jsx(FooterHints, { activePane: activeScrollPane })] })] }))] }));
|
|
1520
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: rootHeight, overflowY: "hidden", children: [_jsx(Header, { model: settings.model, provider: settings.provider, workspace: settings.workspace, status: status, workState: workState, allowWrite: settings.allowWrite, allowShell: settings.allowShell, agentMode: agentMode, subagents: settings.subagents, thinkingMode: settings.thinkingMode, reasoningEffort: settings.reasoningEffort, ollamaUrl: settings.ollamaUrl, telemetry: telemetry, sessionTelemetry: sessionTelemetry, draftTokens: draftTokens, systemStats: systemStats, gpuStats: gpuStats, activeHost: activeHost }), onboarding ? (_jsx(OnboardingPanel, { state: onboarding, height: panelHeight, selectedIndex: onboardingIndex, input: onboardingInput, busyMessage: onboardingBusyMessage, notice: onboardingNotice, onInputChange: setOnboardingInput, onInputSubmit: (value) => void handleOnboardingSubmit(value) })) : (_jsxs(Box, { flexDirection: "row", height: panelHeight + composerReservedHeight + paletteReservedHeight + footerReservedHeight, overflowY: "hidden", children: [_jsx(Sidebar, { workspace: settings.workspace, model: settings.model, provider: settings.provider, ollamaUrl: settings.ollamaUrl, agentMode: agentMode, allowWrite: settings.allowWrite, allowShell: settings.allowShell, subagents: settings.subagents, workState: workState, sessionId: sessionStoreRef.current.sessionId, systemStats: systemStats, gpuStats: gpuStats, telemetry: telemetry, sessionTelemetry: sessionTelemetry, draftTokens: draftTokens, height: panelHeight, scrollOffset: sessionScrollOffset, advisors: advisorNotes, isActive: activeScrollPane === "session", activeHost: activeHost }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, height: panelHeight + composerReservedHeight + paletteReservedHeight + footerReservedHeight, overflowY: "hidden", children: [_jsx(Transcript, { lines: lines, isRunning: isRunning, isActive: activeScrollPane === "transcript", height: panelHeight, width: transcriptWidth, scrollOffset: transcriptScrollOffset }), _jsx(Composer, { input: input, isRunning: isRunning, status: status, draftTokens: draftTokens, onChange: setInput, onSubmit: (value) => void handleSubmit(value) }), paletteItems.length > 0 ? _jsx(CommandSuggestions, { items: paletteItems, selectedIndex: paletteIndex }) : null, _jsx(FooterHints, { activePane: activeScrollPane })] })] }))] }));
|
|
1281
1521
|
}
|
|
1282
1522
|
async function loadAvailableModels(provider, ollamaUrl, setModelOptions, refresh = false) {
|
|
1283
1523
|
const cacheKey = `${provider}:${provider === "ollama" ? ollamaUrl : "default"}`;
|
|
@@ -1451,7 +1691,7 @@ function buildCommandSuggestionItems(options) {
|
|
|
1451
1691
|
});
|
|
1452
1692
|
}
|
|
1453
1693
|
else {
|
|
1454
|
-
items.unshift(...
|
|
1694
|
+
items.unshift(...selectableModels(modelQuery, options.modelOptions).slice(0, 8).map((model) => ({
|
|
1455
1695
|
key: `model-${model}`,
|
|
1456
1696
|
category: "model",
|
|
1457
1697
|
label: model,
|
|
@@ -1470,7 +1710,7 @@ function getOnboardingOptionCount(onboarding) {
|
|
|
1470
1710
|
case "host":
|
|
1471
1711
|
return onboarding.hosts.length + 1;
|
|
1472
1712
|
case "api-key-choice":
|
|
1473
|
-
return 2;
|
|
1713
|
+
return onboarding.hasExistingKey ? 2 : 1;
|
|
1474
1714
|
case "model":
|
|
1475
1715
|
return onboarding.models.length;
|
|
1476
1716
|
default:
|
|
@@ -1525,7 +1765,7 @@ function selectModelFromInput(value, models, selectedIndex, options = {}) {
|
|
|
1525
1765
|
if (models.includes(normalizedValue)) {
|
|
1526
1766
|
return normalizedValue;
|
|
1527
1767
|
}
|
|
1528
|
-
const matches =
|
|
1768
|
+
const matches = selectableModels(normalizedValue, models);
|
|
1529
1769
|
if (matches.length === 1) {
|
|
1530
1770
|
return matches[0] ?? null;
|
|
1531
1771
|
}
|
|
@@ -1534,34 +1774,6 @@ function selectModelFromInput(value, models, selectedIndex, options = {}) {
|
|
|
1534
1774
|
function isPlausibleCloudModelId(value) {
|
|
1535
1775
|
return /^[A-Za-z0-9][A-Za-z0-9._:/+-]*$/.test(value) && value.length >= 3;
|
|
1536
1776
|
}
|
|
1537
|
-
function filterModelOptions(query, models) {
|
|
1538
|
-
const normalizedQuery = query.trim().toLowerCase();
|
|
1539
|
-
if (!normalizedQuery) {
|
|
1540
|
-
return models;
|
|
1541
|
-
}
|
|
1542
|
-
return models
|
|
1543
|
-
.map((model) => ({
|
|
1544
|
-
model,
|
|
1545
|
-
score: scoreModelMatch(model, normalizedQuery)
|
|
1546
|
-
}))
|
|
1547
|
-
.filter((item) => item.score !== null)
|
|
1548
|
-
.sort((left, right) => left.score - right.score || left.model.localeCompare(right.model))
|
|
1549
|
-
.map((item) => item.model);
|
|
1550
|
-
}
|
|
1551
|
-
function scoreModelMatch(model, query) {
|
|
1552
|
-
const normalizedModel = model.toLowerCase();
|
|
1553
|
-
if (normalizedModel === query) {
|
|
1554
|
-
return 0;
|
|
1555
|
-
}
|
|
1556
|
-
if (normalizedModel.startsWith(query)) {
|
|
1557
|
-
return 1;
|
|
1558
|
-
}
|
|
1559
|
-
if (normalizedModel.includes(query)) {
|
|
1560
|
-
return 2 + normalizedModel.indexOf(query) / 1000;
|
|
1561
|
-
}
|
|
1562
|
-
const tokens = query.split(/[\s/:_-]+/).filter(Boolean);
|
|
1563
|
-
return tokens.length > 0 && tokens.every((token) => normalizedModel.includes(token)) ? 10 : null;
|
|
1564
|
-
}
|
|
1565
1777
|
function defaultModelForProvider(provider, currentModel) {
|
|
1566
1778
|
if (provider === "nvidia") {
|
|
1567
1779
|
return currentModel.includes("/") && !currentModel.startsWith("openrouter/") ? currentModel : defaultNvidiaModel;
|
|
@@ -1633,7 +1845,7 @@ async function ejectOllamaModels(options) {
|
|
|
1633
1845
|
return ejected;
|
|
1634
1846
|
}
|
|
1635
1847
|
function isReasoningEffort(value) {
|
|
1636
|
-
return value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "adaptive";
|
|
1848
|
+
return value === "none" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "adaptive";
|
|
1637
1849
|
}
|
|
1638
1850
|
function upsertAdvisorNote(notes, nextNote) {
|
|
1639
1851
|
const nextNotes = notes.filter((note) => note.role !== nextNote.role);
|
|
@@ -1643,46 +1855,75 @@ function eventToLine(event) {
|
|
|
1643
1855
|
switch (event.type) {
|
|
1644
1856
|
case "status":
|
|
1645
1857
|
return {
|
|
1858
|
+
kind: "status",
|
|
1646
1859
|
tone: "muted",
|
|
1647
|
-
label:
|
|
1648
|
-
text: event.message
|
|
1860
|
+
label: event.workState,
|
|
1861
|
+
text: event.message,
|
|
1862
|
+
workState: event.workState
|
|
1649
1863
|
};
|
|
1650
1864
|
case "assistant":
|
|
1651
1865
|
return {
|
|
1866
|
+
kind: "assistant",
|
|
1652
1867
|
tone: "accent",
|
|
1653
1868
|
label: "pilot",
|
|
1654
|
-
text: event.message
|
|
1869
|
+
text: event.message,
|
|
1870
|
+
workState: event.workState
|
|
1655
1871
|
};
|
|
1656
1872
|
case "subagent":
|
|
1657
1873
|
return {
|
|
1874
|
+
kind: "assistant",
|
|
1658
1875
|
tone: "accent",
|
|
1659
1876
|
label: event.role,
|
|
1660
1877
|
text: "advisor brief updated",
|
|
1661
|
-
detail: event.message
|
|
1878
|
+
detail: event.message,
|
|
1879
|
+
workState: event.workState
|
|
1662
1880
|
};
|
|
1663
1881
|
case "tool":
|
|
1664
1882
|
return {
|
|
1883
|
+
kind: event.name === "git_diff" ? "diff" : "tool",
|
|
1665
1884
|
tone: event.ok ? "success" : "warning",
|
|
1666
1885
|
label: event.name,
|
|
1667
|
-
text: event.summary
|
|
1886
|
+
text: event.summary,
|
|
1887
|
+
workState: event.workState,
|
|
1888
|
+
tool: event.name,
|
|
1889
|
+
toolCallId: event.toolCallId,
|
|
1890
|
+
category: event.category,
|
|
1891
|
+
preview: event.preview
|
|
1892
|
+
};
|
|
1893
|
+
case "approval":
|
|
1894
|
+
return {
|
|
1895
|
+
kind: "approval",
|
|
1896
|
+
tone: event.decision === "deny" ? "warning" : "success",
|
|
1897
|
+
label: "approval",
|
|
1898
|
+
text: `${event.request.tool} ${event.decision.replace("_", " ")}`,
|
|
1899
|
+
detail: event.request.preview,
|
|
1900
|
+
workState: event.workState,
|
|
1901
|
+
tool: event.request.tool,
|
|
1902
|
+
preview: event.request.preview
|
|
1668
1903
|
};
|
|
1669
1904
|
case "final":
|
|
1670
1905
|
return {
|
|
1906
|
+
kind: "final",
|
|
1671
1907
|
tone: "success",
|
|
1672
1908
|
label: "final",
|
|
1673
|
-
text: event.message
|
|
1909
|
+
text: event.message,
|
|
1910
|
+
workState: event.workState
|
|
1674
1911
|
};
|
|
1675
1912
|
case "error":
|
|
1676
1913
|
return {
|
|
1914
|
+
kind: "error",
|
|
1677
1915
|
tone: "danger",
|
|
1678
1916
|
label: "error",
|
|
1679
|
-
text: event.message
|
|
1917
|
+
text: event.message,
|
|
1918
|
+
workState: event.workState
|
|
1680
1919
|
};
|
|
1681
1920
|
case "metrics":
|
|
1682
1921
|
return {
|
|
1922
|
+
kind: "status",
|
|
1683
1923
|
tone: "muted",
|
|
1684
1924
|
label: "metrics",
|
|
1685
|
-
text: formatTokens(event.metrics)
|
|
1925
|
+
text: formatTokens(event.metrics),
|
|
1926
|
+
workState: event.workState
|
|
1686
1927
|
};
|
|
1687
1928
|
}
|
|
1688
1929
|
}
|
|
@@ -1696,8 +1937,26 @@ function eventToStatus(event) {
|
|
|
1696
1937
|
if (event.type === "subagent") {
|
|
1697
1938
|
return `${event.role} subagent`;
|
|
1698
1939
|
}
|
|
1940
|
+
if (event.type === "approval") {
|
|
1941
|
+
return `${event.request.tool}: ${event.decision.replace("_", " ")}`;
|
|
1942
|
+
}
|
|
1699
1943
|
return event.type;
|
|
1700
1944
|
}
|
|
1945
|
+
function defaultLogKind(line) {
|
|
1946
|
+
if (line.kind) {
|
|
1947
|
+
return line.kind;
|
|
1948
|
+
}
|
|
1949
|
+
if (line.label === "you") {
|
|
1950
|
+
return "user";
|
|
1951
|
+
}
|
|
1952
|
+
if (line.label === "error") {
|
|
1953
|
+
return "error";
|
|
1954
|
+
}
|
|
1955
|
+
if (line.label === "final") {
|
|
1956
|
+
return "final";
|
|
1957
|
+
}
|
|
1958
|
+
return "status";
|
|
1959
|
+
}
|
|
1701
1960
|
function formatHostOptions(hosts) {
|
|
1702
1961
|
return hosts
|
|
1703
1962
|
.map((host, index) => {
|