@jx-grxf/patchpilot 0.2.0 → 0.3.0
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 +67 -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 +5 -2
- package/dist/core/agent.js +167 -24
- 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 +13 -22
- 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 +495 -13
- package/dist/core/workspace.js.map +1 -1
- package/dist/tui/App.js +291 -88
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/commands.js +37 -2
- package/dist/tui/commands.js.map +1 -1
- package/dist/tui/components/Header.d.ts +2 -2
- package/dist/tui/components/Header.js +17 -54
- 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 +15 -6
- 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/types.d.ts +12 -2
- 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/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,7 @@ 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 { selectableModels } from "./modelSelection.js";
|
|
24
28
|
import { readGpuStats, readSystemStats } from "./systemStats.js";
|
|
25
29
|
import { maxTranscriptLines } from "./types.js";
|
|
26
30
|
const modelCacheTtlMs = 5 * 60_000;
|
|
@@ -32,11 +36,21 @@ export function App(props) {
|
|
|
32
36
|
const didRunInitialTask = useRef(false);
|
|
33
37
|
const didOpenDefaultOnboarding = useRef(false);
|
|
34
38
|
const abortControllerRef = useRef(null);
|
|
39
|
+
const sessionStoreRef = useRef(new SessionStore({ workspace: props.workspace }));
|
|
40
|
+
const approvalResolverRef = useRef(null);
|
|
41
|
+
const grantedPermissionsRef = useRef({
|
|
42
|
+
allowWrite: props.allowWrite,
|
|
43
|
+
allowShell: props.allowShell
|
|
44
|
+
});
|
|
45
|
+
const activeHostSyncInFlightRef = useRef(false);
|
|
46
|
+
const autoLoadKeysRef = useRef(new Set());
|
|
35
47
|
const usedOllamaModelsRef = useRef(new Set());
|
|
36
48
|
const [lines, setLines] = useState([]);
|
|
37
49
|
const [advisorNotes, setAdvisorNotes] = useState([]);
|
|
38
50
|
const [isRunning, setIsRunning] = useState(false);
|
|
39
51
|
const [status, setStatus] = useState("idle");
|
|
52
|
+
const [workState, setWorkState] = useState("idle");
|
|
53
|
+
const [pendingApproval, setPendingApproval] = useState(null);
|
|
40
54
|
const [telemetry, setTelemetry] = useState(null);
|
|
41
55
|
const [sessionTelemetry, setSessionTelemetry] = useState(() => emptySessionTelemetry());
|
|
42
56
|
const [systemStats, setSystemStats] = useState(() => readSystemStats().stats);
|
|
@@ -51,6 +65,7 @@ export function App(props) {
|
|
|
51
65
|
const [onboardingIndex, setOnboardingIndex] = useState(0);
|
|
52
66
|
const [onboardingInput, setOnboardingInput] = useState("");
|
|
53
67
|
const [onboardingBusyMessage, setOnboardingBusyMessage] = useState(null);
|
|
68
|
+
const [onboardingNotice, setOnboardingNotice] = useState(null);
|
|
54
69
|
const [paletteIndex, setPaletteIndex] = useState(0);
|
|
55
70
|
const [activeScrollPane, setActiveScrollPane] = useState("transcript");
|
|
56
71
|
const [transcriptScrollOffset, setTranscriptScrollOffset] = useState(0);
|
|
@@ -82,7 +97,7 @@ export function App(props) {
|
|
|
82
97
|
})
|
|
83
98
|
: [];
|
|
84
99
|
const rootHeight = Math.max(24, terminalRows);
|
|
85
|
-
const headerReservedHeight =
|
|
100
|
+
const headerReservedHeight = 5;
|
|
86
101
|
const paletteReservedHeight = !onboarding && paletteItems.length > 0 ? Math.min(8, paletteItems.length) + 4 : 0;
|
|
87
102
|
const composerReservedHeight = onboarding ? 0 : 2;
|
|
88
103
|
const footerReservedHeight = onboarding ? 0 : 1;
|
|
@@ -94,16 +109,34 @@ export function App(props) {
|
|
|
94
109
|
...currentLines.slice(-maxTranscriptLines),
|
|
95
110
|
{
|
|
96
111
|
...line,
|
|
112
|
+
kind: line.kind ?? defaultLogKind(line),
|
|
97
113
|
id: Date.now() + Math.random()
|
|
98
114
|
}
|
|
99
115
|
]);
|
|
100
116
|
}, []);
|
|
117
|
+
const resolveApproval = useCallback((decision) => {
|
|
118
|
+
if (!pendingApproval || !approvalResolverRef.current) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
approvalResolverRef.current(decision);
|
|
122
|
+
approvalResolverRef.current = null;
|
|
123
|
+
appendLine({
|
|
124
|
+
kind: "approval",
|
|
125
|
+
tone: decision === "deny" ? "warning" : "success",
|
|
126
|
+
label: "approval",
|
|
127
|
+
text: `${pendingApproval.tool} ${decision.replace("_", " ")}`,
|
|
128
|
+
detail: pendingApproval.preview,
|
|
129
|
+
workState: "waiting_approval",
|
|
130
|
+
tool: pendingApproval.tool
|
|
131
|
+
});
|
|
132
|
+
setPendingApproval(null);
|
|
133
|
+
}, [appendLine, pendingApproval]);
|
|
101
134
|
const applyMode = useCallback((nextMode, announce = true) => {
|
|
102
135
|
setAgentMode(nextMode);
|
|
103
136
|
setSettings((currentSettings) => ({
|
|
104
137
|
...currentSettings,
|
|
105
|
-
allowWrite: nextMode === "build" ?
|
|
106
|
-
allowShell: nextMode === "build" ?
|
|
138
|
+
allowWrite: nextMode === "build" ? grantedPermissionsRef.current.allowWrite : false,
|
|
139
|
+
allowShell: nextMode === "build" ? grantedPermissionsRef.current.allowShell : false
|
|
107
140
|
}));
|
|
108
141
|
if (announce) {
|
|
109
142
|
appendLine({
|
|
@@ -217,6 +250,8 @@ export function App(props) {
|
|
|
217
250
|
}, [appendLine, settings.model]);
|
|
218
251
|
const openModelSelection = useCallback(async (provider, options = {}) => {
|
|
219
252
|
setTelemetry(null);
|
|
253
|
+
setOnboardingInput("");
|
|
254
|
+
setOnboardingNotice(null);
|
|
220
255
|
setOnboardingBusyMessage(`Loading ${provider} models...`);
|
|
221
256
|
const nextModel = defaultModelForProvider(provider, options.currentModel ?? settings.model);
|
|
222
257
|
setSettings((currentSettings) => ({
|
|
@@ -227,16 +262,16 @@ export function App(props) {
|
|
|
227
262
|
try {
|
|
228
263
|
const models = await loadAvailableModels(provider, options.ollamaUrl ?? settings.ollamaUrl, setModelOptions, true);
|
|
229
264
|
if (models.length === 0) {
|
|
230
|
-
|
|
265
|
+
setOnboardingNotice({
|
|
231
266
|
tone: "warning",
|
|
232
|
-
label: "onboarding",
|
|
233
267
|
text: provider === "ollama"
|
|
234
268
|
? "No Ollama models found on that host."
|
|
235
269
|
: provider === "gemini"
|
|
236
270
|
? "No Gemini models listed. Check the API key."
|
|
237
271
|
: provider === "openrouter"
|
|
238
272
|
? "No OpenRouter models listed. Check the API key."
|
|
239
|
-
: "No Codex OAuth models listed."
|
|
273
|
+
: "No Codex OAuth models listed.",
|
|
274
|
+
detail: "Use the back key to choose another provider or retry after fixing the provider setup."
|
|
240
275
|
});
|
|
241
276
|
return;
|
|
242
277
|
}
|
|
@@ -249,21 +284,22 @@ export function App(props) {
|
|
|
249
284
|
setOnboardingIndex(0);
|
|
250
285
|
}
|
|
251
286
|
catch (error) {
|
|
252
|
-
|
|
287
|
+
setOnboardingNotice({
|
|
253
288
|
tone: "danger",
|
|
254
|
-
|
|
255
|
-
|
|
289
|
+
text: error instanceof Error ? error.message : String(error),
|
|
290
|
+
detail: "Fix the provider setup, then press Enter or go back and retry."
|
|
256
291
|
});
|
|
257
292
|
}
|
|
258
293
|
finally {
|
|
259
294
|
setOnboardingBusyMessage(null);
|
|
260
295
|
}
|
|
261
|
-
}, [
|
|
296
|
+
}, [settings.model, settings.ollamaUrl]);
|
|
262
297
|
const closeOnboarding = useCallback(() => {
|
|
263
298
|
setOnboarding(null);
|
|
264
299
|
setOnboardingIndex(0);
|
|
265
300
|
setOnboardingInput("");
|
|
266
301
|
setOnboardingBusyMessage(null);
|
|
302
|
+
setOnboardingNotice(null);
|
|
267
303
|
}, []);
|
|
268
304
|
const goBackOnboarding = useCallback(() => {
|
|
269
305
|
if (!onboarding) {
|
|
@@ -271,6 +307,7 @@ export function App(props) {
|
|
|
271
307
|
}
|
|
272
308
|
setOnboardingBusyMessage(null);
|
|
273
309
|
setOnboardingInput("");
|
|
310
|
+
setOnboardingNotice(null);
|
|
274
311
|
setOnboardingIndex(0);
|
|
275
312
|
switch (onboarding.step) {
|
|
276
313
|
case "entry":
|
|
@@ -327,6 +364,10 @@ export function App(props) {
|
|
|
327
364
|
if (!onboarding) {
|
|
328
365
|
return;
|
|
329
366
|
}
|
|
367
|
+
if (onboardingBusyMessage) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
setOnboardingNotice(null);
|
|
330
371
|
if (onboarding.step === "entry") {
|
|
331
372
|
const selection = readEntrySelection(value, onboardingIndex);
|
|
332
373
|
if (!selection) {
|
|
@@ -343,7 +384,12 @@ export function App(props) {
|
|
|
343
384
|
details = startedHost ? await connectToHost(startedHost, { announce: false }) : null;
|
|
344
385
|
}
|
|
345
386
|
if (!details) {
|
|
346
|
-
setOnboardingBusyMessage(
|
|
387
|
+
setOnboardingBusyMessage(null);
|
|
388
|
+
setOnboardingNotice({
|
|
389
|
+
tone: "warning",
|
|
390
|
+
text: "Local Ollama is not reachable.",
|
|
391
|
+
detail: "Start Ollama.app or run `ollama serve`, then press Enter to retry."
|
|
392
|
+
});
|
|
347
393
|
return;
|
|
348
394
|
}
|
|
349
395
|
await openModelSelection("ollama", {
|
|
@@ -394,9 +440,8 @@ export function App(props) {
|
|
|
394
440
|
}
|
|
395
441
|
const selectedHost = onboarding.hosts[selectionIndex - 1];
|
|
396
442
|
if (!selectedHost) {
|
|
397
|
-
|
|
443
|
+
setOnboardingNotice({
|
|
398
444
|
tone: "warning",
|
|
399
|
-
label: "onboarding",
|
|
400
445
|
text: "Unknown host selection."
|
|
401
446
|
});
|
|
402
447
|
return;
|
|
@@ -407,6 +452,11 @@ export function App(props) {
|
|
|
407
452
|
});
|
|
408
453
|
if (!details) {
|
|
409
454
|
setOnboardingBusyMessage(null);
|
|
455
|
+
setOnboardingNotice({
|
|
456
|
+
tone: "warning",
|
|
457
|
+
text: `No Ollama server answered at ${selectedHost.url}.`,
|
|
458
|
+
detail: "Check firewall, MagicDNS/IP, and whether Ollama is listening on that machine."
|
|
459
|
+
});
|
|
410
460
|
return;
|
|
411
461
|
}
|
|
412
462
|
await openModelSelection("ollama", {
|
|
@@ -418,9 +468,8 @@ export function App(props) {
|
|
|
418
468
|
if (onboarding.step === "host-input") {
|
|
419
469
|
const hostValue = value.trim();
|
|
420
470
|
if (!hostValue) {
|
|
421
|
-
|
|
471
|
+
setOnboardingNotice({
|
|
422
472
|
tone: "warning",
|
|
423
|
-
label: "onboarding",
|
|
424
473
|
text: "Host cannot be empty."
|
|
425
474
|
});
|
|
426
475
|
return;
|
|
@@ -431,6 +480,11 @@ export function App(props) {
|
|
|
431
480
|
});
|
|
432
481
|
if (!details) {
|
|
433
482
|
setOnboardingBusyMessage(null);
|
|
483
|
+
setOnboardingNotice({
|
|
484
|
+
tone: "warning",
|
|
485
|
+
text: `No Ollama server answered at ${hostValue}.`,
|
|
486
|
+
detail: "Check the IP, MagicDNS name, firewall rules, and whether Ollama is running."
|
|
487
|
+
});
|
|
434
488
|
return;
|
|
435
489
|
}
|
|
436
490
|
await openModelSelection("ollama", {
|
|
@@ -460,9 +514,8 @@ export function App(props) {
|
|
|
460
514
|
if (onboarding.step === "gemini-key") {
|
|
461
515
|
const apiKey = value.trim();
|
|
462
516
|
if (!apiKey) {
|
|
463
|
-
|
|
517
|
+
setOnboardingNotice({
|
|
464
518
|
tone: "warning",
|
|
465
|
-
label: "onboarding",
|
|
466
519
|
text: "Gemini API key cannot be empty."
|
|
467
520
|
});
|
|
468
521
|
return;
|
|
@@ -473,9 +526,8 @@ export function App(props) {
|
|
|
473
526
|
PATCHPILOT_MODEL: defaultGeminiModel,
|
|
474
527
|
GEMINI_API_KEY: apiKey
|
|
475
528
|
});
|
|
476
|
-
|
|
529
|
+
setOnboardingNotice({
|
|
477
530
|
tone: "success",
|
|
478
|
-
label: "onboarding",
|
|
479
531
|
text: "Gemini API key saved to PatchPilot config."
|
|
480
532
|
});
|
|
481
533
|
await openModelSelection("gemini", {
|
|
@@ -486,9 +538,8 @@ export function App(props) {
|
|
|
486
538
|
if (onboarding.step === "openrouter-key") {
|
|
487
539
|
const apiKey = value.trim();
|
|
488
540
|
if (!apiKey) {
|
|
489
|
-
|
|
541
|
+
setOnboardingNotice({
|
|
490
542
|
tone: "warning",
|
|
491
|
-
label: "onboarding",
|
|
492
543
|
text: "OpenRouter API key cannot be empty."
|
|
493
544
|
});
|
|
494
545
|
return;
|
|
@@ -499,9 +550,8 @@ export function App(props) {
|
|
|
499
550
|
PATCHPILOT_MODEL: defaultOpenRouterModel,
|
|
500
551
|
OPENROUTER_API_KEY: apiKey
|
|
501
552
|
});
|
|
502
|
-
|
|
553
|
+
setOnboardingNotice({
|
|
503
554
|
tone: "success",
|
|
504
|
-
label: "onboarding",
|
|
505
555
|
text: "OpenRouter API key saved to PatchPilot config."
|
|
506
556
|
});
|
|
507
557
|
await openModelSelection("openrouter", {
|
|
@@ -512,9 +562,8 @@ export function App(props) {
|
|
|
512
562
|
if (onboarding.step === "nvidia-key") {
|
|
513
563
|
const apiKey = value.trim();
|
|
514
564
|
if (!apiKey) {
|
|
515
|
-
|
|
565
|
+
setOnboardingNotice({
|
|
516
566
|
tone: "warning",
|
|
517
|
-
label: "onboarding",
|
|
518
567
|
text: "NVIDIA API key cannot be empty."
|
|
519
568
|
});
|
|
520
569
|
return;
|
|
@@ -525,9 +574,8 @@ export function App(props) {
|
|
|
525
574
|
PATCHPILOT_MODEL: defaultNvidiaModel,
|
|
526
575
|
NVIDIA_API_KEY: apiKey
|
|
527
576
|
});
|
|
528
|
-
|
|
577
|
+
setOnboardingNotice({
|
|
529
578
|
tone: "success",
|
|
530
|
-
label: "onboarding",
|
|
531
579
|
text: "NVIDIA API key saved to PatchPilot config."
|
|
532
580
|
});
|
|
533
581
|
await openModelSelection("nvidia", {
|
|
@@ -537,10 +585,10 @@ export function App(props) {
|
|
|
537
585
|
}
|
|
538
586
|
if (onboarding.step === "codex-login") {
|
|
539
587
|
if (!hasCodexCliOAuth()) {
|
|
540
|
-
|
|
588
|
+
setOnboardingNotice({
|
|
541
589
|
tone: "warning",
|
|
542
|
-
|
|
543
|
-
|
|
590
|
+
text: "Codex OAuth is still missing.",
|
|
591
|
+
detail: "Run `codex login` in another terminal, then press Enter to retry."
|
|
544
592
|
});
|
|
545
593
|
return;
|
|
546
594
|
}
|
|
@@ -549,14 +597,13 @@ export function App(props) {
|
|
|
549
597
|
});
|
|
550
598
|
return;
|
|
551
599
|
}
|
|
552
|
-
const
|
|
553
|
-
const selectedModel = selectModelFromInput(value,
|
|
600
|
+
const visibleModels = selectableModels(onboardingInput, onboarding.models);
|
|
601
|
+
const selectedModel = selectModelFromInput(value, visibleModels, onboardingIndex, {
|
|
554
602
|
allowManual: onboarding.provider !== "ollama"
|
|
555
603
|
});
|
|
556
604
|
if (!selectedModel) {
|
|
557
|
-
|
|
605
|
+
setOnboardingNotice({
|
|
558
606
|
tone: "warning",
|
|
559
|
-
label: "onboarding",
|
|
560
607
|
text: "Unknown model selection. Pick a listed model."
|
|
561
608
|
});
|
|
562
609
|
return;
|
|
@@ -587,7 +634,7 @@ export function App(props) {
|
|
|
587
634
|
});
|
|
588
635
|
}
|
|
589
636
|
closeOnboarding();
|
|
590
|
-
}, [activeHost?.host.url, appendLine, closeOnboarding, connectToHost, loadHostSuggestions, onboarding, onboardingIndex, openModelSelection, settings.ollamaUrl]);
|
|
637
|
+
}, [activeHost?.host.url, appendLine, closeOnboarding, connectToHost, loadHostSuggestions, onboarding, onboardingBusyMessage, onboardingIndex, openModelSelection, settings.ollamaUrl]);
|
|
591
638
|
const runTask = useCallback(async (task) => {
|
|
592
639
|
if (!task.trim() || isRunning) {
|
|
593
640
|
return;
|
|
@@ -596,6 +643,7 @@ export function App(props) {
|
|
|
596
643
|
setTranscriptScrollOffset(0);
|
|
597
644
|
setIsRunning(true);
|
|
598
645
|
appendLine({
|
|
646
|
+
kind: "user",
|
|
599
647
|
tone: "normal",
|
|
600
648
|
label: "you",
|
|
601
649
|
text: task
|
|
@@ -609,9 +657,41 @@ export function App(props) {
|
|
|
609
657
|
abortControllerRef.current = abortController;
|
|
610
658
|
const taskRunner = new AgentRunner({
|
|
611
659
|
...runnableSettings,
|
|
612
|
-
signal: abortController.signal
|
|
660
|
+
signal: abortController.signal,
|
|
661
|
+
sessionStore: sessionStoreRef.current,
|
|
662
|
+
approvalHandler: (request) => new Promise((resolve) => {
|
|
663
|
+
if (agentMode === "plan") {
|
|
664
|
+
appendLine({
|
|
665
|
+
kind: "approval",
|
|
666
|
+
tone: "warning",
|
|
667
|
+
label: "approval",
|
|
668
|
+
text: `${request.tool} blocked in plan mode`,
|
|
669
|
+
detail: "Switch to /mode build before approving write, script, test, or shell tools.",
|
|
670
|
+
workState: "waiting_approval",
|
|
671
|
+
tool: request.tool,
|
|
672
|
+
preview: request.preview
|
|
673
|
+
});
|
|
674
|
+
resolve("deny");
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
setPendingApproval(request);
|
|
678
|
+
setWorkState("waiting_approval");
|
|
679
|
+
setStatus(`approval needed for ${request.tool}`);
|
|
680
|
+
appendLine({
|
|
681
|
+
kind: "approval",
|
|
682
|
+
tone: "warning",
|
|
683
|
+
label: "approval",
|
|
684
|
+
text: `${request.tool} needs ${request.permission} approval`,
|
|
685
|
+
detail: `${request.preview} Press y once, a session, or n deny.`,
|
|
686
|
+
workState: "waiting_approval",
|
|
687
|
+
tool: request.tool,
|
|
688
|
+
preview: request.preview
|
|
689
|
+
});
|
|
690
|
+
approvalResolverRef.current = resolve;
|
|
691
|
+
})
|
|
613
692
|
});
|
|
614
693
|
for await (const event of taskRunner.run(task)) {
|
|
694
|
+
setWorkState(event.workState);
|
|
615
695
|
if (event.type === "metrics") {
|
|
616
696
|
if (runnableSettings.provider === "ollama") {
|
|
617
697
|
usedOllamaModelsRef.current.add(`${runnableSettings.ollamaUrl}|${runnableSettings.model}`);
|
|
@@ -634,17 +714,20 @@ export function App(props) {
|
|
|
634
714
|
}
|
|
635
715
|
catch (error) {
|
|
636
716
|
appendLine({
|
|
717
|
+
kind: "error",
|
|
637
718
|
tone: "danger",
|
|
638
719
|
label: "error",
|
|
639
|
-
text: error instanceof Error ? error.message : String(error)
|
|
720
|
+
text: error instanceof Error ? error.message : String(error),
|
|
721
|
+
workState: "error"
|
|
640
722
|
});
|
|
641
723
|
}
|
|
642
724
|
finally {
|
|
643
725
|
abortControllerRef.current = null;
|
|
644
726
|
setStatus("idle");
|
|
727
|
+
setWorkState("idle");
|
|
645
728
|
setIsRunning(false);
|
|
646
729
|
}
|
|
647
|
-
}, [appendLine, isRunning, modelOptions, settings]);
|
|
730
|
+
}, [agentMode, appendLine, isRunning, modelOptions, settings]);
|
|
648
731
|
const handleSlashCommand = useCallback(async (rawCommand) => {
|
|
649
732
|
const [commandName = "", ...args] = rawCommand.slice(1).trim().split(/\s+/);
|
|
650
733
|
const command = commandName.toLowerCase();
|
|
@@ -769,7 +852,7 @@ export function App(props) {
|
|
|
769
852
|
appendLine({
|
|
770
853
|
tone: "accent",
|
|
771
854
|
label: "reasoning",
|
|
772
|
-
text: `current ${settings.reasoningEffort}. Use /reasoning low, medium, high, xhigh, or adaptive.`
|
|
855
|
+
text: `current ${settings.reasoningEffort}. Use /reasoning none, low, medium, high, xhigh, or adaptive.`
|
|
773
856
|
});
|
|
774
857
|
return;
|
|
775
858
|
}
|
|
@@ -783,7 +866,7 @@ export function App(props) {
|
|
|
783
866
|
appendLine({
|
|
784
867
|
tone: "success",
|
|
785
868
|
label: "reasoning",
|
|
786
|
-
text:
|
|
869
|
+
text: formatReasoningSupport(settings.provider, settings.model, nextEffort === "adaptive" ? undefined : nextEffort)
|
|
787
870
|
});
|
|
788
871
|
return;
|
|
789
872
|
}
|
|
@@ -793,6 +876,7 @@ export function App(props) {
|
|
|
793
876
|
if (writeEnabled) {
|
|
794
877
|
setAgentMode("build");
|
|
795
878
|
}
|
|
879
|
+
grantedPermissionsRef.current.allowWrite = writeEnabled;
|
|
796
880
|
setSettings((currentSettings) => ({
|
|
797
881
|
...currentSettings,
|
|
798
882
|
allowWrite: writeEnabled
|
|
@@ -809,6 +893,7 @@ export function App(props) {
|
|
|
809
893
|
if (shellEnabled) {
|
|
810
894
|
setAgentMode("build");
|
|
811
895
|
}
|
|
896
|
+
grantedPermissionsRef.current.allowShell = shellEnabled;
|
|
812
897
|
setSettings((currentSettings) => ({
|
|
813
898
|
...currentSettings,
|
|
814
899
|
allowShell: shellEnabled
|
|
@@ -848,7 +933,7 @@ export function App(props) {
|
|
|
848
933
|
tone: "warning",
|
|
849
934
|
label: "model",
|
|
850
935
|
text: `No unique model match for "${requestedModel}".`,
|
|
851
|
-
detail: formatModelOptions(
|
|
936
|
+
detail: formatModelOptions(selectableModels(requestedModel, models).slice(0, 12), settings.model)
|
|
852
937
|
});
|
|
853
938
|
return;
|
|
854
939
|
}
|
|
@@ -897,6 +982,8 @@ export function App(props) {
|
|
|
897
982
|
});
|
|
898
983
|
return;
|
|
899
984
|
}
|
|
985
|
+
setInput("/models ");
|
|
986
|
+
setPaletteIndex(0);
|
|
900
987
|
appendLine({
|
|
901
988
|
tone: "accent",
|
|
902
989
|
label: "models",
|
|
@@ -919,9 +1006,65 @@ export function App(props) {
|
|
|
919
1006
|
label: "status",
|
|
920
1007
|
text: settings.provider === "ollama"
|
|
921
1008
|
? `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"} | write ${settings.allowWrite ? "on" : "off"} | shell ${settings.allowShell ? "on" : "off"} | draft ${draftTokens} tok | last ${formatTokens(telemetry)} | session ${formatSessionTokens(sessionTelemetry)} | cost ${formatCost(sessionTelemetry.estimatedCostUsd)}`
|
|
922
|
-
: `provider ${settings.provider} | model ${settings.model} | host ${settings.provider} api | compute cloud | agents ${settings.subagents ? "on" : "off"} | think ${settings.thinkingMode} | reasoning ${settings.reasoningEffort} | write ${settings.allowWrite ? "on" : "off"} | shell ${settings.allowShell ? "on" : "off"} | draft ${draftTokens} tok | last ${formatTokens(telemetry)} | session ${formatSessionTokens(sessionTelemetry)} | cost ${formatCost(sessionTelemetry.estimatedCostUsd)}`
|
|
1009
|
+
: `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)} | write ${settings.allowWrite ? "on" : "off"} | shell ${settings.allowShell ? "on" : "off"} | draft ${draftTokens} tok | last ${formatTokens(telemetry)} | session ${formatSessionTokens(sessionTelemetry)} | cost ${formatCost(sessionTelemetry.estimatedCostUsd)}`
|
|
1010
|
+
});
|
|
1011
|
+
return;
|
|
1012
|
+
case "sessions": {
|
|
1013
|
+
const sessions = await listWorkspaceSessions(settings.workspace);
|
|
1014
|
+
appendLine({
|
|
1015
|
+
kind: "status",
|
|
1016
|
+
tone: sessions.length > 0 ? "accent" : "muted",
|
|
1017
|
+
label: "sessions",
|
|
1018
|
+
text: sessions.length > 0 ? `Found ${sessions.length} workspace session${sessions.length === 1 ? "" : "s"}.` : "No workspace sessions yet.",
|
|
1019
|
+
detail: sessions
|
|
1020
|
+
.slice(0, 8)
|
|
1021
|
+
.map((session, index) => `${index + 1}. ${session.sessionId} ${session.updatedAt} ${session.lastTask ?? "no task"}`)
|
|
1022
|
+
.join("\n")
|
|
1023
|
+
});
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
case "resume": {
|
|
1027
|
+
const sessionId = args[0] ?? "";
|
|
1028
|
+
const sessions = await listWorkspaceSessions(settings.workspace);
|
|
1029
|
+
const selectedSession = sessionId ? await loadSessionSummary(settings.workspace, sessionId) : sessions[0] ?? null;
|
|
1030
|
+
appendLine({
|
|
1031
|
+
kind: "status",
|
|
1032
|
+
tone: selectedSession ? "accent" : "warning",
|
|
1033
|
+
label: "resume",
|
|
1034
|
+
text: selectedSession ? `Loaded session ${selectedSession.sessionId}` : "No session available to resume.",
|
|
1035
|
+
detail: selectedSession
|
|
1036
|
+
? `workspace ${selectedSession.workspace}\nupdated ${selectedSession.updatedAt}\nmodel ${selectedSession.provider ?? "-"} ${selectedSession.model ?? "-"}\nlast task ${selectedSession.lastTask ?? "-"}`
|
|
1037
|
+
: "Run /sessions after at least one PatchPilot run."
|
|
923
1038
|
});
|
|
924
1039
|
return;
|
|
1040
|
+
}
|
|
1041
|
+
case "diff": {
|
|
1042
|
+
const result = await new WorkspaceTools({
|
|
1043
|
+
root: settings.workspace,
|
|
1044
|
+
allowWrite: false,
|
|
1045
|
+
allowShell: false
|
|
1046
|
+
}).execute({
|
|
1047
|
+
name: "git_diff",
|
|
1048
|
+
arguments: {}
|
|
1049
|
+
});
|
|
1050
|
+
appendLine({
|
|
1051
|
+
kind: "diff",
|
|
1052
|
+
tone: result.ok ? "accent" : "warning",
|
|
1053
|
+
label: "diff",
|
|
1054
|
+
text: result.summary,
|
|
1055
|
+
detail: result.content,
|
|
1056
|
+
tool: "git_diff"
|
|
1057
|
+
});
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
case "approve": {
|
|
1061
|
+
const decision = args[0] === "session" ? "allow_session" : "allow_once";
|
|
1062
|
+
resolveApproval(decision);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
case "deny":
|
|
1066
|
+
resolveApproval("deny");
|
|
1067
|
+
return;
|
|
925
1068
|
case "connect":
|
|
926
1069
|
case "host":
|
|
927
1070
|
case "ollama":
|
|
@@ -940,6 +1083,8 @@ export function App(props) {
|
|
|
940
1083
|
text: "Scanning LAN and Tailscale for Ollama hosts..."
|
|
941
1084
|
});
|
|
942
1085
|
await loadHostSuggestions(true, true);
|
|
1086
|
+
setInput("/connect ");
|
|
1087
|
+
setPaletteIndex(0);
|
|
943
1088
|
return;
|
|
944
1089
|
}
|
|
945
1090
|
if (args.join(" ").trim().toLowerCase() === "local") {
|
|
@@ -965,6 +1110,8 @@ export function App(props) {
|
|
|
965
1110
|
text: "Scanning LAN and Tailscale for Ollama hosts..."
|
|
966
1111
|
});
|
|
967
1112
|
await loadHostSuggestions(true, true);
|
|
1113
|
+
setInput("/connect ");
|
|
1114
|
+
setPaletteIndex(0);
|
|
968
1115
|
return;
|
|
969
1116
|
case "eject": {
|
|
970
1117
|
if (settings.provider !== "ollama") {
|
|
@@ -1050,6 +1197,7 @@ export function App(props) {
|
|
|
1050
1197
|
loadHostSuggestions,
|
|
1051
1198
|
loadProviderModels,
|
|
1052
1199
|
modelOptions,
|
|
1200
|
+
resolveApproval,
|
|
1053
1201
|
sessionTelemetry,
|
|
1054
1202
|
settings,
|
|
1055
1203
|
telemetry
|
|
@@ -1081,7 +1229,10 @@ export function App(props) {
|
|
|
1081
1229
|
await runTask(nextValue);
|
|
1082
1230
|
}, [handleOnboardingSubmit, handleSlashCommand, isRunning, onboarding, paletteIndex, paletteItems, runTask]);
|
|
1083
1231
|
useEffect(() => {
|
|
1084
|
-
|
|
1232
|
+
void sessionStoreRef.current.create();
|
|
1233
|
+
}, []);
|
|
1234
|
+
useEffect(() => {
|
|
1235
|
+
if (!props.initialTask || didRunInitialTask.current || onboarding || process.env.PATCHPILOT_ONBOARDING_COMPLETE !== "1") {
|
|
1085
1236
|
return;
|
|
1086
1237
|
}
|
|
1087
1238
|
didRunInitialTask.current = true;
|
|
@@ -1091,7 +1242,7 @@ export function App(props) {
|
|
|
1091
1242
|
setPaletteIndex(0);
|
|
1092
1243
|
}, [hostOptions, input, modelOptions, onboarding, settings.model, settings.provider]);
|
|
1093
1244
|
useEffect(() => {
|
|
1094
|
-
if (didOpenDefaultOnboarding.current ||
|
|
1245
|
+
if (didOpenDefaultOnboarding.current || onboarding || process.env.PATCHPILOT_ONBOARDING_COMPLETE === "1") {
|
|
1095
1246
|
return;
|
|
1096
1247
|
}
|
|
1097
1248
|
didOpenDefaultOnboarding.current = true;
|
|
@@ -1109,6 +1260,10 @@ export function App(props) {
|
|
|
1109
1260
|
}
|
|
1110
1261
|
let cancelled = false;
|
|
1111
1262
|
async function syncActiveHost() {
|
|
1263
|
+
if (activeHostSyncInFlightRef.current) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
activeHostSyncInFlightRef.current = true;
|
|
1112
1267
|
const verifiedHost = await checkOllamaHost(settings.ollamaUrl, {
|
|
1113
1268
|
timeoutMs: 800
|
|
1114
1269
|
});
|
|
@@ -1116,6 +1271,7 @@ export function App(props) {
|
|
|
1116
1271
|
if (!cancelled) {
|
|
1117
1272
|
setActiveHost((currentHost) => (currentHost?.host.url === settings.ollamaUrl ? currentHost : null));
|
|
1118
1273
|
}
|
|
1274
|
+
activeHostSyncInFlightRef.current = false;
|
|
1119
1275
|
return;
|
|
1120
1276
|
}
|
|
1121
1277
|
const details = await readOllamaHostDetails(verifiedHost).catch(() => ({
|
|
@@ -1124,6 +1280,7 @@ export function App(props) {
|
|
|
1124
1280
|
runningModels: [],
|
|
1125
1281
|
fetchedAt: Date.now()
|
|
1126
1282
|
}));
|
|
1283
|
+
activeHostSyncInFlightRef.current = false;
|
|
1127
1284
|
if (cancelled) {
|
|
1128
1285
|
return;
|
|
1129
1286
|
}
|
|
@@ -1147,16 +1304,40 @@ export function App(props) {
|
|
|
1147
1304
|
}
|
|
1148
1305
|
const trimmedInput = input.trim();
|
|
1149
1306
|
if (settings.provider === "ollama" && (trimmedInput === "/connect" || trimmedInput === "/hosts") && hostOptions.length === 0 && !isLoadingHosts) {
|
|
1150
|
-
|
|
1307
|
+
const key = `${settings.provider}:${settings.ollamaUrl}:${trimmedInput}:hosts`;
|
|
1308
|
+
if (!autoLoadKeysRef.current.has(key)) {
|
|
1309
|
+
autoLoadKeysRef.current.add(key);
|
|
1310
|
+
void loadHostSuggestions(false, false);
|
|
1311
|
+
}
|
|
1151
1312
|
}
|
|
1152
1313
|
if ((trimmedInput === "/models" || trimmedInput === "/model") && modelOptions.length === 0 && !isLoadingModels) {
|
|
1153
|
-
|
|
1314
|
+
const key = `${settings.provider}:${settings.ollamaUrl}:${trimmedInput}:models`;
|
|
1315
|
+
if (!autoLoadKeysRef.current.has(key)) {
|
|
1316
|
+
autoLoadKeysRef.current.add(key);
|
|
1317
|
+
void loadProviderModels(false);
|
|
1318
|
+
}
|
|
1154
1319
|
}
|
|
1155
1320
|
}, [hostOptions.length, input, isLoadingHosts, isLoadingModels, isRunning, loadHostSuggestions, loadProviderModels, modelOptions.length, onboarding, settings.provider]);
|
|
1156
1321
|
useInput((inputValue, key) => {
|
|
1322
|
+
if (pendingApproval) {
|
|
1323
|
+
const normalizedInput = inputValue.toLowerCase();
|
|
1324
|
+
if (normalizedInput === "y") {
|
|
1325
|
+
resolveApproval("allow_once");
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
if (normalizedInput === "a") {
|
|
1329
|
+
resolveApproval("allow_session");
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
if (normalizedInput === "n" || key.escape) {
|
|
1333
|
+
resolveApproval("deny");
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1157
1337
|
if (isRunning && key.escape) {
|
|
1158
1338
|
abortControllerRef.current?.abort();
|
|
1159
1339
|
appendLine({
|
|
1340
|
+
kind: "status",
|
|
1160
1341
|
tone: "warning",
|
|
1161
1342
|
label: "stop",
|
|
1162
1343
|
text: "Stopping current task..."
|
|
@@ -1169,7 +1350,10 @@ export function App(props) {
|
|
|
1169
1350
|
goBackOnboarding();
|
|
1170
1351
|
return;
|
|
1171
1352
|
}
|
|
1172
|
-
|
|
1353
|
+
if (onboardingBusyMessage) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
const optionCount = onboarding.step === "model" ? selectableModels(onboardingInput, onboarding.models).length : getOnboardingOptionCount(onboarding);
|
|
1173
1357
|
if (optionCount > 0 && key.upArrow) {
|
|
1174
1358
|
setOnboardingIndex((currentIndex) => (currentIndex - 1 + optionCount) % optionCount);
|
|
1175
1359
|
return;
|
|
@@ -1277,7 +1461,7 @@ export function App(props) {
|
|
|
1277
1461
|
clearInterval(timer);
|
|
1278
1462
|
};
|
|
1279
1463
|
}, []);
|
|
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 })] })] }))] }));
|
|
1464
|
+
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
1465
|
}
|
|
1282
1466
|
async function loadAvailableModels(provider, ollamaUrl, setModelOptions, refresh = false) {
|
|
1283
1467
|
const cacheKey = `${provider}:${provider === "ollama" ? ollamaUrl : "default"}`;
|
|
@@ -1451,7 +1635,7 @@ function buildCommandSuggestionItems(options) {
|
|
|
1451
1635
|
});
|
|
1452
1636
|
}
|
|
1453
1637
|
else {
|
|
1454
|
-
items.unshift(...
|
|
1638
|
+
items.unshift(...selectableModels(modelQuery, options.modelOptions).slice(0, 8).map((model) => ({
|
|
1455
1639
|
key: `model-${model}`,
|
|
1456
1640
|
category: "model",
|
|
1457
1641
|
label: model,
|
|
@@ -1470,7 +1654,7 @@ function getOnboardingOptionCount(onboarding) {
|
|
|
1470
1654
|
case "host":
|
|
1471
1655
|
return onboarding.hosts.length + 1;
|
|
1472
1656
|
case "api-key-choice":
|
|
1473
|
-
return 2;
|
|
1657
|
+
return onboarding.hasExistingKey ? 2 : 1;
|
|
1474
1658
|
case "model":
|
|
1475
1659
|
return onboarding.models.length;
|
|
1476
1660
|
default:
|
|
@@ -1525,7 +1709,7 @@ function selectModelFromInput(value, models, selectedIndex, options = {}) {
|
|
|
1525
1709
|
if (models.includes(normalizedValue)) {
|
|
1526
1710
|
return normalizedValue;
|
|
1527
1711
|
}
|
|
1528
|
-
const matches =
|
|
1712
|
+
const matches = selectableModels(normalizedValue, models);
|
|
1529
1713
|
if (matches.length === 1) {
|
|
1530
1714
|
return matches[0] ?? null;
|
|
1531
1715
|
}
|
|
@@ -1534,34 +1718,6 @@ function selectModelFromInput(value, models, selectedIndex, options = {}) {
|
|
|
1534
1718
|
function isPlausibleCloudModelId(value) {
|
|
1535
1719
|
return /^[A-Za-z0-9][A-Za-z0-9._:/+-]*$/.test(value) && value.length >= 3;
|
|
1536
1720
|
}
|
|
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
1721
|
function defaultModelForProvider(provider, currentModel) {
|
|
1566
1722
|
if (provider === "nvidia") {
|
|
1567
1723
|
return currentModel.includes("/") && !currentModel.startsWith("openrouter/") ? currentModel : defaultNvidiaModel;
|
|
@@ -1633,7 +1789,7 @@ async function ejectOllamaModels(options) {
|
|
|
1633
1789
|
return ejected;
|
|
1634
1790
|
}
|
|
1635
1791
|
function isReasoningEffort(value) {
|
|
1636
|
-
return value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "adaptive";
|
|
1792
|
+
return value === "none" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "adaptive";
|
|
1637
1793
|
}
|
|
1638
1794
|
function upsertAdvisorNote(notes, nextNote) {
|
|
1639
1795
|
const nextNotes = notes.filter((note) => note.role !== nextNote.role);
|
|
@@ -1643,46 +1799,75 @@ function eventToLine(event) {
|
|
|
1643
1799
|
switch (event.type) {
|
|
1644
1800
|
case "status":
|
|
1645
1801
|
return {
|
|
1802
|
+
kind: "status",
|
|
1646
1803
|
tone: "muted",
|
|
1647
|
-
label:
|
|
1648
|
-
text: event.message
|
|
1804
|
+
label: event.workState,
|
|
1805
|
+
text: event.message,
|
|
1806
|
+
workState: event.workState
|
|
1649
1807
|
};
|
|
1650
1808
|
case "assistant":
|
|
1651
1809
|
return {
|
|
1810
|
+
kind: "assistant",
|
|
1652
1811
|
tone: "accent",
|
|
1653
1812
|
label: "pilot",
|
|
1654
|
-
text: event.message
|
|
1813
|
+
text: event.message,
|
|
1814
|
+
workState: event.workState
|
|
1655
1815
|
};
|
|
1656
1816
|
case "subagent":
|
|
1657
1817
|
return {
|
|
1818
|
+
kind: "assistant",
|
|
1658
1819
|
tone: "accent",
|
|
1659
1820
|
label: event.role,
|
|
1660
1821
|
text: "advisor brief updated",
|
|
1661
|
-
detail: event.message
|
|
1822
|
+
detail: event.message,
|
|
1823
|
+
workState: event.workState
|
|
1662
1824
|
};
|
|
1663
1825
|
case "tool":
|
|
1664
1826
|
return {
|
|
1827
|
+
kind: event.name === "git_diff" ? "diff" : "tool",
|
|
1665
1828
|
tone: event.ok ? "success" : "warning",
|
|
1666
1829
|
label: event.name,
|
|
1667
|
-
text: event.summary
|
|
1830
|
+
text: event.summary,
|
|
1831
|
+
workState: event.workState,
|
|
1832
|
+
tool: event.name,
|
|
1833
|
+
toolCallId: event.toolCallId,
|
|
1834
|
+
category: event.category,
|
|
1835
|
+
preview: event.preview
|
|
1836
|
+
};
|
|
1837
|
+
case "approval":
|
|
1838
|
+
return {
|
|
1839
|
+
kind: "approval",
|
|
1840
|
+
tone: event.decision === "deny" ? "warning" : "success",
|
|
1841
|
+
label: "approval",
|
|
1842
|
+
text: `${event.request.tool} ${event.decision.replace("_", " ")}`,
|
|
1843
|
+
detail: event.request.preview,
|
|
1844
|
+
workState: event.workState,
|
|
1845
|
+
tool: event.request.tool,
|
|
1846
|
+
preview: event.request.preview
|
|
1668
1847
|
};
|
|
1669
1848
|
case "final":
|
|
1670
1849
|
return {
|
|
1850
|
+
kind: "final",
|
|
1671
1851
|
tone: "success",
|
|
1672
1852
|
label: "final",
|
|
1673
|
-
text: event.message
|
|
1853
|
+
text: event.message,
|
|
1854
|
+
workState: event.workState
|
|
1674
1855
|
};
|
|
1675
1856
|
case "error":
|
|
1676
1857
|
return {
|
|
1858
|
+
kind: "error",
|
|
1677
1859
|
tone: "danger",
|
|
1678
1860
|
label: "error",
|
|
1679
|
-
text: event.message
|
|
1861
|
+
text: event.message,
|
|
1862
|
+
workState: event.workState
|
|
1680
1863
|
};
|
|
1681
1864
|
case "metrics":
|
|
1682
1865
|
return {
|
|
1866
|
+
kind: "status",
|
|
1683
1867
|
tone: "muted",
|
|
1684
1868
|
label: "metrics",
|
|
1685
|
-
text: formatTokens(event.metrics)
|
|
1869
|
+
text: formatTokens(event.metrics),
|
|
1870
|
+
workState: event.workState
|
|
1686
1871
|
};
|
|
1687
1872
|
}
|
|
1688
1873
|
}
|
|
@@ -1696,8 +1881,26 @@ function eventToStatus(event) {
|
|
|
1696
1881
|
if (event.type === "subagent") {
|
|
1697
1882
|
return `${event.role} subagent`;
|
|
1698
1883
|
}
|
|
1884
|
+
if (event.type === "approval") {
|
|
1885
|
+
return `${event.request.tool}: ${event.decision.replace("_", " ")}`;
|
|
1886
|
+
}
|
|
1699
1887
|
return event.type;
|
|
1700
1888
|
}
|
|
1889
|
+
function defaultLogKind(line) {
|
|
1890
|
+
if (line.kind) {
|
|
1891
|
+
return line.kind;
|
|
1892
|
+
}
|
|
1893
|
+
if (line.label === "you") {
|
|
1894
|
+
return "user";
|
|
1895
|
+
}
|
|
1896
|
+
if (line.label === "error") {
|
|
1897
|
+
return "error";
|
|
1898
|
+
}
|
|
1899
|
+
if (line.label === "final") {
|
|
1900
|
+
return "final";
|
|
1901
|
+
}
|
|
1902
|
+
return "status";
|
|
1903
|
+
}
|
|
1701
1904
|
function formatHostOptions(hosts) {
|
|
1702
1905
|
return hosts
|
|
1703
1906
|
.map((host, index) => {
|