@jx-grxf/patchpilot 0.4.0 → 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 +69 -14
- package/SECURITY.md +7 -1
- 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/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 +1 -1
- package/dist/core/nvidia.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 +293 -21
- package/dist/core/workspace.js.map +1 -1
- package/dist/tui/App.js +536 -69
- 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/CommandSuggestions.js +8 -3
- package/dist/tui/components/CommandSuggestions.js.map +1 -1
- package/dist/tui/components/Composer.js +1 -1
- package/dist/tui/components/Composer.js.map +1 -1
- package/dist/tui/components/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 +17 -13
- package/dist/tui/components/Sidebar.js.map +1 -1
- package/dist/tui/components/Transcript.js +2 -2
- 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 +1 -1
- 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,22 +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";
|
|
18
21
|
import { ApprovalPanel } from "./components/ApprovalPanel.js";
|
|
19
22
|
import { CommandSuggestions } from "./components/CommandSuggestions.js";
|
|
20
23
|
import { Composer, FooterHints } from "./components/Composer.js";
|
|
24
|
+
import { ExperimentalPanel, experimentalFlagAt, experimentalFlagCount } from "./components/ExperimentalPanel.js";
|
|
21
25
|
import { Header } from "./components/Header.js";
|
|
22
26
|
import { OnboardingPanel } from "./components/OnboardingPanel.js";
|
|
23
27
|
import { Sidebar } from "./components/Sidebar.js";
|
|
@@ -40,6 +44,11 @@ export function App(props) {
|
|
|
40
44
|
const abortControllerRef = useRef(null);
|
|
41
45
|
const sessionStoreRef = useRef(new SessionStore({ workspace: props.workspace }));
|
|
42
46
|
const approvalResolverRef = useRef(null);
|
|
47
|
+
const runtimeStateRef = useRef({
|
|
48
|
+
isRunning: false,
|
|
49
|
+
hasPendingApproval: false,
|
|
50
|
+
lastSigintAt: 0
|
|
51
|
+
});
|
|
43
52
|
const grantedPermissionsRef = useRef({
|
|
44
53
|
allowWrite: props.allowWrite,
|
|
45
54
|
allowShell: props.allowShell
|
|
@@ -55,6 +64,7 @@ export function App(props) {
|
|
|
55
64
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
56
65
|
const [telemetry, setTelemetry] = useState(null);
|
|
57
66
|
const [sessionTelemetry, setSessionTelemetry] = useState(() => emptySessionTelemetry());
|
|
67
|
+
const [resumeContext, setResumeContext] = useState("");
|
|
58
68
|
const [systemStats, setSystemStats] = useState(() => readSystemStats().stats);
|
|
59
69
|
const [gpuStats, setGpuStats] = useState(null);
|
|
60
70
|
const [agentMode, setAgentMode] = useState(() => initialAgentMode({ allowWrite: props.allowWrite, allowShell: props.allowShell }));
|
|
@@ -65,6 +75,13 @@ export function App(props) {
|
|
|
65
75
|
const [modelOptions, setModelOptions] = useState([]);
|
|
66
76
|
const [isLoadingModels, setIsLoadingModels] = useState(false);
|
|
67
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
|
+
});
|
|
68
85
|
const [onboardingIndex, setOnboardingIndex] = useState(0);
|
|
69
86
|
const [onboardingInput, setOnboardingInput] = useState("");
|
|
70
87
|
const [onboardingBusyMessage, setOnboardingBusyMessage] = useState(null);
|
|
@@ -88,7 +105,7 @@ export function App(props) {
|
|
|
88
105
|
const draftTokens = estimateTokens(input);
|
|
89
106
|
const terminalRows = stdout.rows ?? 40;
|
|
90
107
|
const terminalColumns = stdout.columns ?? 120;
|
|
91
|
-
const paletteItems = !isRunning && !onboarding
|
|
108
|
+
const paletteItems = !isRunning && !onboarding && !experimentalOpen
|
|
92
109
|
? buildCommandSuggestionItems({
|
|
93
110
|
input,
|
|
94
111
|
provider: settings.provider,
|
|
@@ -102,9 +119,9 @@ export function App(props) {
|
|
|
102
119
|
const rootHeight = Math.max(24, terminalRows);
|
|
103
120
|
const headerReservedHeight = 5;
|
|
104
121
|
const paletteReservedHeight = !onboarding && paletteItems.length > 0 ? Math.min(8, paletteItems.length) + 4 : 0;
|
|
105
|
-
const composerReservedHeight = onboarding ? 0 : 2;
|
|
106
|
-
const footerReservedHeight = onboarding ? 0 : 1;
|
|
107
|
-
const approvalReservedHeight = !onboarding && (pendingApproval || bypassConfirmation) ? 6 : 0;
|
|
122
|
+
const composerReservedHeight = onboarding || experimentalOpen ? 0 : 2;
|
|
123
|
+
const footerReservedHeight = onboarding || experimentalOpen ? 0 : 1;
|
|
124
|
+
const approvalReservedHeight = !onboarding && !experimentalOpen && (pendingApproval || bypassConfirmation) ? 6 : 0;
|
|
108
125
|
const panelHeight = Math.max(8, rootHeight - headerReservedHeight - composerReservedHeight - paletteReservedHeight - footerReservedHeight - approvalReservedHeight);
|
|
109
126
|
const transcriptWidth = Math.max(42, terminalColumns - 38);
|
|
110
127
|
const scrollStep = Math.max(4, Math.floor(panelHeight * 0.8));
|
|
@@ -124,6 +141,7 @@ export function App(props) {
|
|
|
124
141
|
}
|
|
125
142
|
approvalResolverRef.current(decision);
|
|
126
143
|
approvalResolverRef.current = null;
|
|
144
|
+
setInput("");
|
|
127
145
|
appendLine({
|
|
128
146
|
kind: "approval",
|
|
129
147
|
tone: decision === "deny" ? "warning" : "success",
|
|
@@ -139,6 +157,7 @@ export function App(props) {
|
|
|
139
157
|
const permissions = permissionsForMode(nextMode);
|
|
140
158
|
setAgentMode(nextMode);
|
|
141
159
|
setBypassConfirmation(false);
|
|
160
|
+
grantedPermissionsRef.current = permissions;
|
|
142
161
|
setSettings((currentSettings) => ({
|
|
143
162
|
...currentSettings,
|
|
144
163
|
allowWrite: permissions.allowWrite,
|
|
@@ -166,9 +185,24 @@ export function App(props) {
|
|
|
166
185
|
setWorkState("waiting_approval");
|
|
167
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);
|
|
173
207
|
setStatus("idle");
|
|
174
208
|
setWorkState("idle");
|
|
@@ -291,13 +325,14 @@ export function App(props) {
|
|
|
291
325
|
setTelemetry(null);
|
|
292
326
|
setOnboardingInput("");
|
|
293
327
|
setOnboardingNotice(null);
|
|
294
|
-
setOnboardingBusyMessage(
|
|
328
|
+
setOnboardingBusyMessage(null);
|
|
295
329
|
const nextModel = defaultModelForProvider(provider, options.currentModel ?? settings.model);
|
|
296
330
|
setSettings((currentSettings) => ({
|
|
297
331
|
...currentSettings,
|
|
298
332
|
provider,
|
|
299
333
|
model: nextModel
|
|
300
334
|
}));
|
|
335
|
+
setOnboardingBusyMessage(`Loading ${provider} models...`);
|
|
301
336
|
try {
|
|
302
337
|
const models = await loadAvailableModels(provider, options.ollamaUrl ?? settings.ollamaUrl, setModelOptions, true);
|
|
303
338
|
if (models.length === 0) {
|
|
@@ -307,9 +342,13 @@ export function App(props) {
|
|
|
307
342
|
? "No Ollama models found on that host."
|
|
308
343
|
: provider === "gemini"
|
|
309
344
|
? "No Gemini models listed. Check the API key."
|
|
310
|
-
: provider === "
|
|
311
|
-
? "No
|
|
312
|
-
:
|
|
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.",
|
|
313
352
|
detail: "Use the back key to choose another provider or retry after fixing the provider setup."
|
|
314
353
|
});
|
|
315
354
|
return;
|
|
@@ -355,6 +394,11 @@ export function App(props) {
|
|
|
355
394
|
case "host":
|
|
356
395
|
case "api-key-choice":
|
|
357
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":
|
|
358
402
|
case "openrouter-key":
|
|
359
403
|
case "nvidia-key":
|
|
360
404
|
case "codex-login":
|
|
@@ -380,6 +424,12 @@ export function App(props) {
|
|
|
380
424
|
openApiKeyChoice("gemini", setOnboarding, setOnboardingIndex);
|
|
381
425
|
return;
|
|
382
426
|
}
|
|
427
|
+
if (onboarding.provider === "gemini-wrapper") {
|
|
428
|
+
setOnboarding({
|
|
429
|
+
step: "gemini-wrapper-model-mode"
|
|
430
|
+
});
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
383
433
|
if (onboarding.provider === "nvidia") {
|
|
384
434
|
openApiKeyChoice("nvidia", setOnboarding, setOnboardingIndex);
|
|
385
435
|
return;
|
|
@@ -452,7 +502,7 @@ export function App(props) {
|
|
|
452
502
|
}
|
|
453
503
|
return;
|
|
454
504
|
}
|
|
455
|
-
if (selection === "gemini" || selection === "openrouter" || selection === "nvidia") {
|
|
505
|
+
if (selection === "gemini" || selection === "gemini-wrapper" || selection === "openrouter" || selection === "nvidia") {
|
|
456
506
|
openApiKeyChoice(selection, setOnboarding, setOnboardingIndex);
|
|
457
507
|
return;
|
|
458
508
|
}
|
|
@@ -538,12 +588,20 @@ export function App(props) {
|
|
|
538
588
|
return;
|
|
539
589
|
}
|
|
540
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
|
+
}
|
|
541
599
|
await openModelSelection(onboarding.provider, {
|
|
542
600
|
currentModel: defaultModelForProvider(onboarding.provider, settings.model)
|
|
543
601
|
});
|
|
544
602
|
return;
|
|
545
603
|
}
|
|
546
|
-
setOnboarding({
|
|
604
|
+
setOnboarding(onboarding.provider === "gemini-wrapper" ? { step: "gemini-wrapper-psid" } : {
|
|
547
605
|
step: `${onboarding.provider}-key`
|
|
548
606
|
});
|
|
549
607
|
setOnboardingInput("");
|
|
@@ -574,6 +632,151 @@ export function App(props) {
|
|
|
574
632
|
});
|
|
575
633
|
return;
|
|
576
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
|
+
}
|
|
577
780
|
if (onboarding.step === "openrouter-key") {
|
|
578
781
|
const apiKey = value.trim();
|
|
579
782
|
if (!apiKey) {
|
|
@@ -638,7 +841,7 @@ export function App(props) {
|
|
|
638
841
|
}
|
|
639
842
|
const visibleModels = selectableModels(onboardingInput, onboarding.models);
|
|
640
843
|
const selectedModel = visibleModels[onboardingIndex] ?? selectModelFromInput(value, visibleModels, onboardingIndex, {
|
|
641
|
-
allowManual: onboarding.provider !== "ollama"
|
|
844
|
+
allowManual: onboarding.provider !== "ollama" && onboarding.provider !== "gemini-wrapper"
|
|
642
845
|
});
|
|
643
846
|
if (!selectedModel) {
|
|
644
847
|
setOnboardingNotice({
|
|
@@ -674,7 +877,7 @@ export function App(props) {
|
|
|
674
877
|
}
|
|
675
878
|
closeOnboarding();
|
|
676
879
|
}, [activeHost?.host.url, appendLine, closeOnboarding, connectToHost, loadHostSuggestions, onboarding, onboardingBusyMessage, onboardingIndex, openModelSelection, settings.ollamaUrl]);
|
|
677
|
-
const runTask = useCallback(async (task) => {
|
|
880
|
+
const runTask = useCallback(async (task, overrides = {}) => {
|
|
678
881
|
if (!task.trim() || isRunning) {
|
|
679
882
|
return;
|
|
680
883
|
}
|
|
@@ -694,13 +897,17 @@ export function App(props) {
|
|
|
694
897
|
}
|
|
695
898
|
const abortController = new AbortController();
|
|
696
899
|
abortControllerRef.current = abortController;
|
|
900
|
+
const effectiveMode = overrides.mode ?? agentMode;
|
|
697
901
|
const taskRunner = new AgentRunner({
|
|
698
902
|
...runnableSettings,
|
|
699
|
-
|
|
903
|
+
allowExternalFileAnalysis: experimentalFlags.fileAnalysis,
|
|
904
|
+
memoryEnabled: experimentalFlags.memory,
|
|
905
|
+
mode: effectiveMode,
|
|
700
906
|
signal: abortController.signal,
|
|
701
907
|
sessionStore: sessionStoreRef.current,
|
|
908
|
+
resumeContext,
|
|
702
909
|
approvalHandler: (request) => new Promise((resolve) => {
|
|
703
|
-
if (
|
|
910
|
+
if (effectiveMode === "plan") {
|
|
704
911
|
appendLine({
|
|
705
912
|
kind: "approval",
|
|
706
913
|
tone: "warning",
|
|
@@ -714,7 +921,7 @@ export function App(props) {
|
|
|
714
921
|
resolve("deny");
|
|
715
922
|
return;
|
|
716
923
|
}
|
|
717
|
-
if (
|
|
924
|
+
if (effectiveMode === "bypass" && ((request.permission === "write" && runnableSettings.allowWrite) || (request.permission === "shell" && runnableSettings.allowShell))) {
|
|
718
925
|
resolve("allow_session");
|
|
719
926
|
return;
|
|
720
927
|
}
|
|
@@ -772,7 +979,7 @@ export function App(props) {
|
|
|
772
979
|
setWorkState("idle");
|
|
773
980
|
setIsRunning(false);
|
|
774
981
|
}
|
|
775
|
-
}, [agentMode, appendLine, isRunning, modelOptions, settings]);
|
|
982
|
+
}, [agentMode, appendLine, experimentalFlags, isRunning, modelOptions, resumeContext, settings]);
|
|
776
983
|
const handleSlashCommand = useCallback(async (rawCommand) => {
|
|
777
984
|
const [commandName = "", ...args] = rawCommand.slice(1).trim().split(/\s+/);
|
|
778
985
|
const command = commandName.toLowerCase();
|
|
@@ -821,11 +1028,11 @@ export function App(props) {
|
|
|
821
1028
|
return;
|
|
822
1029
|
case "provider": {
|
|
823
1030
|
const nextProvider = args[0]?.toLowerCase();
|
|
824
|
-
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") {
|
|
825
1032
|
appendLine({
|
|
826
1033
|
tone: "accent",
|
|
827
1034
|
label: "provider",
|
|
828
|
-
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.`
|
|
829
1036
|
});
|
|
830
1037
|
return;
|
|
831
1038
|
}
|
|
@@ -848,7 +1055,7 @@ export function App(props) {
|
|
|
848
1055
|
tone: needsApiKey(nextProvider) && !hasApiKey(nextProvider) ? "warning" : "success",
|
|
849
1056
|
label: "provider",
|
|
850
1057
|
text: needsApiKey(nextProvider) && !hasApiKey(nextProvider)
|
|
851
|
-
? `${nextProvider} needs
|
|
1058
|
+
? `${nextProvider} needs setup. Setup opened.`
|
|
852
1059
|
: `switched to ${nextProvider} using ${nextModel}`
|
|
853
1060
|
});
|
|
854
1061
|
return;
|
|
@@ -868,6 +1075,10 @@ export function App(props) {
|
|
|
868
1075
|
...currentSettings,
|
|
869
1076
|
subagents: subagentsEnabled
|
|
870
1077
|
}));
|
|
1078
|
+
setExperimentalFlags((currentFlags) => ({
|
|
1079
|
+
...currentFlags,
|
|
1080
|
+
subagents: subagentsEnabled
|
|
1081
|
+
}));
|
|
871
1082
|
appendLine({
|
|
872
1083
|
tone: "success",
|
|
873
1084
|
label: "agents",
|
|
@@ -924,38 +1135,30 @@ export function App(props) {
|
|
|
924
1135
|
case "write":
|
|
925
1136
|
case "apply": {
|
|
926
1137
|
const writeEnabled = readToggle(args[0], !settings.allowWrite);
|
|
927
|
-
|
|
928
|
-
requestBypassMode();
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
grantedPermissionsRef.current.allowWrite = writeEnabled;
|
|
932
|
-
applyMode("build", false);
|
|
1138
|
+
setExplicitPermission("write", writeEnabled);
|
|
933
1139
|
appendLine({
|
|
934
1140
|
tone: "success",
|
|
935
1141
|
label: "write",
|
|
936
|
-
text: "workspace writes
|
|
1142
|
+
text: writeEnabled ? "workspace writes are allowed; shell remains separately controlled" : "workspace writes disabled"
|
|
937
1143
|
});
|
|
938
1144
|
return;
|
|
939
1145
|
}
|
|
940
1146
|
case "shell": {
|
|
941
1147
|
const shellEnabled = readToggle(args[0], !settings.allowShell);
|
|
942
|
-
|
|
943
|
-
requestBypassMode();
|
|
944
|
-
return;
|
|
945
|
-
}
|
|
946
|
-
grantedPermissionsRef.current.allowShell = shellEnabled;
|
|
947
|
-
applyMode("build", false);
|
|
1148
|
+
setExplicitPermission("shell", shellEnabled);
|
|
948
1149
|
appendLine({
|
|
949
1150
|
tone: "success",
|
|
950
1151
|
label: "shell",
|
|
951
|
-
text: "shell commands
|
|
1152
|
+
text: shellEnabled ? "shell commands are allowed; writes remain separately controlled" : "shell commands disabled"
|
|
952
1153
|
});
|
|
953
1154
|
return;
|
|
954
1155
|
}
|
|
955
1156
|
case "model": {
|
|
956
1157
|
const requestedModel = normalizeModelAlias(args.join(" ").trim());
|
|
957
1158
|
if (!requestedModel) {
|
|
958
|
-
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
|
+
});
|
|
959
1162
|
if (!models) {
|
|
960
1163
|
return;
|
|
961
1164
|
}
|
|
@@ -968,12 +1171,14 @@ export function App(props) {
|
|
|
968
1171
|
return;
|
|
969
1172
|
}
|
|
970
1173
|
{
|
|
971
|
-
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
|
+
});
|
|
972
1177
|
if (!models) {
|
|
973
1178
|
return;
|
|
974
1179
|
}
|
|
975
1180
|
const nextModel = selectModelFromInput(requestedModel, models, undefined, {
|
|
976
|
-
allowManual: settings.provider !== "ollama"
|
|
1181
|
+
allowManual: settings.provider !== "ollama" && settings.provider !== "gemini-wrapper"
|
|
977
1182
|
});
|
|
978
1183
|
if (!nextModel) {
|
|
979
1184
|
appendLine({
|
|
@@ -991,12 +1196,14 @@ export function App(props) {
|
|
|
991
1196
|
case "models": {
|
|
992
1197
|
const requestedModel = args.join(" ").trim();
|
|
993
1198
|
if (requestedModel) {
|
|
994
|
-
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
|
+
});
|
|
995
1202
|
if (!installedModels) {
|
|
996
1203
|
return;
|
|
997
1204
|
}
|
|
998
1205
|
const nextModel = selectModelFromInput(requestedModel, installedModels, undefined, {
|
|
999
|
-
allowManual: settings.provider !== "ollama"
|
|
1206
|
+
allowManual: settings.provider !== "ollama" && settings.provider !== "gemini-wrapper"
|
|
1000
1207
|
});
|
|
1001
1208
|
if (!nextModel) {
|
|
1002
1209
|
appendLine({
|
|
@@ -1074,11 +1281,26 @@ export function App(props) {
|
|
|
1074
1281
|
const sessionId = args[0] ?? "";
|
|
1075
1282
|
const sessions = await listWorkspaceSessions(settings.workspace);
|
|
1076
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
|
+
}
|
|
1077
1299
|
appendLine({
|
|
1078
1300
|
kind: "status",
|
|
1079
1301
|
tone: selectedSession ? "accent" : "warning",
|
|
1080
1302
|
label: "resume",
|
|
1081
|
-
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.",
|
|
1082
1304
|
detail: selectedSession
|
|
1083
1305
|
? `workspace ${selectedSession.workspace}\nupdated ${selectedSession.updatedAt}\nmodel ${selectedSession.provider ?? "-"} ${selectedSession.model ?? "-"}\nlast task ${selectedSession.lastTask ?? "-"}`
|
|
1084
1306
|
: "Run /sessions after at least one PatchPilot run."
|
|
@@ -1196,29 +1418,151 @@ export function App(props) {
|
|
|
1196
1418
|
return;
|
|
1197
1419
|
}
|
|
1198
1420
|
case "doctor": {
|
|
1421
|
+
const shouldFix = args.some((arg) => arg.toLowerCase() === "fix" || arg.toLowerCase() === "--fix");
|
|
1199
1422
|
appendLine({
|
|
1200
1423
|
tone: "muted",
|
|
1201
1424
|
label: "doctor",
|
|
1202
|
-
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
|
|
1203
1429
|
});
|
|
1204
|
-
const doctorResults = await runDoctor(settings.provider, settings.ollamaUrl, settings.model);
|
|
1205
1430
|
for (const result of doctorResults) {
|
|
1206
1431
|
appendLine({
|
|
1207
1432
|
tone: result.ok ? "success" : "danger",
|
|
1208
1433
|
label: result.name,
|
|
1209
|
-
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."
|
|
1210
1442
|
});
|
|
1211
1443
|
}
|
|
1212
1444
|
return;
|
|
1213
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."
|
|
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
|
+
}));
|
|
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
|
+
});
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1214
1525
|
case "clear":
|
|
1215
1526
|
setLines([]);
|
|
1216
1527
|
setAdvisorNotes([]);
|
|
1217
1528
|
setTelemetry(null);
|
|
1529
|
+
setResumeContext("");
|
|
1218
1530
|
setSessionTelemetry(emptySessionTelemetry());
|
|
1219
1531
|
setTranscriptScrollOffset(0);
|
|
1220
1532
|
setSessionScrollOffset(0);
|
|
1221
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();
|
|
1548
|
+
setLines([]);
|
|
1549
|
+
setAdvisorNotes([]);
|
|
1550
|
+
setTelemetry(null);
|
|
1551
|
+
setSessionTelemetry(emptySessionTelemetry());
|
|
1552
|
+
setPendingApproval(null);
|
|
1553
|
+
approvalResolverRef.current = null;
|
|
1554
|
+
setBypassConfirmation(false);
|
|
1555
|
+
setInput("");
|
|
1556
|
+
setTranscriptScrollOffset(0);
|
|
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
|
+
});
|
|
1565
|
+
return;
|
|
1222
1566
|
case "exit":
|
|
1223
1567
|
case "quit":
|
|
1224
1568
|
case "q":
|
|
@@ -1245,6 +1589,7 @@ export function App(props) {
|
|
|
1245
1589
|
loadHostSuggestions,
|
|
1246
1590
|
loadProviderModels,
|
|
1247
1591
|
modelOptions,
|
|
1592
|
+
isRunning,
|
|
1248
1593
|
resolveApproval,
|
|
1249
1594
|
sessionTelemetry,
|
|
1250
1595
|
settings,
|
|
@@ -1286,6 +1631,10 @@ export function App(props) {
|
|
|
1286
1631
|
useEffect(() => {
|
|
1287
1632
|
void sessionStoreRef.current.create();
|
|
1288
1633
|
}, []);
|
|
1634
|
+
useEffect(() => {
|
|
1635
|
+
runtimeStateRef.current.isRunning = isRunning;
|
|
1636
|
+
runtimeStateRef.current.hasPendingApproval = Boolean(pendingApproval || bypassConfirmation);
|
|
1637
|
+
}, [bypassConfirmation, isRunning, pendingApproval]);
|
|
1289
1638
|
useEffect(() => {
|
|
1290
1639
|
if (!props.initialTask || didRunInitialTask.current || onboarding || process.env.PATCHPILOT_ONBOARDING_COMPLETE !== "1") {
|
|
1291
1640
|
return;
|
|
@@ -1374,6 +1723,44 @@ export function App(props) {
|
|
|
1374
1723
|
}
|
|
1375
1724
|
}, [hostOptions.length, input, isLoadingHosts, isLoadingModels, isRunning, loadHostSuggestions, loadProviderModels, modelOptions.length, onboarding, settings.provider]);
|
|
1376
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
|
+
}
|
|
1377
1764
|
if (bypassConfirmation) {
|
|
1378
1765
|
const normalizedInput = inputValue.toLowerCase();
|
|
1379
1766
|
if (key.tab) {
|
|
@@ -1432,7 +1819,7 @@ export function App(props) {
|
|
|
1432
1819
|
setOnboardingIndex((currentIndex) => (currentIndex + 1) % optionCount);
|
|
1433
1820
|
return;
|
|
1434
1821
|
}
|
|
1435
|
-
if (optionCount > 0 && key.return
|
|
1822
|
+
if (optionCount > 0 && key.return) {
|
|
1436
1823
|
void handleOnboardingSubmit(String(onboardingIndex + 1));
|
|
1437
1824
|
return;
|
|
1438
1825
|
}
|
|
@@ -1457,6 +1844,16 @@ export function App(props) {
|
|
|
1457
1844
|
}
|
|
1458
1845
|
}
|
|
1459
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
|
+
}
|
|
1460
1857
|
if (canUsePanelKeys && key.leftArrow) {
|
|
1461
1858
|
setActiveScrollPane("session");
|
|
1462
1859
|
return;
|
|
@@ -1490,19 +1887,44 @@ export function App(props) {
|
|
|
1490
1887
|
}
|
|
1491
1888
|
});
|
|
1492
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
|
+
};
|
|
1493
1915
|
const unloadAndExit = () => {
|
|
1494
1916
|
void unloadUsedOllamaModels(usedOllamaModelsRef.current).finally(() => {
|
|
1495
1917
|
process.exit(0);
|
|
1496
1918
|
});
|
|
1497
1919
|
};
|
|
1498
|
-
process.
|
|
1499
|
-
process.
|
|
1920
|
+
process.on("SIGINT", gracefulStopOrExit);
|
|
1921
|
+
process.on("SIGTERM", unloadAndExit);
|
|
1500
1922
|
return () => {
|
|
1501
|
-
process.off("SIGINT",
|
|
1923
|
+
process.off("SIGINT", gracefulStopOrExit);
|
|
1502
1924
|
process.off("SIGTERM", unloadAndExit);
|
|
1503
1925
|
void unloadUsedOllamaModels(usedOllamaModelsRef.current);
|
|
1504
1926
|
};
|
|
1505
|
-
}, []);
|
|
1927
|
+
}, [appendLine]);
|
|
1506
1928
|
useEffect(() => {
|
|
1507
1929
|
let previousSnapshot = readSystemStats().snapshot;
|
|
1508
1930
|
const timer = setInterval(() => {
|
|
@@ -1531,10 +1953,10 @@ export function App(props) {
|
|
|
1531
1953
|
clearInterval(timer);
|
|
1532
1954
|
};
|
|
1533
1955
|
}, []);
|
|
1534
|
-
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 + 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 })] })] }))] }));
|
|
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 })] })] }))] }));
|
|
1535
1957
|
}
|
|
1536
1958
|
async function loadAvailableModels(provider, ollamaUrl, setModelOptions, refresh = false) {
|
|
1537
|
-
const cacheKey =
|
|
1959
|
+
const cacheKey = modelCacheKey(provider, ollamaUrl);
|
|
1538
1960
|
const cachedModels = modelCache.get(cacheKey);
|
|
1539
1961
|
if (!refresh && cachedModels && cachedModels.expiresAt > Date.now()) {
|
|
1540
1962
|
setModelOptions(cachedModels.models);
|
|
@@ -1551,9 +1973,24 @@ async function loadAvailableModels(provider, ollamaUrl, setModelOptions, refresh
|
|
|
1551
1973
|
setModelOptions(models);
|
|
1552
1974
|
return models;
|
|
1553
1975
|
}
|
|
1554
|
-
|
|
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 = {}) {
|
|
1555
1992
|
try {
|
|
1556
|
-
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);
|
|
1557
1994
|
}
|
|
1558
1995
|
catch (error) {
|
|
1559
1996
|
appendLine({
|
|
@@ -1577,7 +2014,7 @@ async function switchModel(provider, nextModel, ollamaUrl, currentModel, appendL
|
|
|
1577
2014
|
if (!installedModels) {
|
|
1578
2015
|
return;
|
|
1579
2016
|
}
|
|
1580
|
-
if (!installedModels.includes(nextModel) && !(provider
|
|
2017
|
+
if (!installedModels.includes(nextModel) && !canUseUnverifiedCloudModel(provider, nextModel)) {
|
|
1581
2018
|
appendLine({
|
|
1582
2019
|
tone: "warning",
|
|
1583
2020
|
label: "model",
|
|
@@ -1588,9 +2025,11 @@ async function switchModel(provider, nextModel, ollamaUrl, currentModel, appendL
|
|
|
1588
2025
|
? "No models installed on the selected host."
|
|
1589
2026
|
: provider === "gemini"
|
|
1590
2027
|
? "Check GEMINI_API_KEY in PatchPilot config."
|
|
1591
|
-
: provider === "
|
|
1592
|
-
? "Check
|
|
1593
|
-
:
|
|
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."
|
|
1594
2033
|
});
|
|
1595
2034
|
return;
|
|
1596
2035
|
}
|
|
@@ -1633,7 +2072,7 @@ async function resolveRunnableSettings(settings, modelOptions, appendLine, setMo
|
|
|
1633
2072
|
});
|
|
1634
2073
|
return null;
|
|
1635
2074
|
}
|
|
1636
|
-
if (installedModels.includes(settings.model) || (settings.provider
|
|
2075
|
+
if (installedModels.includes(settings.model) || canUseUnverifiedCloudModel(settings.provider, settings.model)) {
|
|
1637
2076
|
if (!installedModels.includes(settings.model)) {
|
|
1638
2077
|
appendLine({
|
|
1639
2078
|
tone: "warning",
|
|
@@ -1654,9 +2093,11 @@ async function resolveRunnableSettings(settings, modelOptions, appendLine, setMo
|
|
|
1654
2093
|
? "No models installed on the selected host."
|
|
1655
2094
|
: settings.provider === "gemini"
|
|
1656
2095
|
? "No Gemini models listed. Check GEMINI_API_KEY in PatchPilot config."
|
|
1657
|
-
: settings.provider === "
|
|
1658
|
-
? "No
|
|
1659
|
-
:
|
|
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."
|
|
1660
2101
|
});
|
|
1661
2102
|
return null;
|
|
1662
2103
|
}
|
|
@@ -1666,7 +2107,6 @@ function buildCommandSuggestionItems(options) {
|
|
|
1666
2107
|
}
|
|
1667
2108
|
const trimmedInput = options.input.trimStart().toLowerCase();
|
|
1668
2109
|
const items = filterSlashCommands(options.input)
|
|
1669
|
-
.slice(0, 6)
|
|
1670
2110
|
.map((command) => {
|
|
1671
2111
|
const baseCommand = `/${command.name}`;
|
|
1672
2112
|
return {
|
|
@@ -1701,7 +2141,7 @@ function buildCommandSuggestionItems(options) {
|
|
|
1701
2141
|
})));
|
|
1702
2142
|
}
|
|
1703
2143
|
}
|
|
1704
|
-
if (trimmedInput
|
|
2144
|
+
if (trimmedInput.startsWith("/models ") || trimmedInput.startsWith("/model ")) {
|
|
1705
2145
|
const modelQuery = trimmedInput.replace(/^\/models?/, "").trim();
|
|
1706
2146
|
if (options.isLoadingModels) {
|
|
1707
2147
|
items.unshift({
|
|
@@ -1724,16 +2164,18 @@ function buildCommandSuggestionItems(options) {
|
|
|
1724
2164
|
})));
|
|
1725
2165
|
}
|
|
1726
2166
|
}
|
|
1727
|
-
return items
|
|
2167
|
+
return items;
|
|
1728
2168
|
}
|
|
1729
2169
|
function getOnboardingOptionCount(onboarding) {
|
|
1730
2170
|
switch (onboarding.step) {
|
|
1731
2171
|
case "entry":
|
|
1732
|
-
return
|
|
2172
|
+
return 7;
|
|
1733
2173
|
case "host":
|
|
1734
2174
|
return onboarding.hosts.length + 1;
|
|
1735
2175
|
case "api-key-choice":
|
|
1736
2176
|
return onboarding.hasExistingKey ? 2 : 1;
|
|
2177
|
+
case "gemini-wrapper-model-mode":
|
|
2178
|
+
return 2;
|
|
1737
2179
|
case "model":
|
|
1738
2180
|
return onboarding.models.length;
|
|
1739
2181
|
default:
|
|
@@ -1743,7 +2185,7 @@ function getOnboardingOptionCount(onboarding) {
|
|
|
1743
2185
|
function readEntrySelection(value, selectedIndex) {
|
|
1744
2186
|
const normalizedValue = value.trim().toLowerCase();
|
|
1745
2187
|
if (!normalizedValue) {
|
|
1746
|
-
return ["local", "host", "gemini", "openrouter", "nvidia", "codex"][selectedIndex];
|
|
2188
|
+
return ["local", "host", "gemini", "gemini-wrapper", "openrouter", "nvidia", "codex"][selectedIndex];
|
|
1747
2189
|
}
|
|
1748
2190
|
if (normalizedValue === "1" || normalizedValue === "local" || normalizedValue === "this device") {
|
|
1749
2191
|
return "local";
|
|
@@ -1754,17 +2196,33 @@ function readEntrySelection(value, selectedIndex) {
|
|
|
1754
2196
|
if (normalizedValue === "3" || normalizedValue === "gemini" || normalizedValue === "google") {
|
|
1755
2197
|
return "gemini";
|
|
1756
2198
|
}
|
|
1757
|
-
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") {
|
|
1758
2203
|
return "openrouter";
|
|
1759
2204
|
}
|
|
1760
|
-
if (normalizedValue === "
|
|
2205
|
+
if (normalizedValue === "6" || normalizedValue === "nvidia" || normalizedValue === "nim") {
|
|
1761
2206
|
return "nvidia";
|
|
1762
2207
|
}
|
|
1763
|
-
if (normalizedValue === "
|
|
2208
|
+
if (normalizedValue === "7" || normalizedValue === "codex") {
|
|
1764
2209
|
return "codex";
|
|
1765
2210
|
}
|
|
1766
2211
|
return null;
|
|
1767
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
|
+
}
|
|
1768
2226
|
function readIndexedSelection(value, selectedIndex) {
|
|
1769
2227
|
const normalizedValue = value.trim();
|
|
1770
2228
|
if (!normalizedValue) {
|
|
@@ -1797,6 +2255,9 @@ function selectModelFromInput(value, models, selectedIndex, options = {}) {
|
|
|
1797
2255
|
function isPlausibleCloudModelId(value) {
|
|
1798
2256
|
return /^[A-Za-z0-9][A-Za-z0-9._:/+-]*$/.test(value) && value.length >= 3;
|
|
1799
2257
|
}
|
|
2258
|
+
function canUseUnverifiedCloudModel(provider, model) {
|
|
2259
|
+
return provider !== "ollama" && provider !== "gemini-wrapper" && isPlausibleCloudModelId(model);
|
|
2260
|
+
}
|
|
1800
2261
|
function defaultModelForProvider(provider, currentModel) {
|
|
1801
2262
|
if (provider === "nvidia") {
|
|
1802
2263
|
return currentModel.includes("/") && !currentModel.startsWith("openrouter/") ? currentModel : defaultNvidiaModel;
|
|
@@ -1804,6 +2265,9 @@ function defaultModelForProvider(provider, currentModel) {
|
|
|
1804
2265
|
if (provider === "openrouter") {
|
|
1805
2266
|
return currentModel.includes("/") ? currentModel : defaultOpenRouterModel;
|
|
1806
2267
|
}
|
|
2268
|
+
if (provider === "gemini-wrapper") {
|
|
2269
|
+
return currentModel === defaultGeminiWrapperModel || currentModel.startsWith("gemini-3-") ? currentModel : defaultGeminiWrapperModel;
|
|
2270
|
+
}
|
|
1807
2271
|
if (provider === "gemini") {
|
|
1808
2272
|
return currentModel.startsWith("gemini-") ? currentModel : defaultGeminiModel;
|
|
1809
2273
|
}
|
|
@@ -1821,12 +2285,15 @@ function openApiKeyChoice(provider, setOnboarding, setOnboardingIndex) {
|
|
|
1821
2285
|
setOnboardingIndex(0);
|
|
1822
2286
|
}
|
|
1823
2287
|
function needsApiKey(provider) {
|
|
1824
|
-
return provider === "gemini" || provider === "openrouter" || provider === "nvidia";
|
|
2288
|
+
return provider === "gemini" || provider === "gemini-wrapper" || provider === "openrouter" || provider === "nvidia";
|
|
1825
2289
|
}
|
|
1826
2290
|
function hasApiKey(provider) {
|
|
1827
2291
|
if (provider === "gemini") {
|
|
1828
2292
|
return Boolean(readGeminiApiKey());
|
|
1829
2293
|
}
|
|
2294
|
+
if (provider === "gemini-wrapper") {
|
|
2295
|
+
return Boolean(readGeminiWrapperBaseUrl() || readGeminiWrapperCookiesJson());
|
|
2296
|
+
}
|
|
1830
2297
|
if (provider === "openrouter") {
|
|
1831
2298
|
return Boolean(readOpenRouterApiKey());
|
|
1832
2299
|
}
|