@raingor/pi-web-switch 0.6.0 → 0.6.2
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/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/pi-reader.ts +82 -15
- package/src/components/subagents/SubagentsPage.tsx +124 -10
- package/src/lib/translations/en.ts +9 -0
- package/src/lib/translations/ja.ts +9 -0
- package/src/lib/translations/zh-CN.ts +9 -0
- package/src/lib/translations/zh-TW.ts +9 -0
- package/vite.config.ts +16 -0
package/dist/index.html
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
<link rel="manifest" href="./manifest.webmanifest" />
|
|
10
10
|
<meta name="theme-color" content="#05090d" />
|
|
11
11
|
<meta name="description" content="Web UI for pi coding agent — configuration management, session browser, and usage dashboard" />
|
|
12
|
-
<script type="module" crossorigin src="./assets/main-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="./assets/main-
|
|
12
|
+
<script type="module" crossorigin src="./assets/main-KiBX8nrj.js"></script>
|
|
13
|
+
<link rel="stylesheet" crossorigin href="./assets/main-DF0CuClO.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="root"></div>
|
package/package.json
CHANGED
package/server/pi-reader.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, existsSync, statSync, unlinkSync, writeFileSync, mkdirSync, renameSync, chmodSync } from "fs";
|
|
2
2
|
import { homedir, platform } from "os";
|
|
3
3
|
import { join, resolve, dirname, relative, sep } from "path";
|
|
4
|
-
import { spawnSync } from "child_process";
|
|
4
|
+
import { spawnSync, spawn } from "child_process";
|
|
5
5
|
import { DatabaseSync } from "node:sqlite";
|
|
6
6
|
|
|
7
7
|
const PI_DIR = join(homedir(), ".pi", "agent");
|
|
@@ -1868,26 +1868,39 @@ export async function optimizeMemory(): Promise<OptimizeMemoryResult> {
|
|
|
1868
1868
|
? cfg.consolidationTimeoutMs
|
|
1869
1869
|
: 600000;
|
|
1870
1870
|
|
|
1871
|
+
// IMPORTANT: use async spawn, NOT spawnSync. This runs inside the Vite dev
|
|
1872
|
+
// server's Node process; a multi-minute spawnSync would block the event loop,
|
|
1873
|
+
// freeze the HMR WebSocket heartbeat, and make the browser's Vite client
|
|
1874
|
+
// force a full page reload when the socket reconnects. Async spawn keeps the
|
|
1875
|
+
// loop free so /api/pi/memory/status polling and HMR stay responsive.
|
|
1871
1876
|
try {
|
|
1872
|
-
const
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1877
|
+
const status2: { code: number | null; killed: boolean; error?: Error } = await new Promise((resolvePromise) => {
|
|
1878
|
+
const child = spawn(bin, args, { stdio: ["ignore", "ignore", "ignore"] });
|
|
1879
|
+
let settled = false;
|
|
1880
|
+
const finish = (r: { code: number | null; killed: boolean; error?: Error }) => {
|
|
1881
|
+
if (settled) return;
|
|
1882
|
+
settled = true;
|
|
1883
|
+
clearTimeout(killTimer);
|
|
1884
|
+
resolvePromise(r);
|
|
1885
|
+
};
|
|
1886
|
+
const killTimer = setTimeout(() => {
|
|
1887
|
+
child.kill("SIGKILL");
|
|
1888
|
+
finish({ code: null, killed: true });
|
|
1889
|
+
}, timeoutMs);
|
|
1890
|
+
child.on("error", (error) => finish({ code: null, killed: false, error }));
|
|
1891
|
+
child.on("close", (code) => finish({ code, killed: false }));
|
|
1876
1892
|
});
|
|
1893
|
+
|
|
1877
1894
|
const after = memoryFileSizes();
|
|
1878
1895
|
const freedBytes = Object.values(before).reduce((a, b) => a + b, 0)
|
|
1879
1896
|
- Object.values(after).reduce((a, b) => a + b, 0);
|
|
1880
|
-
if (
|
|
1881
|
-
|
|
1882
|
-
return {
|
|
1883
|
-
success: false,
|
|
1884
|
-
before,
|
|
1885
|
-
after,
|
|
1886
|
-
freedBytes,
|
|
1887
|
-
message: killed ? `consolidation timed out after ${Math.round(timeoutMs / 1000)}s` : String(out.error.message),
|
|
1888
|
-
};
|
|
1897
|
+
if (status2.error) {
|
|
1898
|
+
return { success: false, before, after, freedBytes, message: String(status2.error.message) };
|
|
1889
1899
|
}
|
|
1890
|
-
|
|
1900
|
+
if (status2.killed) {
|
|
1901
|
+
return { success: false, before, after, freedBytes, message: `consolidation timed out after ${Math.round(timeoutMs / 1000)}s` };
|
|
1902
|
+
}
|
|
1903
|
+
return { success: status2.code === 0, before, after, freedBytes, message: status2.code === 0 ? undefined : `exit ${status2.code}` };
|
|
1891
1904
|
} catch (e) {
|
|
1892
1905
|
const after = memoryFileSizes();
|
|
1893
1906
|
return { success: false, before, after, freedBytes: 0, message: e instanceof Error ? e.message : String(e) };
|
|
@@ -2741,6 +2754,60 @@ export function readSubagents(): SubagentsData {
|
|
|
2741
2754
|
};
|
|
2742
2755
|
}
|
|
2743
2756
|
|
|
2757
|
+
const AGENT_NAME_RE = /^[\w.-]+\.md$/;
|
|
2758
|
+
|
|
2759
|
+
/**
|
|
2760
|
+
* Patch a single agent's frontmatter field (model / thinking) in place while
|
|
2761
|
+
* preserving the rest of the file byte-for-byte. Rewriting from the parsed
|
|
2762
|
+
* AgentDef would be lossy (body is truncated to 500 chars in listAgents), so we
|
|
2763
|
+
* edit the original file's YAML frontmatter line directly.
|
|
2764
|
+
*
|
|
2765
|
+
* - Updating `model` to a non-empty value replaces or inserts the line.
|
|
2766
|
+
* - An empty string removes the field so the agent falls back to the default.
|
|
2767
|
+
*/
|
|
2768
|
+
export function updateAgentFields(
|
|
2769
|
+
fileName: string,
|
|
2770
|
+
patch: { model?: string; thinking?: string }
|
|
2771
|
+
): boolean {
|
|
2772
|
+
try {
|
|
2773
|
+
// Guard against path traversal: only bare *.md names in the agents dir.
|
|
2774
|
+
if (!AGENT_NAME_RE.test(fileName)) return false;
|
|
2775
|
+
const filePath = join(AGENTS_DIR, fileName);
|
|
2776
|
+
if (!existsSync(filePath)) return false;
|
|
2777
|
+
|
|
2778
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
2779
|
+
if (raw.indexOf("---") !== 0) return false;
|
|
2780
|
+
const second = raw.indexOf("---", 3);
|
|
2781
|
+
if (second === -1) return false;
|
|
2782
|
+
|
|
2783
|
+
const fmBlock = raw.slice(3, second);
|
|
2784
|
+
const rest = raw.slice(second); // starts at closing "---"
|
|
2785
|
+
const lines = fmBlock.replace(/^\n/, "").split("\n");
|
|
2786
|
+
|
|
2787
|
+
const setField = (key: string, value: string | undefined) => {
|
|
2788
|
+
const idx = lines.findIndex((l) => l.trimStart().toLowerCase().startsWith(`${key}:`));
|
|
2789
|
+
if (value === undefined || value === "") {
|
|
2790
|
+
// Remove the field entirely when cleared.
|
|
2791
|
+
if (idx !== -1) lines.splice(idx, 1);
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
const newLine = `${key}: ${value}`;
|
|
2795
|
+
if (idx !== -1) lines[idx] = newLine;
|
|
2796
|
+
else lines.push(newLine);
|
|
2797
|
+
};
|
|
2798
|
+
|
|
2799
|
+
if (patch.model !== undefined) setField("model", patch.model.trim());
|
|
2800
|
+
if (patch.thinking !== undefined) setField("thinking", patch.thinking.trim());
|
|
2801
|
+
|
|
2802
|
+
const newFm = lines.join("\n");
|
|
2803
|
+
const out = `---\n${newFm}\n${rest}`;
|
|
2804
|
+
writeFileSync(filePath, out, "utf-8");
|
|
2805
|
+
return true;
|
|
2806
|
+
} catch {
|
|
2807
|
+
return false;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2744
2811
|
// ─── Built-in Provider Catalog (from the local pi install) ───
|
|
2745
2812
|
// pi ships its full model catalog (same source as pi.dev/models) inside
|
|
2746
2813
|
// @earendil-works/pi-ai as dist/providers/data/*.json. Reading it locally
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import { useTranslation } from "@/lib/i18n";
|
|
3
|
+
import { useConfigStore } from "@/store/config-store";
|
|
3
4
|
import { Badge } from "@/components/ui/Badge";
|
|
4
5
|
import { EmptyState } from "@/components/ui/EmptyState";
|
|
5
6
|
import { formatTokens } from "@/lib/utils";
|
|
@@ -16,6 +17,9 @@ import {
|
|
|
16
17
|
Loader2,
|
|
17
18
|
Search,
|
|
18
19
|
ExternalLink,
|
|
20
|
+
Pencil,
|
|
21
|
+
Check,
|
|
22
|
+
X,
|
|
19
23
|
} from "lucide-react";
|
|
20
24
|
|
|
21
25
|
const API_BASE = "/api/pi";
|
|
@@ -226,7 +230,7 @@ function AgentList({
|
|
|
226
230
|
{/* Right: Agent detail */}
|
|
227
231
|
<div className="min-w-0 flex-1 p-6">
|
|
228
232
|
{selected ? (
|
|
229
|
-
<AgentDetail agent={selected} />
|
|
233
|
+
<AgentDetail agent={selected} onSaved={onRefresh} />
|
|
230
234
|
) : (
|
|
231
235
|
<div className="flex h-40 items-center justify-center text-sm text-gray-500">
|
|
232
236
|
{t("providers_models.select_hint")}
|
|
@@ -237,8 +241,47 @@ function AgentList({
|
|
|
237
241
|
);
|
|
238
242
|
}
|
|
239
243
|
|
|
240
|
-
function AgentDetail({ agent }: { agent: AgentDef }) {
|
|
244
|
+
function AgentDetail({ agent, onSaved }: { agent: AgentDef; onSaved: () => void }) {
|
|
241
245
|
const { t } = useTranslation();
|
|
246
|
+
const { allModels } = useConfigStore();
|
|
247
|
+
const [editing, setEditing] = useState(false);
|
|
248
|
+
const [model, setModel] = useState(agent.model ?? "");
|
|
249
|
+
const [thinking, setThinking] = useState(agent.thinking ?? "");
|
|
250
|
+
const [saving, setSaving] = useState(false);
|
|
251
|
+
const [msg, setMsg] = useState<{ ok: boolean; text: string } | null>(null);
|
|
252
|
+
|
|
253
|
+
// Reset the draft whenever a different agent is selected.
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
setModel(agent.model ?? "");
|
|
256
|
+
setThinking(agent.thinking ?? "");
|
|
257
|
+
setEditing(false);
|
|
258
|
+
setMsg(null);
|
|
259
|
+
}, [agent.fileName]);
|
|
260
|
+
|
|
261
|
+
const handleSave = async () => {
|
|
262
|
+
setSaving(true);
|
|
263
|
+
setMsg(null);
|
|
264
|
+
try {
|
|
265
|
+
const res = await fetch(`${API_BASE}/subagents/update-agent`, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: { "Content-Type": "application/json" },
|
|
268
|
+
body: JSON.stringify({ fileName: agent.fileName, model: model.trim(), thinking: thinking.trim() }),
|
|
269
|
+
});
|
|
270
|
+
const { success } = (await res.json()) as { success: boolean };
|
|
271
|
+
if (success) {
|
|
272
|
+
setMsg({ ok: true, text: t("subagents.saved") });
|
|
273
|
+
setEditing(false);
|
|
274
|
+
onSaved();
|
|
275
|
+
} else {
|
|
276
|
+
setMsg({ ok: false, text: t("subagents.save_failed") });
|
|
277
|
+
}
|
|
278
|
+
} catch {
|
|
279
|
+
setMsg({ ok: false, text: t("subagents.save_failed") });
|
|
280
|
+
} finally {
|
|
281
|
+
setSaving(false);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
242
285
|
return (
|
|
243
286
|
<div className="space-y-4">
|
|
244
287
|
<div className="flex items-center gap-3">
|
|
@@ -246,23 +289,94 @@ function AgentDetail({ agent }: { agent: AgentDef }) {
|
|
|
246
289
|
<Badge variant={agent.package === "custom" ? "default" : "info"}>
|
|
247
290
|
{agent.package}
|
|
248
291
|
</Badge>
|
|
292
|
+
{agent.package === "custom" && !editing && (
|
|
293
|
+
<button
|
|
294
|
+
onClick={() => { setMsg(null); setEditing(true); }}
|
|
295
|
+
className="ml-auto flex items-center gap-1.5 rounded-lg border border-gray-700 px-2.5 py-1 text-xs text-gray-300 hover:bg-gray-800"
|
|
296
|
+
>
|
|
297
|
+
<Pencil className="h-3.5 w-3.5" />
|
|
298
|
+
{t("subagents.edit")}
|
|
299
|
+
</button>
|
|
300
|
+
)}
|
|
249
301
|
</div>
|
|
250
302
|
|
|
251
303
|
<p className="text-sm text-gray-400">{agent.description}</p>
|
|
252
304
|
|
|
253
305
|
<div className="grid grid-cols-2 gap-4 rounded-lg border border-gray-800 bg-gray-900/70 p-4">
|
|
254
|
-
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
306
|
+
{/* Model — editable for custom agents */}
|
|
307
|
+
<div className={editing ? "col-span-2" : ""}>
|
|
308
|
+
<span className="text-xs text-gray-500">{t("subagents.model")}</span>
|
|
309
|
+
{editing ? (
|
|
310
|
+
<>
|
|
311
|
+
<input
|
|
312
|
+
type="text"
|
|
313
|
+
list="agent-model-options"
|
|
314
|
+
value={model}
|
|
315
|
+
onChange={(e) => setModel(e.target.value)}
|
|
316
|
+
placeholder={t("subagents.model_placeholder")}
|
|
317
|
+
className="mt-1 w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-1.5 font-mono text-sm text-gray-100 outline-none focus:ring-1 focus:ring-blue-500"
|
|
318
|
+
/>
|
|
319
|
+
<datalist id="agent-model-options">
|
|
320
|
+
{allModels.map((m) => (
|
|
321
|
+
<option key={`${m.providerId}/${m.id}`} value={`${m.providerId}/${m.id}`}>
|
|
322
|
+
{m.providerName} · {m.name ?? m.id}
|
|
323
|
+
</option>
|
|
324
|
+
))}
|
|
325
|
+
</datalist>
|
|
326
|
+
</>
|
|
327
|
+
) : (
|
|
328
|
+
<p className="mt-0.5 text-sm text-gray-200 font-mono">{agent.model || t("subagents.model_default")}</p>
|
|
329
|
+
)}
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
{/* Thinking — editable for custom agents */}
|
|
333
|
+
<div>
|
|
334
|
+
<span className="text-xs text-gray-500">{t("subagents.thinking")}</span>
|
|
335
|
+
{editing ? (
|
|
336
|
+
<select
|
|
337
|
+
value={thinking}
|
|
338
|
+
onChange={(e) => setThinking(e.target.value)}
|
|
339
|
+
className="mt-1 w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-1.5 text-sm text-gray-100 outline-none focus:ring-1 focus:ring-blue-500"
|
|
340
|
+
>
|
|
341
|
+
<option value="">{t("subagents.thinking_default")}</option>
|
|
342
|
+
{["off", "minimal", "low", "medium", "high", "xhigh"].map((lvl) => (
|
|
343
|
+
<option key={lvl} value={lvl}>{lvl}</option>
|
|
344
|
+
))}
|
|
345
|
+
</select>
|
|
346
|
+
) : (
|
|
347
|
+
<p className="mt-0.5 text-sm text-gray-200">{agent.thinking || t("subagents.thinking_default")}</p>
|
|
348
|
+
)}
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
{editing && (
|
|
352
|
+
<div className="col-span-2 flex items-center gap-3">
|
|
353
|
+
<button
|
|
354
|
+
onClick={handleSave}
|
|
355
|
+
disabled={saving}
|
|
356
|
+
className="flex items-center gap-1.5 rounded-lg bg-blue-600 px-4 py-1.5 text-sm font-medium text-white hover:bg-blue-500 disabled:opacity-50"
|
|
357
|
+
>
|
|
358
|
+
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Check className="h-3.5 w-3.5" />}
|
|
359
|
+
{saving ? t("subagents.saving") : t("subagents.save")}
|
|
360
|
+
</button>
|
|
361
|
+
<button
|
|
362
|
+
onClick={() => { setEditing(false); setModel(agent.model ?? ""); setThinking(agent.thinking ?? ""); }}
|
|
363
|
+
disabled={saving}
|
|
364
|
+
className="flex items-center gap-1.5 rounded-lg border border-gray-700 px-4 py-1.5 text-sm text-gray-300 hover:bg-gray-800"
|
|
365
|
+
>
|
|
366
|
+
<X className="h-3.5 w-3.5" />
|
|
367
|
+
{t("subagents.cancel")}
|
|
368
|
+
</button>
|
|
369
|
+
{msg && (
|
|
370
|
+
<span className={`text-xs ${msg.ok ? "text-emerald-400" : "text-red-400"}`}>{msg.text}</span>
|
|
371
|
+
)}
|
|
258
372
|
</div>
|
|
259
373
|
)}
|
|
260
|
-
{
|
|
261
|
-
<div>
|
|
262
|
-
<span className=
|
|
263
|
-
<p className="mt-0.5 text-sm text-gray-200">{agent.thinking}</p>
|
|
374
|
+
{!editing && msg && (
|
|
375
|
+
<div className="col-span-2">
|
|
376
|
+
<span className={`text-xs ${msg.ok ? "text-emerald-400" : "text-red-400"}`}>{msg.text}</span>
|
|
264
377
|
</div>
|
|
265
378
|
)}
|
|
379
|
+
|
|
266
380
|
{agent.tools && (
|
|
267
381
|
<div className="col-span-2">
|
|
268
382
|
<span className="text-xs text-gray-500">{t("subagents.tools")}</span>
|
|
@@ -140,6 +140,15 @@ const en: Record<string, string> = {
|
|
|
140
140
|
"subagents.no_history": "No run history",
|
|
141
141
|
"subagents.no_history_desc": "Subagent run history will appear here after agents are executed.",
|
|
142
142
|
"subagents.model": "Model",
|
|
143
|
+
"subagents.edit": "Edit",
|
|
144
|
+
"subagents.save": "Save",
|
|
145
|
+
"subagents.saving": "Saving…",
|
|
146
|
+
"subagents.cancel": "Cancel",
|
|
147
|
+
"subagents.saved": "Saved",
|
|
148
|
+
"subagents.save_failed": "Save failed",
|
|
149
|
+
"subagents.model_placeholder": "provider/model-id (blank = default)",
|
|
150
|
+
"subagents.model_default": "(default)",
|
|
151
|
+
"subagents.thinking_default": "(default)",
|
|
143
152
|
"subagents.thinking": "Thinking Level",
|
|
144
153
|
"subagents.tools": "Tools",
|
|
145
154
|
"subagents.system_prompt_mode": "System Prompt Mode",
|
|
@@ -138,6 +138,15 @@ const ja: Record<string, string> = {
|
|
|
138
138
|
"subagents.no_history": "実行履歴がありません",
|
|
139
139
|
"subagents.no_history_desc": "エージェントを実行すると、履歴がここに表示されます。",
|
|
140
140
|
"subagents.model": "モデル",
|
|
141
|
+
"subagents.edit": "編集",
|
|
142
|
+
"subagents.save": "保存",
|
|
143
|
+
"subagents.saving": "保存中…",
|
|
144
|
+
"subagents.cancel": "キャンセル",
|
|
145
|
+
"subagents.saved": "保存しました",
|
|
146
|
+
"subagents.save_failed": "保存に失敗",
|
|
147
|
+
"subagents.model_placeholder": "provider/model-id(空欄で既定)",
|
|
148
|
+
"subagents.model_default": "(既定)",
|
|
149
|
+
"subagents.thinking_default": "(既定)",
|
|
141
150
|
"subagents.thinking": "思考レベル",
|
|
142
151
|
"subagents.tools": "ツール",
|
|
143
152
|
"subagents.system_prompt_mode": "システムプロンプトモード",
|
|
@@ -138,6 +138,15 @@ const zhCN: Record<string, string> = {
|
|
|
138
138
|
"subagents.no_history": "暂无运行记录",
|
|
139
139
|
"subagents.no_history_desc": "运行子代理后,运行记录将显示在这里。",
|
|
140
140
|
"subagents.model": "模型",
|
|
141
|
+
"subagents.edit": "编辑",
|
|
142
|
+
"subagents.save": "保存",
|
|
143
|
+
"subagents.saving": "保存中…",
|
|
144
|
+
"subagents.cancel": "取消",
|
|
145
|
+
"subagents.saved": "已保存",
|
|
146
|
+
"subagents.save_failed": "保存失败",
|
|
147
|
+
"subagents.model_placeholder": "provider/model-id(留空用默认)",
|
|
148
|
+
"subagents.model_default": "(默认)",
|
|
149
|
+
"subagents.thinking_default": "(默认)",
|
|
141
150
|
"subagents.thinking": "思考级别",
|
|
142
151
|
"subagents.tools": "工具",
|
|
143
152
|
"subagents.system_prompt_mode": "系统提示模式",
|
|
@@ -137,6 +137,15 @@ const zhTW: Record<string, string> = {
|
|
|
137
137
|
"subagents.no_history": "暫無執行記錄",
|
|
138
138
|
"subagents.no_history_desc": "執行子代理後,執行記錄將顯示在這裡。",
|
|
139
139
|
"subagents.model": "模型",
|
|
140
|
+
"subagents.edit": "編輯",
|
|
141
|
+
"subagents.save": "儲存",
|
|
142
|
+
"subagents.saving": "儲存中…",
|
|
143
|
+
"subagents.cancel": "取消",
|
|
144
|
+
"subagents.saved": "已儲存",
|
|
145
|
+
"subagents.save_failed": "儲存失敗",
|
|
146
|
+
"subagents.model_placeholder": "provider/model-id(留空用預設)",
|
|
147
|
+
"subagents.model_default": "(預設)",
|
|
148
|
+
"subagents.thinking_default": "(預設)",
|
|
140
149
|
"subagents.thinking": "思考級別",
|
|
141
150
|
"subagents.tools": "工具",
|
|
142
151
|
"subagents.system_prompt_mode": "系統提示模式",
|
package/vite.config.ts
CHANGED
|
@@ -148,6 +148,22 @@ function piApiPlugin(): Plugin {
|
|
|
148
148
|
res.setHeader("Content-Type", "application/json");
|
|
149
149
|
res.end(JSON.stringify(data));
|
|
150
150
|
},
|
|
151
|
+
"POST /api/pi/subagents/update-agent"(req, res) {
|
|
152
|
+
let body = "";
|
|
153
|
+
req.on("data", (chunk: string) => (body += chunk));
|
|
154
|
+
req.on("end", () => {
|
|
155
|
+
try {
|
|
156
|
+
const { fileName, model, thinking } = JSON.parse(body) as { fileName: string; model?: string; thinking?: string };
|
|
157
|
+
const ok = pi.updateAgentFields(fileName, { model, thinking });
|
|
158
|
+
res.setHeader("Content-Type", "application/json");
|
|
159
|
+
res.end(JSON.stringify({ success: ok }));
|
|
160
|
+
} catch {
|
|
161
|
+
res.statusCode = 400;
|
|
162
|
+
res.setHeader("Content-Type", "application/json");
|
|
163
|
+
res.end(JSON.stringify({ success: false, error: "Invalid request body" }));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
},
|
|
151
167
|
"GET /api/pi/memory/config"(_, res) {
|
|
152
168
|
res.setHeader("Content-Type", "application/json");
|
|
153
169
|
res.end(JSON.stringify(pi.readHermesMemoryConfig() ?? {}));
|