@jx-grxf/patchpilot 0.3.1-beta → 1.0.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/.env.example +17 -1
- package/README.md +80 -22
- package/SECURITY.md +10 -2
- package/dist/cli.js +59 -13
- package/dist/cli.js.map +1 -1
- package/dist/core/agent.d.ts +3 -0
- package/dist/core/agent.js +56 -12
- package/dist/core/agent.js.map +1 -1
- package/dist/core/cleanup.d.ts +3 -0
- package/dist/core/cleanup.js +29 -0
- package/dist/core/cleanup.js.map +1 -0
- package/dist/core/doctor.d.ts +4 -1
- package/dist/core/doctor.js +119 -1
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/gemini.js +27 -14
- package/dist/core/gemini.js.map +1 -1
- package/dist/core/geminiWrapper.d.ts +51 -0
- package/dist/core/geminiWrapper.js +718 -0
- package/dist/core/geminiWrapper.js.map +1 -0
- package/dist/core/json.js +65 -1
- package/dist/core/json.js.map +1 -1
- package/dist/core/memory.d.ts +16 -0
- package/dist/core/memory.js +108 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/modelClient.js +7 -0
- package/dist/core/modelClient.js.map +1 -1
- package/dist/core/nvidia.js +20 -2
- package/dist/core/nvidia.js.map +1 -1
- package/dist/core/openrouter.d.ts +2 -0
- package/dist/core/openrouter.js +51 -7
- package/dist/core/openrouter.js.map +1 -1
- package/dist/core/projectInit.d.ts +6 -0
- package/dist/core/projectInit.js +44 -0
- package/dist/core/projectInit.js.map +1 -0
- package/dist/core/reasoning.js +3 -0
- package/dist/core/reasoning.js.map +1 -1
- package/dist/core/session.d.ts +1 -0
- package/dist/core/session.js +46 -0
- package/dist/core/session.js.map +1 -1
- package/dist/core/types.d.ts +9 -4
- package/dist/core/workspace.d.ts +8 -0
- package/dist/core/workspace.js +314 -21
- package/dist/core/workspace.js.map +1 -1
- package/dist/tui/App.js +571 -81
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/commands.js +35 -6
- package/dist/tui/commands.js.map +1 -1
- package/dist/tui/components/ApprovalPanel.d.ts +6 -0
- package/dist/tui/components/ApprovalPanel.js +16 -0
- package/dist/tui/components/ApprovalPanel.js.map +1 -0
- package/dist/tui/components/CommandSuggestions.js +8 -3
- package/dist/tui/components/CommandSuggestions.js.map +1 -1
- package/dist/tui/components/Composer.d.ts +1 -0
- package/dist/tui/components/Composer.js +1 -1
- package/dist/tui/components/Composer.js.map +1 -1
- package/dist/tui/components/ExperimentalPanel.d.ts +10 -0
- package/dist/tui/components/ExperimentalPanel.js +33 -0
- package/dist/tui/components/ExperimentalPanel.js.map +1 -0
- package/dist/tui/components/Header.js +3 -3
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/OnboardingPanel.d.ts +13 -1
- package/dist/tui/components/OnboardingPanel.js +23 -9
- package/dist/tui/components/OnboardingPanel.js.map +1 -1
- package/dist/tui/components/Sidebar.js +32 -26
- package/dist/tui/components/Sidebar.js.map +1 -1
- package/dist/tui/components/Transcript.js +4 -3
- package/dist/tui/components/Transcript.js.map +1 -1
- package/dist/tui/format.js +7 -7
- package/dist/tui/format.js.map +1 -1
- package/dist/tui/modes.d.ts +1 -1
- package/dist/tui/modes.js +8 -2
- package/dist/tui/modes.js.map +1 -1
- package/docs/gemini-wrapper.md +87 -0
- package/docs/releases/v0.1.1-beta.md +18 -0
- package/docs/releases/v0.2.1.md +1 -1
- package/docs/releases/v0.3.1-beta.md +4 -0
- package/docs/releases/v0.4.0.md +27 -0
- package/docs/releases/v1.0.0.md +28 -0
- package/docs/showcase/patchpilot-banner.png +0 -0
- package/docs/showcase/patchpilot-logo.png +0 -0
- package/package.json +5 -2
package/dist/tui/App.js
CHANGED
|
@@ -2,21 +2,26 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { Box, useApp, useInput, useStdout } from "ink";
|
|
4
4
|
import { AgentRunner } from "../core/agent.js";
|
|
5
|
+
import { cleanupPatchPilot, readCleanupTarget } from "../core/cleanup.js";
|
|
5
6
|
import { defaultCodexModel, hasCodexCliOAuth } from "../core/codex.js";
|
|
6
7
|
import { describeComputeTarget } from "../core/compute.js";
|
|
7
8
|
import { runDoctor } from "../core/doctor.js";
|
|
8
9
|
import { savePatchPilotEnvValues } from "../core/env.js";
|
|
9
10
|
import { defaultGeminiModel, readGeminiApiKey } from "../core/gemini.js";
|
|
11
|
+
import { defaultGeminiWrapperModel, geminiWrapperRequiresApiKey, readGeminiWrapperApiKey, readGeminiWrapperBaseUrl, readGeminiWrapperCookiesJson, readGeminiWrapperMode, readGeminiWrapperPythonCommand, saveGeminiWrapperCookieFile } from "../core/geminiWrapper.js";
|
|
10
12
|
import { createModelClient } from "../core/modelClient.js";
|
|
11
13
|
import { defaultNvidiaModel, readNvidiaApiKey } from "../core/nvidia.js";
|
|
12
14
|
import { defaultOllamaModel, OllamaClient } from "../core/ollama.js";
|
|
13
15
|
import { defaultOpenRouterModel, isOpenRouterFreeModel, readOpenRouterApiKey } from "../core/openrouter.js";
|
|
16
|
+
import { ensurePatchPilotGitignore, patchPilotInitPrompt } from "../core/projectInit.js";
|
|
14
17
|
import { formatReasoningSupport } from "../core/reasoning.js";
|
|
15
|
-
import { listWorkspaceSessions, loadSessionSummary, SessionStore } from "../core/session.js";
|
|
18
|
+
import { buildSessionResumeContext, listWorkspaceSessions, loadSessionSummary, SessionStore } from "../core/session.js";
|
|
16
19
|
import { addTelemetryToSession, emptySessionTelemetry, estimateTokens } from "../core/tokenAccounting.js";
|
|
17
20
|
import { WorkspaceTools } from "../core/workspace.js";
|
|
21
|
+
import { ApprovalPanel } from "./components/ApprovalPanel.js";
|
|
18
22
|
import { CommandSuggestions } from "./components/CommandSuggestions.js";
|
|
19
23
|
import { Composer, FooterHints } from "./components/Composer.js";
|
|
24
|
+
import { ExperimentalPanel, experimentalFlagAt, experimentalFlagCount } from "./components/ExperimentalPanel.js";
|
|
20
25
|
import { Header } from "./components/Header.js";
|
|
21
26
|
import { OnboardingPanel } from "./components/OnboardingPanel.js";
|
|
22
27
|
import { Sidebar } from "./components/Sidebar.js";
|
|
@@ -39,6 +44,11 @@ export function App(props) {
|
|
|
39
44
|
const abortControllerRef = useRef(null);
|
|
40
45
|
const sessionStoreRef = useRef(new SessionStore({ workspace: props.workspace }));
|
|
41
46
|
const approvalResolverRef = useRef(null);
|
|
47
|
+
const runtimeStateRef = useRef({
|
|
48
|
+
isRunning: false,
|
|
49
|
+
hasPendingApproval: false,
|
|
50
|
+
lastSigintAt: 0
|
|
51
|
+
});
|
|
42
52
|
const grantedPermissionsRef = useRef({
|
|
43
53
|
allowWrite: props.allowWrite,
|
|
44
54
|
allowShell: props.allowShell
|
|
@@ -54,6 +64,7 @@ export function App(props) {
|
|
|
54
64
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
55
65
|
const [telemetry, setTelemetry] = useState(null);
|
|
56
66
|
const [sessionTelemetry, setSessionTelemetry] = useState(() => emptySessionTelemetry());
|
|
67
|
+
const [resumeContext, setResumeContext] = useState("");
|
|
57
68
|
const [systemStats, setSystemStats] = useState(() => readSystemStats().stats);
|
|
58
69
|
const [gpuStats, setGpuStats] = useState(null);
|
|
59
70
|
const [agentMode, setAgentMode] = useState(() => initialAgentMode({ allowWrite: props.allowWrite, allowShell: props.allowShell }));
|
|
@@ -64,6 +75,13 @@ export function App(props) {
|
|
|
64
75
|
const [modelOptions, setModelOptions] = useState([]);
|
|
65
76
|
const [isLoadingModels, setIsLoadingModels] = useState(false);
|
|
66
77
|
const [onboarding, setOnboarding] = useState(null);
|
|
78
|
+
const [experimentalOpen, setExperimentalOpen] = useState(false);
|
|
79
|
+
const [experimentalIndex, setExperimentalIndex] = useState(0);
|
|
80
|
+
const [experimentalFlags, setExperimentalFlags] = useState({
|
|
81
|
+
fileAnalysis: readBooleanEnv(process.env.PATCHPILOT_EXPERIMENTAL_FILE_ANALYSIS, false),
|
|
82
|
+
memory: readBooleanEnv(process.env.PATCHPILOT_EXPERIMENTAL_MEMORY, false),
|
|
83
|
+
subagents: props.subagents
|
|
84
|
+
});
|
|
67
85
|
const [onboardingIndex, setOnboardingIndex] = useState(0);
|
|
68
86
|
const [onboardingInput, setOnboardingInput] = useState("");
|
|
69
87
|
const [onboardingBusyMessage, setOnboardingBusyMessage] = useState(null);
|
|
@@ -87,7 +105,7 @@ export function App(props) {
|
|
|
87
105
|
const draftTokens = estimateTokens(input);
|
|
88
106
|
const terminalRows = stdout.rows ?? 40;
|
|
89
107
|
const terminalColumns = stdout.columns ?? 120;
|
|
90
|
-
const paletteItems = !isRunning && !onboarding
|
|
108
|
+
const paletteItems = !isRunning && !onboarding && !experimentalOpen
|
|
91
109
|
? buildCommandSuggestionItems({
|
|
92
110
|
input,
|
|
93
111
|
provider: settings.provider,
|
|
@@ -101,9 +119,10 @@ export function App(props) {
|
|
|
101
119
|
const rootHeight = Math.max(24, terminalRows);
|
|
102
120
|
const headerReservedHeight = 5;
|
|
103
121
|
const paletteReservedHeight = !onboarding && paletteItems.length > 0 ? Math.min(8, paletteItems.length) + 4 : 0;
|
|
104
|
-
const composerReservedHeight = onboarding ? 0 : 2;
|
|
105
|
-
const footerReservedHeight = onboarding ? 0 : 1;
|
|
106
|
-
const
|
|
122
|
+
const composerReservedHeight = onboarding || experimentalOpen ? 0 : 2;
|
|
123
|
+
const footerReservedHeight = onboarding || experimentalOpen ? 0 : 1;
|
|
124
|
+
const approvalReservedHeight = !onboarding && !experimentalOpen && (pendingApproval || bypassConfirmation) ? 6 : 0;
|
|
125
|
+
const panelHeight = Math.max(8, rootHeight - headerReservedHeight - composerReservedHeight - paletteReservedHeight - footerReservedHeight - approvalReservedHeight);
|
|
107
126
|
const transcriptWidth = Math.max(42, terminalColumns - 38);
|
|
108
127
|
const scrollStep = Math.max(4, Math.floor(panelHeight * 0.8));
|
|
109
128
|
const appendLine = useCallback((line) => {
|
|
@@ -122,6 +141,7 @@ export function App(props) {
|
|
|
122
141
|
}
|
|
123
142
|
approvalResolverRef.current(decision);
|
|
124
143
|
approvalResolverRef.current = null;
|
|
144
|
+
setInput("");
|
|
125
145
|
appendLine({
|
|
126
146
|
kind: "approval",
|
|
127
147
|
tone: decision === "deny" ? "warning" : "success",
|
|
@@ -137,6 +157,7 @@ export function App(props) {
|
|
|
137
157
|
const permissions = permissionsForMode(nextMode);
|
|
138
158
|
setAgentMode(nextMode);
|
|
139
159
|
setBypassConfirmation(false);
|
|
160
|
+
grantedPermissionsRef.current = permissions;
|
|
140
161
|
setSettings((currentSettings) => ({
|
|
141
162
|
...currentSettings,
|
|
142
163
|
allowWrite: permissions.allowWrite,
|
|
@@ -156,20 +177,35 @@ export function App(props) {
|
|
|
156
177
|
}
|
|
157
178
|
}, [appendLine]);
|
|
158
179
|
const requestBypassMode = useCallback(() => {
|
|
180
|
+
if (bypassConfirmation) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
159
183
|
setBypassConfirmation(true);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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]);
|
|
184
|
+
setStatus("bypass confirmation needed");
|
|
185
|
+
setWorkState("waiting_approval");
|
|
186
|
+
}, [bypassConfirmation]);
|
|
168
187
|
const confirmBypassMode = useCallback(() => {
|
|
188
|
+
setInput("");
|
|
169
189
|
applyMode("bypass");
|
|
170
190
|
}, [applyMode]);
|
|
191
|
+
const setExplicitPermission = useCallback((permission, enabled) => {
|
|
192
|
+
const nextPermissions = {
|
|
193
|
+
allowWrite: permission === "write" ? enabled : settings.allowWrite,
|
|
194
|
+
allowShell: permission === "shell" ? enabled : settings.allowShell
|
|
195
|
+
};
|
|
196
|
+
grantedPermissionsRef.current = nextPermissions;
|
|
197
|
+
setBypassConfirmation(false);
|
|
198
|
+
setAgentMode(nextPermissions.allowWrite && nextPermissions.allowShell ? "bypass" : nextPermissions.allowWrite || nextPermissions.allowShell ? "build" : "plan");
|
|
199
|
+
setSettings((currentSettings) => ({
|
|
200
|
+
...currentSettings,
|
|
201
|
+
...nextPermissions
|
|
202
|
+
}));
|
|
203
|
+
}, [settings.allowShell, settings.allowWrite]);
|
|
171
204
|
const cancelBypassMode = useCallback(() => {
|
|
205
|
+
setInput("");
|
|
172
206
|
setBypassConfirmation(false);
|
|
207
|
+
setStatus("idle");
|
|
208
|
+
setWorkState("idle");
|
|
173
209
|
applyMode("build", false);
|
|
174
210
|
appendLine({
|
|
175
211
|
kind: "approval",
|
|
@@ -289,13 +325,14 @@ export function App(props) {
|
|
|
289
325
|
setTelemetry(null);
|
|
290
326
|
setOnboardingInput("");
|
|
291
327
|
setOnboardingNotice(null);
|
|
292
|
-
setOnboardingBusyMessage(
|
|
328
|
+
setOnboardingBusyMessage(null);
|
|
293
329
|
const nextModel = defaultModelForProvider(provider, options.currentModel ?? settings.model);
|
|
294
330
|
setSettings((currentSettings) => ({
|
|
295
331
|
...currentSettings,
|
|
296
332
|
provider,
|
|
297
333
|
model: nextModel
|
|
298
334
|
}));
|
|
335
|
+
setOnboardingBusyMessage(`Loading ${provider} models...`);
|
|
299
336
|
try {
|
|
300
337
|
const models = await loadAvailableModels(provider, options.ollamaUrl ?? settings.ollamaUrl, setModelOptions, true);
|
|
301
338
|
if (models.length === 0) {
|
|
@@ -305,9 +342,13 @@ export function App(props) {
|
|
|
305
342
|
? "No Ollama models found on that host."
|
|
306
343
|
: provider === "gemini"
|
|
307
344
|
? "No Gemini models listed. Check the API key."
|
|
308
|
-
: provider === "
|
|
309
|
-
? "No
|
|
310
|
-
:
|
|
345
|
+
: provider === "gemini-wrapper"
|
|
346
|
+
? "No Gemini-Wrapper models listed. Check the bridge install and cookie setup."
|
|
347
|
+
: provider === "openrouter"
|
|
348
|
+
? "No OpenRouter models listed. Check the API key."
|
|
349
|
+
: provider === "nvidia"
|
|
350
|
+
? "No NVIDIA models listed. Check the API key."
|
|
351
|
+
: "No Codex OAuth models listed.",
|
|
311
352
|
detail: "Use the back key to choose another provider or retry after fixing the provider setup."
|
|
312
353
|
});
|
|
313
354
|
return;
|
|
@@ -353,6 +394,11 @@ export function App(props) {
|
|
|
353
394
|
case "host":
|
|
354
395
|
case "api-key-choice":
|
|
355
396
|
case "gemini-key":
|
|
397
|
+
case "gemini-wrapper-url":
|
|
398
|
+
case "gemini-wrapper-psid":
|
|
399
|
+
case "gemini-wrapper-psidts":
|
|
400
|
+
case "gemini-wrapper-model-mode":
|
|
401
|
+
case "gemini-wrapper-key":
|
|
356
402
|
case "openrouter-key":
|
|
357
403
|
case "nvidia-key":
|
|
358
404
|
case "codex-login":
|
|
@@ -378,6 +424,12 @@ export function App(props) {
|
|
|
378
424
|
openApiKeyChoice("gemini", setOnboarding, setOnboardingIndex);
|
|
379
425
|
return;
|
|
380
426
|
}
|
|
427
|
+
if (onboarding.provider === "gemini-wrapper") {
|
|
428
|
+
setOnboarding({
|
|
429
|
+
step: "gemini-wrapper-model-mode"
|
|
430
|
+
});
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
381
433
|
if (onboarding.provider === "nvidia") {
|
|
382
434
|
openApiKeyChoice("nvidia", setOnboarding, setOnboardingIndex);
|
|
383
435
|
return;
|
|
@@ -450,7 +502,7 @@ export function App(props) {
|
|
|
450
502
|
}
|
|
451
503
|
return;
|
|
452
504
|
}
|
|
453
|
-
if (selection === "gemini" || selection === "openrouter" || selection === "nvidia") {
|
|
505
|
+
if (selection === "gemini" || selection === "gemini-wrapper" || selection === "openrouter" || selection === "nvidia") {
|
|
454
506
|
openApiKeyChoice(selection, setOnboarding, setOnboardingIndex);
|
|
455
507
|
return;
|
|
456
508
|
}
|
|
@@ -536,12 +588,20 @@ export function App(props) {
|
|
|
536
588
|
return;
|
|
537
589
|
}
|
|
538
590
|
if (choice === 0 && onboarding.hasExistingKey) {
|
|
591
|
+
if (onboarding.provider === "gemini-wrapper") {
|
|
592
|
+
setOnboarding({
|
|
593
|
+
step: "gemini-wrapper-model-mode"
|
|
594
|
+
});
|
|
595
|
+
setOnboardingInput("");
|
|
596
|
+
setOnboardingIndex(0);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
539
599
|
await openModelSelection(onboarding.provider, {
|
|
540
600
|
currentModel: defaultModelForProvider(onboarding.provider, settings.model)
|
|
541
601
|
});
|
|
542
602
|
return;
|
|
543
603
|
}
|
|
544
|
-
setOnboarding({
|
|
604
|
+
setOnboarding(onboarding.provider === "gemini-wrapper" ? { step: "gemini-wrapper-psid" } : {
|
|
545
605
|
step: `${onboarding.provider}-key`
|
|
546
606
|
});
|
|
547
607
|
setOnboardingInput("");
|
|
@@ -572,6 +632,151 @@ export function App(props) {
|
|
|
572
632
|
});
|
|
573
633
|
return;
|
|
574
634
|
}
|
|
635
|
+
if (onboarding.step === "gemini-wrapper-psid") {
|
|
636
|
+
const secure1psid = value.trim();
|
|
637
|
+
if (!secure1psid) {
|
|
638
|
+
setOnboardingNotice({
|
|
639
|
+
tone: "warning",
|
|
640
|
+
text: "__Secure-1PSID cannot be empty.",
|
|
641
|
+
detail: "Paste the cookie value manually. PatchPilot will not scan browser profiles."
|
|
642
|
+
});
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
setOnboarding({
|
|
646
|
+
step: "gemini-wrapper-psidts",
|
|
647
|
+
secure1psid
|
|
648
|
+
});
|
|
649
|
+
setOnboardingInput("");
|
|
650
|
+
setOnboardingIndex(0);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (onboarding.step === "gemini-wrapper-psidts") {
|
|
654
|
+
const secure1psidts = value.trim();
|
|
655
|
+
const cookiesPath = saveGeminiWrapperCookieFile({
|
|
656
|
+
secure1psid: onboarding.secure1psid,
|
|
657
|
+
secure1psidts
|
|
658
|
+
});
|
|
659
|
+
process.env.PATCHPILOT_GEMINI_WRAPPER_MODE = "python";
|
|
660
|
+
process.env.PATCHPILOT_GEMINI_WRAPPER_COOKIES_JSON = cookiesPath;
|
|
661
|
+
savePatchPilotEnvValues({
|
|
662
|
+
PATCHPILOT_PROVIDER: "gemini-wrapper",
|
|
663
|
+
PATCHPILOT_MODEL: defaultGeminiWrapperModel,
|
|
664
|
+
PATCHPILOT_GEMINI_WRAPPER_MODE: "python",
|
|
665
|
+
PATCHPILOT_GEMINI_WRAPPER_COOKIES_JSON: cookiesPath
|
|
666
|
+
});
|
|
667
|
+
setOnboardingNotice({
|
|
668
|
+
tone: "success",
|
|
669
|
+
text: "Gemini-API bridge cookies saved to PatchPilot config.",
|
|
670
|
+
detail: `${cookiesPath} was written with owner-only permissions. PatchPilot will run gemini_webapi through python3.`
|
|
671
|
+
});
|
|
672
|
+
setOnboarding({
|
|
673
|
+
step: "gemini-wrapper-model-mode"
|
|
674
|
+
});
|
|
675
|
+
setOnboardingInput("");
|
|
676
|
+
setOnboardingIndex(0);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (onboarding.step === "gemini-wrapper-model-mode") {
|
|
680
|
+
const choice = readIndexedSelection(value, onboardingIndex);
|
|
681
|
+
if (choice === null) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (choice === 0) {
|
|
685
|
+
setTelemetry(null);
|
|
686
|
+
setModelOptions([defaultGeminiWrapperModel]);
|
|
687
|
+
setSettings((currentSettings) => ({
|
|
688
|
+
...currentSettings,
|
|
689
|
+
provider: "gemini-wrapper",
|
|
690
|
+
model: defaultGeminiWrapperModel
|
|
691
|
+
}));
|
|
692
|
+
savePatchPilotEnvValues({
|
|
693
|
+
PATCHPILOT_PROVIDER: "gemini-wrapper",
|
|
694
|
+
PATCHPILOT_MODEL: defaultGeminiWrapperModel,
|
|
695
|
+
PATCHPILOT_ONBOARDING_COMPLETE: "1"
|
|
696
|
+
});
|
|
697
|
+
appendLine({
|
|
698
|
+
tone: "success",
|
|
699
|
+
label: "onboarding",
|
|
700
|
+
text: `ready: gemini-wrapper using ${defaultGeminiWrapperModel}`
|
|
701
|
+
});
|
|
702
|
+
closeOnboarding();
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
await openModelSelection("gemini-wrapper", {
|
|
706
|
+
currentModel: settings.model
|
|
707
|
+
});
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (onboarding.step === "gemini-wrapper-url") {
|
|
711
|
+
const baseUrl = value.trim().replace(/\/$/, "");
|
|
712
|
+
if (!baseUrl) {
|
|
713
|
+
setOnboardingNotice({
|
|
714
|
+
tone: "warning",
|
|
715
|
+
text: "Gemini-Wrapper URL cannot be empty."
|
|
716
|
+
});
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
new URL(baseUrl);
|
|
721
|
+
}
|
|
722
|
+
catch {
|
|
723
|
+
setOnboardingNotice({
|
|
724
|
+
tone: "warning",
|
|
725
|
+
text: "Gemini-Wrapper URL must be a valid URL.",
|
|
726
|
+
detail: "Example: http://localhost:8787/v1"
|
|
727
|
+
});
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
process.env.PATCHPILOT_GEMINI_WRAPPER_BASE_URL = baseUrl;
|
|
731
|
+
savePatchPilotEnvValues({
|
|
732
|
+
PATCHPILOT_PROVIDER: "gemini-wrapper",
|
|
733
|
+
PATCHPILOT_MODEL: defaultGeminiWrapperModel,
|
|
734
|
+
PATCHPILOT_GEMINI_WRAPPER_BASE_URL: baseUrl
|
|
735
|
+
});
|
|
736
|
+
setOnboardingNotice({
|
|
737
|
+
tone: "success",
|
|
738
|
+
text: "Gemini-Wrapper URL saved to PatchPilot config.",
|
|
739
|
+
detail: "PatchPilot uses only this explicit URL and never reads browser cookies."
|
|
740
|
+
});
|
|
741
|
+
if (geminiWrapperRequiresApiKey(baseUrl) && !readGeminiWrapperApiKey()) {
|
|
742
|
+
setOnboarding({
|
|
743
|
+
step: "gemini-wrapper-key",
|
|
744
|
+
baseUrl
|
|
745
|
+
});
|
|
746
|
+
setOnboardingInput("");
|
|
747
|
+
setOnboardingIndex(0);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
await openModelSelection("gemini-wrapper", {
|
|
751
|
+
currentModel: defaultGeminiWrapperModel
|
|
752
|
+
});
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (onboarding.step === "gemini-wrapper-key") {
|
|
756
|
+
const apiKey = value.trim();
|
|
757
|
+
if (geminiWrapperRequiresApiKey(onboarding.baseUrl) && !apiKey) {
|
|
758
|
+
setOnboardingNotice({
|
|
759
|
+
tone: "warning",
|
|
760
|
+
text: "Gemini-Wrapper API key cannot be empty for remote wrapper URLs."
|
|
761
|
+
});
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
process.env.PATCHPILOT_GEMINI_WRAPPER_API_KEY = apiKey;
|
|
765
|
+
savePatchPilotEnvValues({
|
|
766
|
+
PATCHPILOT_PROVIDER: "gemini-wrapper",
|
|
767
|
+
PATCHPILOT_MODEL: defaultGeminiWrapperModel,
|
|
768
|
+
PATCHPILOT_GEMINI_WRAPPER_BASE_URL: onboarding.baseUrl,
|
|
769
|
+
...(apiKey ? { PATCHPILOT_GEMINI_WRAPPER_API_KEY: apiKey } : {})
|
|
770
|
+
});
|
|
771
|
+
setOnboardingNotice({
|
|
772
|
+
tone: "success",
|
|
773
|
+
text: apiKey ? "Gemini-Wrapper API key saved to PatchPilot config." : "Gemini-Wrapper local URL saved without an API key."
|
|
774
|
+
});
|
|
775
|
+
await openModelSelection("gemini-wrapper", {
|
|
776
|
+
currentModel: defaultGeminiWrapperModel
|
|
777
|
+
});
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
575
780
|
if (onboarding.step === "openrouter-key") {
|
|
576
781
|
const apiKey = value.trim();
|
|
577
782
|
if (!apiKey) {
|
|
@@ -635,8 +840,8 @@ export function App(props) {
|
|
|
635
840
|
return;
|
|
636
841
|
}
|
|
637
842
|
const visibleModels = selectableModels(onboardingInput, onboarding.models);
|
|
638
|
-
const selectedModel = selectModelFromInput(value, visibleModels, onboardingIndex, {
|
|
639
|
-
allowManual: onboarding.provider !== "ollama"
|
|
843
|
+
const selectedModel = visibleModels[onboardingIndex] ?? selectModelFromInput(value, visibleModels, onboardingIndex, {
|
|
844
|
+
allowManual: onboarding.provider !== "ollama" && onboarding.provider !== "gemini-wrapper"
|
|
640
845
|
});
|
|
641
846
|
if (!selectedModel) {
|
|
642
847
|
setOnboardingNotice({
|
|
@@ -672,7 +877,7 @@ export function App(props) {
|
|
|
672
877
|
}
|
|
673
878
|
closeOnboarding();
|
|
674
879
|
}, [activeHost?.host.url, appendLine, closeOnboarding, connectToHost, loadHostSuggestions, onboarding, onboardingBusyMessage, onboardingIndex, openModelSelection, settings.ollamaUrl]);
|
|
675
|
-
const runTask = useCallback(async (task) => {
|
|
880
|
+
const runTask = useCallback(async (task, overrides = {}) => {
|
|
676
881
|
if (!task.trim() || isRunning) {
|
|
677
882
|
return;
|
|
678
883
|
}
|
|
@@ -692,13 +897,17 @@ export function App(props) {
|
|
|
692
897
|
}
|
|
693
898
|
const abortController = new AbortController();
|
|
694
899
|
abortControllerRef.current = abortController;
|
|
900
|
+
const effectiveMode = overrides.mode ?? agentMode;
|
|
695
901
|
const taskRunner = new AgentRunner({
|
|
696
902
|
...runnableSettings,
|
|
697
|
-
|
|
903
|
+
allowExternalFileAnalysis: experimentalFlags.fileAnalysis,
|
|
904
|
+
memoryEnabled: experimentalFlags.memory,
|
|
905
|
+
mode: effectiveMode,
|
|
698
906
|
signal: abortController.signal,
|
|
699
907
|
sessionStore: sessionStoreRef.current,
|
|
908
|
+
resumeContext,
|
|
700
909
|
approvalHandler: (request) => new Promise((resolve) => {
|
|
701
|
-
if (
|
|
910
|
+
if (effectiveMode === "plan") {
|
|
702
911
|
appendLine({
|
|
703
912
|
kind: "approval",
|
|
704
913
|
tone: "warning",
|
|
@@ -712,13 +921,14 @@ export function App(props) {
|
|
|
712
921
|
resolve("deny");
|
|
713
922
|
return;
|
|
714
923
|
}
|
|
715
|
-
if (
|
|
924
|
+
if (effectiveMode === "bypass" && ((request.permission === "write" && runnableSettings.allowWrite) || (request.permission === "shell" && runnableSettings.allowShell))) {
|
|
716
925
|
resolve("allow_session");
|
|
717
926
|
return;
|
|
718
927
|
}
|
|
719
928
|
setPendingApproval(request);
|
|
720
929
|
setWorkState("waiting_approval");
|
|
721
930
|
setStatus(`approval needed for ${request.tool}`);
|
|
931
|
+
setTranscriptScrollOffset(0);
|
|
722
932
|
appendLine({
|
|
723
933
|
kind: "approval",
|
|
724
934
|
tone: "warning",
|
|
@@ -769,7 +979,7 @@ export function App(props) {
|
|
|
769
979
|
setWorkState("idle");
|
|
770
980
|
setIsRunning(false);
|
|
771
981
|
}
|
|
772
|
-
}, [agentMode, appendLine, isRunning, modelOptions, settings]);
|
|
982
|
+
}, [agentMode, appendLine, experimentalFlags, isRunning, modelOptions, resumeContext, settings]);
|
|
773
983
|
const handleSlashCommand = useCallback(async (rawCommand) => {
|
|
774
984
|
const [commandName = "", ...args] = rawCommand.slice(1).trim().split(/\s+/);
|
|
775
985
|
const command = commandName.toLowerCase();
|
|
@@ -818,11 +1028,11 @@ export function App(props) {
|
|
|
818
1028
|
return;
|
|
819
1029
|
case "provider": {
|
|
820
1030
|
const nextProvider = args[0]?.toLowerCase();
|
|
821
|
-
if (nextProvider !== "ollama" && nextProvider !== "gemini" && nextProvider !== "codex" && nextProvider !== "openrouter" && nextProvider !== "nvidia") {
|
|
1031
|
+
if (nextProvider !== "ollama" && nextProvider !== "gemini" && nextProvider !== "gemini-wrapper" && nextProvider !== "codex" && nextProvider !== "openrouter" && nextProvider !== "nvidia") {
|
|
822
1032
|
appendLine({
|
|
823
1033
|
tone: "accent",
|
|
824
1034
|
label: "provider",
|
|
825
|
-
text: `current ${settings.provider}. Use /provider ollama, gemini, openrouter, nvidia, or codex.`
|
|
1035
|
+
text: `current ${settings.provider}. Use /provider ollama, gemini, gemini-wrapper, openrouter, nvidia, or codex.`
|
|
826
1036
|
});
|
|
827
1037
|
return;
|
|
828
1038
|
}
|
|
@@ -845,7 +1055,7 @@ export function App(props) {
|
|
|
845
1055
|
tone: needsApiKey(nextProvider) && !hasApiKey(nextProvider) ? "warning" : "success",
|
|
846
1056
|
label: "provider",
|
|
847
1057
|
text: needsApiKey(nextProvider) && !hasApiKey(nextProvider)
|
|
848
|
-
? `${nextProvider} needs
|
|
1058
|
+
? `${nextProvider} needs setup. Setup opened.`
|
|
849
1059
|
: `switched to ${nextProvider} using ${nextModel}`
|
|
850
1060
|
});
|
|
851
1061
|
return;
|
|
@@ -865,6 +1075,10 @@ export function App(props) {
|
|
|
865
1075
|
...currentSettings,
|
|
866
1076
|
subagents: subagentsEnabled
|
|
867
1077
|
}));
|
|
1078
|
+
setExperimentalFlags((currentFlags) => ({
|
|
1079
|
+
...currentFlags,
|
|
1080
|
+
subagents: subagentsEnabled
|
|
1081
|
+
}));
|
|
868
1082
|
appendLine({
|
|
869
1083
|
tone: "success",
|
|
870
1084
|
label: "agents",
|
|
@@ -921,38 +1135,30 @@ export function App(props) {
|
|
|
921
1135
|
case "write":
|
|
922
1136
|
case "apply": {
|
|
923
1137
|
const writeEnabled = readToggle(args[0], !settings.allowWrite);
|
|
924
|
-
|
|
925
|
-
requestBypassMode();
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
grantedPermissionsRef.current.allowWrite = writeEnabled;
|
|
929
|
-
applyMode("build", false);
|
|
1138
|
+
setExplicitPermission("write", writeEnabled);
|
|
930
1139
|
appendLine({
|
|
931
1140
|
tone: "success",
|
|
932
1141
|
label: "write",
|
|
933
|
-
text: "workspace writes
|
|
1142
|
+
text: writeEnabled ? "workspace writes are allowed; shell remains separately controlled" : "workspace writes disabled"
|
|
934
1143
|
});
|
|
935
1144
|
return;
|
|
936
1145
|
}
|
|
937
1146
|
case "shell": {
|
|
938
1147
|
const shellEnabled = readToggle(args[0], !settings.allowShell);
|
|
939
|
-
|
|
940
|
-
requestBypassMode();
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
grantedPermissionsRef.current.allowShell = shellEnabled;
|
|
944
|
-
applyMode("build", false);
|
|
1148
|
+
setExplicitPermission("shell", shellEnabled);
|
|
945
1149
|
appendLine({
|
|
946
1150
|
tone: "success",
|
|
947
1151
|
label: "shell",
|
|
948
|
-
text: "shell commands
|
|
1152
|
+
text: shellEnabled ? "shell commands are allowed; writes remain separately controlled" : "shell commands disabled"
|
|
949
1153
|
});
|
|
950
1154
|
return;
|
|
951
1155
|
}
|
|
952
1156
|
case "model": {
|
|
953
1157
|
const requestedModel = normalizeModelAlias(args.join(" ").trim());
|
|
954
1158
|
if (!requestedModel) {
|
|
955
|
-
const models = await loadKnownOrAvailableModels(settings.provider, settings.ollamaUrl, modelOptions, setModelOptions, appendLine
|
|
1159
|
+
const models = await loadKnownOrAvailableModels(settings.provider, settings.ollamaUrl, modelOptions, setModelOptions, appendLine, {
|
|
1160
|
+
refresh: settings.provider === "gemini-wrapper"
|
|
1161
|
+
});
|
|
956
1162
|
if (!models) {
|
|
957
1163
|
return;
|
|
958
1164
|
}
|
|
@@ -965,12 +1171,14 @@ export function App(props) {
|
|
|
965
1171
|
return;
|
|
966
1172
|
}
|
|
967
1173
|
{
|
|
968
|
-
const models = await loadKnownOrAvailableModels(settings.provider, settings.ollamaUrl, modelOptions, setModelOptions, appendLine
|
|
1174
|
+
const models = await loadKnownOrAvailableModels(settings.provider, settings.ollamaUrl, modelOptions, setModelOptions, appendLine, {
|
|
1175
|
+
refresh: settings.provider === "gemini-wrapper"
|
|
1176
|
+
});
|
|
969
1177
|
if (!models) {
|
|
970
1178
|
return;
|
|
971
1179
|
}
|
|
972
1180
|
const nextModel = selectModelFromInput(requestedModel, models, undefined, {
|
|
973
|
-
allowManual: settings.provider !== "ollama"
|
|
1181
|
+
allowManual: settings.provider !== "ollama" && settings.provider !== "gemini-wrapper"
|
|
974
1182
|
});
|
|
975
1183
|
if (!nextModel) {
|
|
976
1184
|
appendLine({
|
|
@@ -988,12 +1196,14 @@ export function App(props) {
|
|
|
988
1196
|
case "models": {
|
|
989
1197
|
const requestedModel = args.join(" ").trim();
|
|
990
1198
|
if (requestedModel) {
|
|
991
|
-
const installedModels = await loadKnownOrAvailableModels(settings.provider, settings.ollamaUrl, modelOptions, setModelOptions, appendLine
|
|
1199
|
+
const installedModels = await loadKnownOrAvailableModels(settings.provider, settings.ollamaUrl, modelOptions, setModelOptions, appendLine, {
|
|
1200
|
+
refresh: settings.provider === "gemini-wrapper"
|
|
1201
|
+
});
|
|
992
1202
|
if (!installedModels) {
|
|
993
1203
|
return;
|
|
994
1204
|
}
|
|
995
1205
|
const nextModel = selectModelFromInput(requestedModel, installedModels, undefined, {
|
|
996
|
-
allowManual: settings.provider !== "ollama"
|
|
1206
|
+
allowManual: settings.provider !== "ollama" && settings.provider !== "gemini-wrapper"
|
|
997
1207
|
});
|
|
998
1208
|
if (!nextModel) {
|
|
999
1209
|
appendLine({
|
|
@@ -1071,11 +1281,26 @@ export function App(props) {
|
|
|
1071
1281
|
const sessionId = args[0] ?? "";
|
|
1072
1282
|
const sessions = await listWorkspaceSessions(settings.workspace);
|
|
1073
1283
|
const selectedSession = sessionId ? await loadSessionSummary(settings.workspace, sessionId) : sessions[0] ?? null;
|
|
1284
|
+
if (selectedSession) {
|
|
1285
|
+
sessionStoreRef.current = new SessionStore({
|
|
1286
|
+
workspace: settings.workspace,
|
|
1287
|
+
sessionId: selectedSession.sessionId
|
|
1288
|
+
});
|
|
1289
|
+
await sessionStoreRef.current.append({
|
|
1290
|
+
type: "session.resumed",
|
|
1291
|
+
sessionId: selectedSession.sessionId,
|
|
1292
|
+
workspace: settings.workspace,
|
|
1293
|
+
resumedAt: new Date().toISOString()
|
|
1294
|
+
});
|
|
1295
|
+
setResumeContext(await buildSessionResumeContext(settings.workspace, selectedSession.sessionId));
|
|
1296
|
+
setSessionTelemetry(emptySessionTelemetry());
|
|
1297
|
+
setTelemetry(null);
|
|
1298
|
+
}
|
|
1074
1299
|
appendLine({
|
|
1075
1300
|
kind: "status",
|
|
1076
1301
|
tone: selectedSession ? "accent" : "warning",
|
|
1077
1302
|
label: "resume",
|
|
1078
|
-
text: selectedSession ? `Loaded session ${selectedSession.sessionId}
|
|
1303
|
+
text: selectedSession ? `Loaded session ${selectedSession.sessionId} and will inject its summary into the next run.` : "No session available to resume.",
|
|
1079
1304
|
detail: selectedSession
|
|
1080
1305
|
? `workspace ${selectedSession.workspace}\nupdated ${selectedSession.updatedAt}\nmodel ${selectedSession.provider ?? "-"} ${selectedSession.model ?? "-"}\nlast task ${selectedSession.lastTask ?? "-"}`
|
|
1081
1306
|
: "Run /sessions after at least one PatchPilot run."
|
|
@@ -1193,28 +1418,150 @@ export function App(props) {
|
|
|
1193
1418
|
return;
|
|
1194
1419
|
}
|
|
1195
1420
|
case "doctor": {
|
|
1421
|
+
const shouldFix = args.some((arg) => arg.toLowerCase() === "fix" || arg.toLowerCase() === "--fix");
|
|
1196
1422
|
appendLine({
|
|
1197
1423
|
tone: "muted",
|
|
1198
1424
|
label: "doctor",
|
|
1199
|
-
text: "checking local requirements..."
|
|
1425
|
+
text: shouldFix ? "checking local requirements and applying safe fixes..." : "checking local requirements..."
|
|
1426
|
+
});
|
|
1427
|
+
const doctorResults = await runDoctor(settings.provider, settings.ollamaUrl, settings.model, {
|
|
1428
|
+
fix: shouldFix
|
|
1200
1429
|
});
|
|
1201
|
-
const doctorResults = await runDoctor(settings.provider, settings.ollamaUrl, settings.model);
|
|
1202
1430
|
for (const result of doctorResults) {
|
|
1203
1431
|
appendLine({
|
|
1204
1432
|
tone: result.ok ? "success" : "danger",
|
|
1205
1433
|
label: result.name,
|
|
1206
|
-
text: result.details
|
|
1434
|
+
text: result.action && result.action !== "check" ? `${result.action}: ${result.details}` : result.details
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
if (!shouldFix && doctorResults.some((result) => result.action === "skipped")) {
|
|
1438
|
+
appendLine({
|
|
1439
|
+
tone: "accent",
|
|
1440
|
+
label: "doctor",
|
|
1441
|
+
text: "Some safe fixes are available. Run /doctor fix to approve them."
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
case "cleanup": {
|
|
1447
|
+
const target = readCleanupTarget(args[0]);
|
|
1448
|
+
if (!target) {
|
|
1449
|
+
appendLine({
|
|
1450
|
+
tone: "accent",
|
|
1451
|
+
label: "cleanup",
|
|
1452
|
+
text: "Choose what to clean: /cleanup cache, /cleanup sessions, /cleanup temp, or /cleanup all.",
|
|
1453
|
+
detail: "Sessions deletes saved workspace transcripts. Cache/temp are safe first choices."
|
|
1207
1454
|
});
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const removed = await cleanupPatchPilot(settings.workspace, target);
|
|
1458
|
+
if (target === "sessions" || target === "all") {
|
|
1459
|
+
sessionStoreRef.current = new SessionStore({
|
|
1460
|
+
workspace: settings.workspace
|
|
1461
|
+
});
|
|
1462
|
+
await sessionStoreRef.current.create();
|
|
1463
|
+
setResumeContext("");
|
|
1464
|
+
setLines([]);
|
|
1465
|
+
setAdvisorNotes([]);
|
|
1466
|
+
setTelemetry(null);
|
|
1467
|
+
setSessionTelemetry(emptySessionTelemetry());
|
|
1468
|
+
}
|
|
1469
|
+
appendLine({
|
|
1470
|
+
tone: "success",
|
|
1471
|
+
label: "cleanup",
|
|
1472
|
+
text: `cleaned ${removed.join(", ") || target}`
|
|
1473
|
+
});
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
case "experimental": {
|
|
1477
|
+
const requestedFlag = args[0]?.toLowerCase();
|
|
1478
|
+
const requestedValue = args[1]?.toLowerCase();
|
|
1479
|
+
if (!requestedFlag) {
|
|
1480
|
+
setExperimentalOpen(true);
|
|
1481
|
+
setExperimentalIndex(0);
|
|
1482
|
+
setInput("");
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
const enabled = readToggle(requestedValue, true);
|
|
1486
|
+
if (requestedFlag === "subagents" || requestedFlag === "agents") {
|
|
1487
|
+
setSettings((currentSettings) => ({
|
|
1488
|
+
...currentSettings,
|
|
1489
|
+
subagents: enabled
|
|
1490
|
+
}));
|
|
1208
1491
|
}
|
|
1492
|
+
savePatchPilotEnvValues({
|
|
1493
|
+
[`PATCHPILOT_EXPERIMENTAL_${requestedFlag.replace(/-/g, "_").toUpperCase()}`]: enabled ? "1" : "0"
|
|
1494
|
+
});
|
|
1495
|
+
setExperimentalFlags((currentFlags) => ({
|
|
1496
|
+
...currentFlags,
|
|
1497
|
+
...(requestedFlag === "file-analysis"
|
|
1498
|
+
? { fileAnalysis: enabled }
|
|
1499
|
+
: requestedFlag === "memory"
|
|
1500
|
+
? { memory: enabled }
|
|
1501
|
+
: requestedFlag === "subagents" || requestedFlag === "agents"
|
|
1502
|
+
? { subagents: enabled }
|
|
1503
|
+
: {})
|
|
1504
|
+
}));
|
|
1505
|
+
appendLine({
|
|
1506
|
+
tone: "success",
|
|
1507
|
+
label: "experimental",
|
|
1508
|
+
text: `${requestedFlag} ${enabled ? "enabled" : "disabled"}`
|
|
1509
|
+
});
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
case "init": {
|
|
1513
|
+
await ensurePatchPilotGitignore(settings.workspace);
|
|
1514
|
+
appendLine({
|
|
1515
|
+
tone: "accent",
|
|
1516
|
+
label: "init",
|
|
1517
|
+
text: "starting model-driven project init",
|
|
1518
|
+
detail: "PatchPilot will inspect the repository and create or update PATCHPILOT.md with approval-gated writes."
|
|
1519
|
+
});
|
|
1520
|
+
await runTask(patchPilotInitPrompt, {
|
|
1521
|
+
mode: "build"
|
|
1522
|
+
});
|
|
1209
1523
|
return;
|
|
1210
1524
|
}
|
|
1211
1525
|
case "clear":
|
|
1526
|
+
setLines([]);
|
|
1527
|
+
setAdvisorNotes([]);
|
|
1528
|
+
setTelemetry(null);
|
|
1529
|
+
setResumeContext("");
|
|
1530
|
+
setSessionTelemetry(emptySessionTelemetry());
|
|
1531
|
+
setTranscriptScrollOffset(0);
|
|
1532
|
+
setSessionScrollOffset(0);
|
|
1533
|
+
return;
|
|
1534
|
+
case "new":
|
|
1535
|
+
if (isRunning) {
|
|
1536
|
+
appendLine({
|
|
1537
|
+
tone: "warning",
|
|
1538
|
+
label: "new",
|
|
1539
|
+
text: "Cannot start a new session while a run is active.",
|
|
1540
|
+
detail: "Stop the current run first, then use /new again."
|
|
1541
|
+
});
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
sessionStoreRef.current = new SessionStore({
|
|
1545
|
+
workspace: settings.workspace
|
|
1546
|
+
});
|
|
1547
|
+
await sessionStoreRef.current.create();
|
|
1212
1548
|
setLines([]);
|
|
1213
1549
|
setAdvisorNotes([]);
|
|
1214
1550
|
setTelemetry(null);
|
|
1215
1551
|
setSessionTelemetry(emptySessionTelemetry());
|
|
1552
|
+
setPendingApproval(null);
|
|
1553
|
+
approvalResolverRef.current = null;
|
|
1554
|
+
setBypassConfirmation(false);
|
|
1555
|
+
setInput("");
|
|
1216
1556
|
setTranscriptScrollOffset(0);
|
|
1217
1557
|
setSessionScrollOffset(0);
|
|
1558
|
+
setStatus("idle");
|
|
1559
|
+
setWorkState("idle");
|
|
1560
|
+
appendLine({
|
|
1561
|
+
tone: "success",
|
|
1562
|
+
label: "new",
|
|
1563
|
+
text: `started session ${sessionStoreRef.current.sessionId}`
|
|
1564
|
+
});
|
|
1218
1565
|
return;
|
|
1219
1566
|
case "exit":
|
|
1220
1567
|
case "quit":
|
|
@@ -1242,6 +1589,7 @@ export function App(props) {
|
|
|
1242
1589
|
loadHostSuggestions,
|
|
1243
1590
|
loadProviderModels,
|
|
1244
1591
|
modelOptions,
|
|
1592
|
+
isRunning,
|
|
1245
1593
|
resolveApproval,
|
|
1246
1594
|
sessionTelemetry,
|
|
1247
1595
|
settings,
|
|
@@ -1249,7 +1597,14 @@ export function App(props) {
|
|
|
1249
1597
|
]);
|
|
1250
1598
|
const handleSubmit = useCallback(async (value) => {
|
|
1251
1599
|
const nextValue = value.trim();
|
|
1252
|
-
if (!nextValue
|
|
1600
|
+
if (!nextValue) {
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (isRunning && nextValue.startsWith("/")) {
|
|
1604
|
+
await handleSlashCommand(nextValue);
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
if (isRunning) {
|
|
1253
1608
|
return;
|
|
1254
1609
|
}
|
|
1255
1610
|
if (onboarding) {
|
|
@@ -1276,6 +1631,10 @@ export function App(props) {
|
|
|
1276
1631
|
useEffect(() => {
|
|
1277
1632
|
void sessionStoreRef.current.create();
|
|
1278
1633
|
}, []);
|
|
1634
|
+
useEffect(() => {
|
|
1635
|
+
runtimeStateRef.current.isRunning = isRunning;
|
|
1636
|
+
runtimeStateRef.current.hasPendingApproval = Boolean(pendingApproval || bypassConfirmation);
|
|
1637
|
+
}, [bypassConfirmation, isRunning, pendingApproval]);
|
|
1279
1638
|
useEffect(() => {
|
|
1280
1639
|
if (!props.initialTask || didRunInitialTask.current || onboarding || process.env.PATCHPILOT_ONBOARDING_COMPLETE !== "1") {
|
|
1281
1640
|
return;
|
|
@@ -1364,8 +1723,50 @@ export function App(props) {
|
|
|
1364
1723
|
}
|
|
1365
1724
|
}, [hostOptions.length, input, isLoadingHosts, isLoadingModels, isRunning, loadHostSuggestions, loadProviderModels, modelOptions.length, onboarding, settings.provider]);
|
|
1366
1725
|
useInput((inputValue, key) => {
|
|
1726
|
+
if (experimentalOpen) {
|
|
1727
|
+
if (key.upArrow) {
|
|
1728
|
+
setExperimentalIndex((currentIndex) => (currentIndex - 1 + experimentalFlagCount()) % experimentalFlagCount());
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
if (key.downArrow) {
|
|
1732
|
+
setExperimentalIndex((currentIndex) => (currentIndex + 1) % experimentalFlagCount());
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
if (inputValue === " ") {
|
|
1736
|
+
const flag = experimentalFlagAt(experimentalIndex);
|
|
1737
|
+
setExperimentalFlags((currentFlags) => {
|
|
1738
|
+
const nextFlags = {
|
|
1739
|
+
...currentFlags,
|
|
1740
|
+
[flag]: !currentFlags[flag]
|
|
1741
|
+
};
|
|
1742
|
+
if (flag === "subagents") {
|
|
1743
|
+
setSettings((currentSettings) => ({
|
|
1744
|
+
...currentSettings,
|
|
1745
|
+
subagents: nextFlags.subagents
|
|
1746
|
+
}));
|
|
1747
|
+
}
|
|
1748
|
+
savePatchPilotEnvValues({
|
|
1749
|
+
PATCHPILOT_EXPERIMENTAL_FILE_ANALYSIS: nextFlags.fileAnalysis ? "1" : "0",
|
|
1750
|
+
PATCHPILOT_EXPERIMENTAL_MEMORY: nextFlags.memory ? "1" : "0",
|
|
1751
|
+
PATCHPILOT_EXPERIMENTAL_SUBAGENTS: nextFlags.subagents ? "1" : "0"
|
|
1752
|
+
});
|
|
1753
|
+
return nextFlags;
|
|
1754
|
+
});
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (key.return || key.escape || key.leftArrow) {
|
|
1758
|
+
setExperimentalOpen(false);
|
|
1759
|
+
setInput("");
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1367
1764
|
if (bypassConfirmation) {
|
|
1368
1765
|
const normalizedInput = inputValue.toLowerCase();
|
|
1766
|
+
if (key.tab) {
|
|
1767
|
+
cancelBypassMode();
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1369
1770
|
if (normalizedInput === "y") {
|
|
1370
1771
|
confirmBypassMode();
|
|
1371
1772
|
return;
|
|
@@ -1418,7 +1819,7 @@ export function App(props) {
|
|
|
1418
1819
|
setOnboardingIndex((currentIndex) => (currentIndex + 1) % optionCount);
|
|
1419
1820
|
return;
|
|
1420
1821
|
}
|
|
1421
|
-
if (optionCount > 0 && key.return
|
|
1822
|
+
if (optionCount > 0 && key.return) {
|
|
1422
1823
|
void handleOnboardingSubmit(String(onboardingIndex + 1));
|
|
1423
1824
|
return;
|
|
1424
1825
|
}
|
|
@@ -1443,6 +1844,16 @@ export function App(props) {
|
|
|
1443
1844
|
}
|
|
1444
1845
|
}
|
|
1445
1846
|
const canUsePanelKeys = input.length === 0 || isRunning;
|
|
1847
|
+
if (canUsePanelKeys && key.upArrow && paletteItems.length === 0) {
|
|
1848
|
+
const setOffset = activeScrollPane === "session" ? setSessionScrollOffset : setTranscriptScrollOffset;
|
|
1849
|
+
setOffset((currentOffset) => currentOffset + 1);
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
if (canUsePanelKeys && key.downArrow && paletteItems.length === 0) {
|
|
1853
|
+
const setOffset = activeScrollPane === "session" ? setSessionScrollOffset : setTranscriptScrollOffset;
|
|
1854
|
+
setOffset((currentOffset) => Math.max(0, currentOffset - 1));
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1446
1857
|
if (canUsePanelKeys && key.leftArrow) {
|
|
1447
1858
|
setActiveScrollPane("session");
|
|
1448
1859
|
return;
|
|
@@ -1476,19 +1887,44 @@ export function App(props) {
|
|
|
1476
1887
|
}
|
|
1477
1888
|
});
|
|
1478
1889
|
useEffect(() => {
|
|
1890
|
+
const gracefulStopOrExit = () => {
|
|
1891
|
+
const now = Date.now();
|
|
1892
|
+
const state = runtimeStateRef.current;
|
|
1893
|
+
if ((state.isRunning || state.hasPendingApproval) && now - state.lastSigintAt > 1500) {
|
|
1894
|
+
state.lastSigintAt = now;
|
|
1895
|
+
abortControllerRef.current?.abort();
|
|
1896
|
+
approvalResolverRef.current?.("deny");
|
|
1897
|
+
approvalResolverRef.current = null;
|
|
1898
|
+
setPendingApproval(null);
|
|
1899
|
+
setBypassConfirmation(false);
|
|
1900
|
+
setInput("");
|
|
1901
|
+
setStatus("stopping");
|
|
1902
|
+
setWorkState("idle");
|
|
1903
|
+
appendLine({
|
|
1904
|
+
kind: "status",
|
|
1905
|
+
tone: "warning",
|
|
1906
|
+
label: "stop",
|
|
1907
|
+
text: "Stopping current task. Press Ctrl-C again to quit."
|
|
1908
|
+
});
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
void unloadUsedOllamaModels(usedOllamaModelsRef.current).finally(() => {
|
|
1912
|
+
process.exit(0);
|
|
1913
|
+
});
|
|
1914
|
+
};
|
|
1479
1915
|
const unloadAndExit = () => {
|
|
1480
1916
|
void unloadUsedOllamaModels(usedOllamaModelsRef.current).finally(() => {
|
|
1481
1917
|
process.exit(0);
|
|
1482
1918
|
});
|
|
1483
1919
|
};
|
|
1484
|
-
process.
|
|
1485
|
-
process.
|
|
1920
|
+
process.on("SIGINT", gracefulStopOrExit);
|
|
1921
|
+
process.on("SIGTERM", unloadAndExit);
|
|
1486
1922
|
return () => {
|
|
1487
|
-
process.off("SIGINT",
|
|
1923
|
+
process.off("SIGINT", gracefulStopOrExit);
|
|
1488
1924
|
process.off("SIGTERM", unloadAndExit);
|
|
1489
1925
|
void unloadUsedOllamaModels(usedOllamaModelsRef.current);
|
|
1490
1926
|
};
|
|
1491
|
-
}, []);
|
|
1927
|
+
}, [appendLine]);
|
|
1492
1928
|
useEffect(() => {
|
|
1493
1929
|
let previousSnapshot = readSystemStats().snapshot;
|
|
1494
1930
|
const timer = setInterval(() => {
|
|
@@ -1517,10 +1953,10 @@ export function App(props) {
|
|
|
1517
1953
|
clearInterval(timer);
|
|
1518
1954
|
};
|
|
1519
1955
|
}, []);
|
|
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 })] })] }))] }));
|
|
1956
|
+
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 }), experimentalOpen ? (_jsx(ExperimentalPanel, { flags: experimentalFlags, selectedIndex: experimentalIndex, height: panelHeight })) : 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 + approvalReservedHeight + 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 + approvalReservedHeight + composerReservedHeight + paletteReservedHeight + footerReservedHeight, overflowY: "hidden", children: [_jsx(Transcript, { lines: lines, isRunning: isRunning, isActive: activeScrollPane === "transcript", height: panelHeight, width: transcriptWidth, scrollOffset: transcriptScrollOffset }), _jsx(ApprovalPanel, { request: pendingApproval, bypassConfirmation: bypassConfirmation }), _jsx(Composer, { input: input, isRunning: isRunning, status: status, draftTokens: draftTokens, isApprovalWaiting: Boolean(pendingApproval || bypassConfirmation), onChange: setInput, onSubmit: (value) => void handleSubmit(value) }), paletteItems.length > 0 ? _jsx(CommandSuggestions, { items: paletteItems, selectedIndex: paletteIndex }) : null, _jsx(FooterHints, { activePane: activeScrollPane })] })] }))] }));
|
|
1521
1957
|
}
|
|
1522
1958
|
async function loadAvailableModels(provider, ollamaUrl, setModelOptions, refresh = false) {
|
|
1523
|
-
const cacheKey =
|
|
1959
|
+
const cacheKey = modelCacheKey(provider, ollamaUrl);
|
|
1524
1960
|
const cachedModels = modelCache.get(cacheKey);
|
|
1525
1961
|
if (!refresh && cachedModels && cachedModels.expiresAt > Date.now()) {
|
|
1526
1962
|
setModelOptions(cachedModels.models);
|
|
@@ -1537,9 +1973,24 @@ async function loadAvailableModels(provider, ollamaUrl, setModelOptions, refresh
|
|
|
1537
1973
|
setModelOptions(models);
|
|
1538
1974
|
return models;
|
|
1539
1975
|
}
|
|
1540
|
-
|
|
1976
|
+
function modelCacheKey(provider, ollamaUrl) {
|
|
1977
|
+
if (provider === "ollama") {
|
|
1978
|
+
return `${provider}:${ollamaUrl}`;
|
|
1979
|
+
}
|
|
1980
|
+
if (provider === "gemini-wrapper") {
|
|
1981
|
+
return [
|
|
1982
|
+
provider,
|
|
1983
|
+
readGeminiWrapperMode(),
|
|
1984
|
+
readGeminiWrapperBaseUrl() || "python",
|
|
1985
|
+
readGeminiWrapperPythonCommand(),
|
|
1986
|
+
readGeminiWrapperCookiesJson()
|
|
1987
|
+
].join(":");
|
|
1988
|
+
}
|
|
1989
|
+
return `${provider}:default`;
|
|
1990
|
+
}
|
|
1991
|
+
async function loadKnownOrAvailableModels(provider, ollamaUrl, modelOptions, setModelOptions, appendLine, options = {}) {
|
|
1541
1992
|
try {
|
|
1542
|
-
return modelOptions.length > 0 ? modelOptions : await loadAvailableModels(provider, ollamaUrl, setModelOptions);
|
|
1993
|
+
return !options.refresh && modelOptions.length > 0 ? modelOptions : await loadAvailableModels(provider, ollamaUrl, setModelOptions, options.refresh);
|
|
1543
1994
|
}
|
|
1544
1995
|
catch (error) {
|
|
1545
1996
|
appendLine({
|
|
@@ -1563,7 +2014,7 @@ async function switchModel(provider, nextModel, ollamaUrl, currentModel, appendL
|
|
|
1563
2014
|
if (!installedModels) {
|
|
1564
2015
|
return;
|
|
1565
2016
|
}
|
|
1566
|
-
if (!installedModels.includes(nextModel)) {
|
|
2017
|
+
if (!installedModels.includes(nextModel) && !canUseUnverifiedCloudModel(provider, nextModel)) {
|
|
1567
2018
|
appendLine({
|
|
1568
2019
|
tone: "warning",
|
|
1569
2020
|
label: "model",
|
|
@@ -1574,9 +2025,11 @@ async function switchModel(provider, nextModel, ollamaUrl, currentModel, appendL
|
|
|
1574
2025
|
? "No models installed on the selected host."
|
|
1575
2026
|
: provider === "gemini"
|
|
1576
2027
|
? "Check GEMINI_API_KEY in PatchPilot config."
|
|
1577
|
-
: provider === "
|
|
1578
|
-
? "Check
|
|
1579
|
-
:
|
|
2028
|
+
: provider === "gemini-wrapper"
|
|
2029
|
+
? "Check PATCHPILOT_GEMINI_WRAPPER_BASE_URL in PatchPilot config."
|
|
2030
|
+
: provider === "openrouter"
|
|
2031
|
+
? "Check OPENROUTER_API_KEY in PatchPilot config."
|
|
2032
|
+
: "Run codex login first."
|
|
1580
2033
|
});
|
|
1581
2034
|
return;
|
|
1582
2035
|
}
|
|
@@ -1590,9 +2043,10 @@ async function switchModel(provider, nextModel, ollamaUrl, currentModel, appendL
|
|
|
1590
2043
|
PATCHPILOT_MODEL: nextModel
|
|
1591
2044
|
});
|
|
1592
2045
|
appendLine({
|
|
1593
|
-
tone: "success",
|
|
2046
|
+
tone: installedModels.includes(nextModel) ? "success" : "warning",
|
|
1594
2047
|
label: "model",
|
|
1595
|
-
text: `switched to ${nextModel}`
|
|
2048
|
+
text: installedModels.includes(nextModel) ? `switched to ${nextModel}` : `switched to unverified ${provider} model ${nextModel}`,
|
|
2049
|
+
detail: installedModels.includes(nextModel) ? undefined : "The provider did not list this model in discovery. PatchPilot will try it and surface the provider error if it is unavailable."
|
|
1596
2050
|
});
|
|
1597
2051
|
if (provider === "openrouter" && isOpenRouterFreeModel(nextModel)) {
|
|
1598
2052
|
appendLine({
|
|
@@ -1618,7 +2072,15 @@ async function resolveRunnableSettings(settings, modelOptions, appendLine, setMo
|
|
|
1618
2072
|
});
|
|
1619
2073
|
return null;
|
|
1620
2074
|
}
|
|
1621
|
-
if (installedModels.includes(settings.model)) {
|
|
2075
|
+
if (installedModels.includes(settings.model) || canUseUnverifiedCloudModel(settings.provider, settings.model)) {
|
|
2076
|
+
if (!installedModels.includes(settings.model)) {
|
|
2077
|
+
appendLine({
|
|
2078
|
+
tone: "warning",
|
|
2079
|
+
label: "model",
|
|
2080
|
+
text: `using unverified ${settings.provider} model ${settings.model}`,
|
|
2081
|
+
detail: "Model discovery did not list it; the next provider request will be the compatibility check."
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
1622
2084
|
return settings;
|
|
1623
2085
|
}
|
|
1624
2086
|
appendLine({
|
|
@@ -1631,9 +2093,11 @@ async function resolveRunnableSettings(settings, modelOptions, appendLine, setMo
|
|
|
1631
2093
|
? "No models installed on the selected host."
|
|
1632
2094
|
: settings.provider === "gemini"
|
|
1633
2095
|
? "No Gemini models listed. Check GEMINI_API_KEY in PatchPilot config."
|
|
1634
|
-
: settings.provider === "
|
|
1635
|
-
? "No
|
|
1636
|
-
:
|
|
2096
|
+
: settings.provider === "gemini-wrapper"
|
|
2097
|
+
? "No Gemini-Wrapper models listed. Check gemini_webapi install and PATCHPILOT_GEMINI_WRAPPER_COOKIES_JSON in PatchPilot config."
|
|
2098
|
+
: settings.provider === "openrouter"
|
|
2099
|
+
? "No OpenRouter models listed. Check OPENROUTER_API_KEY in PatchPilot config."
|
|
2100
|
+
: "Codex OAuth is not ready. Run codex login."
|
|
1637
2101
|
});
|
|
1638
2102
|
return null;
|
|
1639
2103
|
}
|
|
@@ -1643,7 +2107,6 @@ function buildCommandSuggestionItems(options) {
|
|
|
1643
2107
|
}
|
|
1644
2108
|
const trimmedInput = options.input.trimStart().toLowerCase();
|
|
1645
2109
|
const items = filterSlashCommands(options.input)
|
|
1646
|
-
.slice(0, 6)
|
|
1647
2110
|
.map((command) => {
|
|
1648
2111
|
const baseCommand = `/${command.name}`;
|
|
1649
2112
|
return {
|
|
@@ -1678,7 +2141,7 @@ function buildCommandSuggestionItems(options) {
|
|
|
1678
2141
|
})));
|
|
1679
2142
|
}
|
|
1680
2143
|
}
|
|
1681
|
-
if (trimmedInput
|
|
2144
|
+
if (trimmedInput.startsWith("/models ") || trimmedInput.startsWith("/model ")) {
|
|
1682
2145
|
const modelQuery = trimmedInput.replace(/^\/models?/, "").trim();
|
|
1683
2146
|
if (options.isLoadingModels) {
|
|
1684
2147
|
items.unshift({
|
|
@@ -1701,16 +2164,18 @@ function buildCommandSuggestionItems(options) {
|
|
|
1701
2164
|
})));
|
|
1702
2165
|
}
|
|
1703
2166
|
}
|
|
1704
|
-
return items
|
|
2167
|
+
return items;
|
|
1705
2168
|
}
|
|
1706
2169
|
function getOnboardingOptionCount(onboarding) {
|
|
1707
2170
|
switch (onboarding.step) {
|
|
1708
2171
|
case "entry":
|
|
1709
|
-
return
|
|
2172
|
+
return 7;
|
|
1710
2173
|
case "host":
|
|
1711
2174
|
return onboarding.hosts.length + 1;
|
|
1712
2175
|
case "api-key-choice":
|
|
1713
2176
|
return onboarding.hasExistingKey ? 2 : 1;
|
|
2177
|
+
case "gemini-wrapper-model-mode":
|
|
2178
|
+
return 2;
|
|
1714
2179
|
case "model":
|
|
1715
2180
|
return onboarding.models.length;
|
|
1716
2181
|
default:
|
|
@@ -1720,7 +2185,7 @@ function getOnboardingOptionCount(onboarding) {
|
|
|
1720
2185
|
function readEntrySelection(value, selectedIndex) {
|
|
1721
2186
|
const normalizedValue = value.trim().toLowerCase();
|
|
1722
2187
|
if (!normalizedValue) {
|
|
1723
|
-
return ["local", "host", "gemini", "openrouter", "nvidia", "codex"][selectedIndex];
|
|
2188
|
+
return ["local", "host", "gemini", "gemini-wrapper", "openrouter", "nvidia", "codex"][selectedIndex];
|
|
1724
2189
|
}
|
|
1725
2190
|
if (normalizedValue === "1" || normalizedValue === "local" || normalizedValue === "this device") {
|
|
1726
2191
|
return "local";
|
|
@@ -1731,17 +2196,33 @@ function readEntrySelection(value, selectedIndex) {
|
|
|
1731
2196
|
if (normalizedValue === "3" || normalizedValue === "gemini" || normalizedValue === "google") {
|
|
1732
2197
|
return "gemini";
|
|
1733
2198
|
}
|
|
1734
|
-
if (normalizedValue === "4" || normalizedValue === "
|
|
2199
|
+
if (normalizedValue === "4" || normalizedValue === "gemini-wrapper" || normalizedValue === "geminiwrapper" || normalizedValue === "google-wrapper") {
|
|
2200
|
+
return "gemini-wrapper";
|
|
2201
|
+
}
|
|
2202
|
+
if (normalizedValue === "5" || normalizedValue === "openrouter" || normalizedValue === "open-router") {
|
|
1735
2203
|
return "openrouter";
|
|
1736
2204
|
}
|
|
1737
|
-
if (normalizedValue === "
|
|
2205
|
+
if (normalizedValue === "6" || normalizedValue === "nvidia" || normalizedValue === "nim") {
|
|
1738
2206
|
return "nvidia";
|
|
1739
2207
|
}
|
|
1740
|
-
if (normalizedValue === "
|
|
2208
|
+
if (normalizedValue === "7" || normalizedValue === "codex") {
|
|
1741
2209
|
return "codex";
|
|
1742
2210
|
}
|
|
1743
2211
|
return null;
|
|
1744
2212
|
}
|
|
2213
|
+
function readBooleanEnv(value, fallback) {
|
|
2214
|
+
if (!value) {
|
|
2215
|
+
return fallback;
|
|
2216
|
+
}
|
|
2217
|
+
const normalizedValue = value.trim().toLowerCase();
|
|
2218
|
+
if (["1", "true", "yes", "on", "enabled"].includes(normalizedValue)) {
|
|
2219
|
+
return true;
|
|
2220
|
+
}
|
|
2221
|
+
if (["0", "false", "no", "off", "disabled"].includes(normalizedValue)) {
|
|
2222
|
+
return false;
|
|
2223
|
+
}
|
|
2224
|
+
return fallback;
|
|
2225
|
+
}
|
|
1745
2226
|
function readIndexedSelection(value, selectedIndex) {
|
|
1746
2227
|
const normalizedValue = value.trim();
|
|
1747
2228
|
if (!normalizedValue) {
|
|
@@ -1774,6 +2255,9 @@ function selectModelFromInput(value, models, selectedIndex, options = {}) {
|
|
|
1774
2255
|
function isPlausibleCloudModelId(value) {
|
|
1775
2256
|
return /^[A-Za-z0-9][A-Za-z0-9._:/+-]*$/.test(value) && value.length >= 3;
|
|
1776
2257
|
}
|
|
2258
|
+
function canUseUnverifiedCloudModel(provider, model) {
|
|
2259
|
+
return provider !== "ollama" && provider !== "gemini-wrapper" && isPlausibleCloudModelId(model);
|
|
2260
|
+
}
|
|
1777
2261
|
function defaultModelForProvider(provider, currentModel) {
|
|
1778
2262
|
if (provider === "nvidia") {
|
|
1779
2263
|
return currentModel.includes("/") && !currentModel.startsWith("openrouter/") ? currentModel : defaultNvidiaModel;
|
|
@@ -1781,6 +2265,9 @@ function defaultModelForProvider(provider, currentModel) {
|
|
|
1781
2265
|
if (provider === "openrouter") {
|
|
1782
2266
|
return currentModel.includes("/") ? currentModel : defaultOpenRouterModel;
|
|
1783
2267
|
}
|
|
2268
|
+
if (provider === "gemini-wrapper") {
|
|
2269
|
+
return currentModel === defaultGeminiWrapperModel || currentModel.startsWith("gemini-3-") ? currentModel : defaultGeminiWrapperModel;
|
|
2270
|
+
}
|
|
1784
2271
|
if (provider === "gemini") {
|
|
1785
2272
|
return currentModel.startsWith("gemini-") ? currentModel : defaultGeminiModel;
|
|
1786
2273
|
}
|
|
@@ -1798,12 +2285,15 @@ function openApiKeyChoice(provider, setOnboarding, setOnboardingIndex) {
|
|
|
1798
2285
|
setOnboardingIndex(0);
|
|
1799
2286
|
}
|
|
1800
2287
|
function needsApiKey(provider) {
|
|
1801
|
-
return provider === "gemini" || provider === "openrouter" || provider === "nvidia";
|
|
2288
|
+
return provider === "gemini" || provider === "gemini-wrapper" || provider === "openrouter" || provider === "nvidia";
|
|
1802
2289
|
}
|
|
1803
2290
|
function hasApiKey(provider) {
|
|
1804
2291
|
if (provider === "gemini") {
|
|
1805
2292
|
return Boolean(readGeminiApiKey());
|
|
1806
2293
|
}
|
|
2294
|
+
if (provider === "gemini-wrapper") {
|
|
2295
|
+
return Boolean(readGeminiWrapperBaseUrl() || readGeminiWrapperCookiesJson());
|
|
2296
|
+
}
|
|
1807
2297
|
if (provider === "openrouter") {
|
|
1808
2298
|
return Boolean(readOpenRouterApiKey());
|
|
1809
2299
|
}
|