@co0ontty/wand 2.12.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -4
- package/dist/build-info.json +3 -3
- package/dist/config.d.ts +4 -3
- package/dist/config.js +22 -7
- package/dist/git-quick-commit.js +56 -3
- package/dist/models.d.ts +4 -0
- package/dist/models.js +35 -1
- package/dist/path-repair.d.ts +1 -1
- package/dist/path-repair.js +11 -6
- package/dist/process-manager.d.ts +1 -1
- package/dist/process-manager.js +23 -5
- package/dist/provider-cli-updater.d.ts +38 -0
- package/dist/provider-cli-updater.js +225 -0
- package/dist/pty-text-utils.js +76 -131
- package/dist/server-session-routes.js +4 -4
- package/dist/server.js +117 -5
- package/dist/session-ai-context.d.ts +2 -2
- package/dist/session-ai-context.js +8 -4
- package/dist/storage.js +6 -1
- package/dist/structured-session-manager.d.ts +6 -0
- package/dist/structured-session-manager.js +296 -5
- package/dist/types.d.ts +4 -2
- package/dist/web-ui/content/scripts.js +37 -37
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -37,6 +37,7 @@ import { renderApp } from "./web-ui/index.js";
|
|
|
37
37
|
import { WsBroadcastManager } from "./ws-broadcast.js";
|
|
38
38
|
import { checkRateLimit, recordFailedLogin, resetRateLimit } from "./middleware/rate-limit.js";
|
|
39
39
|
import { isPathWithinBase, isBlockedFolderPath, normalizeFolderPath } from "./middleware/path-safety.js";
|
|
40
|
+
import { checkProviderCliUpdates, updateProviderClis, verifyProviderCliUpdateResults, } from "./provider-cli-updater.js";
|
|
40
41
|
const execAsync = promisify(exec);
|
|
41
42
|
const SERVER_MODULE_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
42
43
|
const RUNTIME_ROOT_DIR = path.resolve(SERVER_MODULE_DIR, "..");
|
|
@@ -1108,6 +1109,15 @@ export async function startServer(config, configPath) {
|
|
|
1108
1109
|
const processes = new ProcessManager(config, storage, configDir);
|
|
1109
1110
|
const structuredLogger = new SessionLogger(configDir, config.shortcutLogMaxBytes);
|
|
1110
1111
|
const structuredSessions = new StructuredSessionManager(storage, config, structuredLogger);
|
|
1112
|
+
let providerCliUpdateCache = null;
|
|
1113
|
+
let providerCliUpdateInFlight = false;
|
|
1114
|
+
let updateInFlight = false;
|
|
1115
|
+
const refreshProviderCliUpdates = async () => {
|
|
1116
|
+
const items = await checkProviderCliUpdates({ inheritEnv: config.inheritEnv !== false });
|
|
1117
|
+
const result = { items, checkedAt: new Date().toISOString() };
|
|
1118
|
+
providerCliUpdateCache = result;
|
|
1119
|
+
return result;
|
|
1120
|
+
};
|
|
1111
1121
|
const useHttps = config.https === true;
|
|
1112
1122
|
const protocol = useHttps ? "https" : "http";
|
|
1113
1123
|
const requireAuth = buildRequireAuth(useHttps, storage, config);
|
|
@@ -1359,6 +1369,7 @@ export async function startServer(config, configPath) {
|
|
|
1359
1369
|
defaultCwd: config.defaultCwd,
|
|
1360
1370
|
defaultModel: defaultModels.claude,
|
|
1361
1371
|
defaultCodexModel: defaultModels.codex,
|
|
1372
|
+
defaultOpenCodeModel: defaultModels.opencode,
|
|
1362
1373
|
defaultModels,
|
|
1363
1374
|
defaultThinkingEffort: config.defaultThinkingEffort ?? "off",
|
|
1364
1375
|
commandPresets: config.commandPresets,
|
|
@@ -1366,6 +1377,7 @@ export async function startServer(config, configPath) {
|
|
|
1366
1377
|
structuredRunners: [
|
|
1367
1378
|
{ label: "Claude Structured", runner: "claude-cli-print" },
|
|
1368
1379
|
{ label: "Codex Structured", runner: "codex-cli-exec" },
|
|
1380
|
+
{ label: "OpenCode Structured", runner: "opencode-cli-run" },
|
|
1369
1381
|
],
|
|
1370
1382
|
structuredChatPersona,
|
|
1371
1383
|
cardDefaults: config.cardDefaults,
|
|
@@ -1527,6 +1539,7 @@ export async function startServer(config, configPath) {
|
|
|
1527
1539
|
...safeConfig,
|
|
1528
1540
|
defaultModel: defaultModels.claude,
|
|
1529
1541
|
defaultCodexModel: defaultModels.codex,
|
|
1542
|
+
defaultOpenCodeModel: defaultModels.opencode,
|
|
1530
1543
|
defaultModels,
|
|
1531
1544
|
},
|
|
1532
1545
|
hasCert: existsSync(certPaths.keyPath) && existsSync(certPaths.certPath),
|
|
@@ -1543,6 +1556,7 @@ export async function startServer(config, configPath) {
|
|
|
1543
1556
|
web: storage.getConfigValue("autoUpdateWeb") === "true",
|
|
1544
1557
|
apk: storage.getConfigValue("autoUpdateApk") === "true",
|
|
1545
1558
|
dmg: storage.getConfigValue("autoUpdateDmg") === "true",
|
|
1559
|
+
cli: storage.getConfigValue("autoUpdateProviderClis") === "true",
|
|
1546
1560
|
},
|
|
1547
1561
|
androidApk: {
|
|
1548
1562
|
enabled: config.android?.enabled === true,
|
|
@@ -1713,6 +1727,10 @@ export async function startServer(config, configPath) {
|
|
|
1713
1727
|
writePreferenceToStorage(config, storage, "defaultCodexModel", modelDefaults.codex);
|
|
1714
1728
|
touchedPreferenceField = true;
|
|
1715
1729
|
}
|
|
1730
|
+
if (Object.prototype.hasOwnProperty.call(modelDefaults, "opencode")) {
|
|
1731
|
+
writePreferenceToStorage(config, storage, "defaultOpenCodeModel", modelDefaults.opencode);
|
|
1732
|
+
touchedPreferenceField = true;
|
|
1733
|
+
}
|
|
1716
1734
|
}
|
|
1717
1735
|
catch (err) {
|
|
1718
1736
|
res.status(400).json({ error: getErrorMessage(err, "默认模型配置校验失败") });
|
|
@@ -1748,6 +1766,7 @@ export async function startServer(config, configPath) {
|
|
|
1748
1766
|
...safeConfig,
|
|
1749
1767
|
defaultModel: defaultModels.claude,
|
|
1750
1768
|
defaultCodexModel: defaultModels.codex,
|
|
1769
|
+
defaultOpenCodeModel: defaultModels.opencode,
|
|
1751
1770
|
defaultModels,
|
|
1752
1771
|
},
|
|
1753
1772
|
restartRequired: touchedDeployField,
|
|
@@ -1763,10 +1782,13 @@ export async function startServer(config, configPath) {
|
|
|
1763
1782
|
res.json({
|
|
1764
1783
|
models: cached.models,
|
|
1765
1784
|
codexModels: cached.codexModels,
|
|
1785
|
+
opencodeModels: cached.opencodeModels,
|
|
1766
1786
|
claudeVersion: cached.claudeVersion,
|
|
1787
|
+
opencodeVersion: cached.opencodeVersion,
|
|
1767
1788
|
refreshedAt: cached.refreshedAt,
|
|
1768
1789
|
defaultModel: defaultModels.claude,
|
|
1769
1790
|
defaultCodexModel: defaultModels.codex,
|
|
1791
|
+
defaultOpenCodeModel: defaultModels.opencode,
|
|
1770
1792
|
defaultModels,
|
|
1771
1793
|
});
|
|
1772
1794
|
});
|
|
@@ -1777,10 +1799,13 @@ export async function startServer(config, configPath) {
|
|
|
1777
1799
|
res.json({
|
|
1778
1800
|
models: refreshed.models,
|
|
1779
1801
|
codexModels: refreshed.codexModels,
|
|
1802
|
+
opencodeModels: refreshed.opencodeModels,
|
|
1780
1803
|
claudeVersion: refreshed.claudeVersion,
|
|
1804
|
+
opencodeVersion: refreshed.opencodeVersion,
|
|
1781
1805
|
refreshedAt: refreshed.refreshedAt,
|
|
1782
1806
|
defaultModel: defaultModels.claude,
|
|
1783
1807
|
defaultCodexModel: defaultModels.codex,
|
|
1808
|
+
defaultOpenCodeModel: defaultModels.opencode,
|
|
1784
1809
|
defaultModels,
|
|
1785
1810
|
});
|
|
1786
1811
|
}
|
|
@@ -1788,6 +1813,46 @@ export async function startServer(config, configPath) {
|
|
|
1788
1813
|
res.status(500).json({ error: getErrorMessage(error, "刷新模型列表失败。") });
|
|
1789
1814
|
}
|
|
1790
1815
|
});
|
|
1816
|
+
app.get("/api/provider-cli-updates", async (req, res) => {
|
|
1817
|
+
try {
|
|
1818
|
+
const force = req.query.refresh === "1" || !providerCliUpdateCache;
|
|
1819
|
+
const data = force ? await refreshProviderCliUpdates() : providerCliUpdateCache;
|
|
1820
|
+
res.json({
|
|
1821
|
+
...data,
|
|
1822
|
+
updating: providerCliUpdateInFlight,
|
|
1823
|
+
autoUpdate: storage.getConfigValue("autoUpdateProviderClis") === "true",
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
catch (error) {
|
|
1827
|
+
res.status(500).json({ error: getErrorMessage(error, "检查 CLI 更新失败。") });
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
app.post("/api/provider-cli-updates", express.json(), async (req, res) => {
|
|
1831
|
+
if (providerCliUpdateInFlight || updateInFlight) {
|
|
1832
|
+
res.status(409).json({ error: "CLI 更新正在进行中,请稍候。" });
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
const rawIds = Array.isArray(req.body?.ids) ? req.body.ids : [];
|
|
1836
|
+
const ids = rawIds.filter((value) => value === "claude" || value === "codex" || value === "opencode");
|
|
1837
|
+
providerCliUpdateInFlight = true;
|
|
1838
|
+
try {
|
|
1839
|
+
const before = await refreshProviderCliUpdates();
|
|
1840
|
+
const commandResults = await updateProviderClis(before.items, ids.length ? ids : undefined, {
|
|
1841
|
+
inheritEnv: config.inheritEnv !== false,
|
|
1842
|
+
onLog: (line) => process.stdout.write(`[wand] ${line}\n`),
|
|
1843
|
+
});
|
|
1844
|
+
const after = await refreshProviderCliUpdates();
|
|
1845
|
+
const results = verifyProviderCliUpdateResults(commandResults, after.items);
|
|
1846
|
+
refreshModels().catch(() => { });
|
|
1847
|
+
res.json({ ok: results.every((item) => item.ok), results, ...after, autoUpdate: storage.getConfigValue("autoUpdateProviderClis") === "true" });
|
|
1848
|
+
}
|
|
1849
|
+
catch (error) {
|
|
1850
|
+
res.status(500).json({ error: getErrorMessage(error, "更新 CLI 失败。") });
|
|
1851
|
+
}
|
|
1852
|
+
finally {
|
|
1853
|
+
providerCliUpdateInFlight = false;
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1791
1856
|
app.post("/api/settings/upload-cert", async (req, res) => {
|
|
1792
1857
|
const { key, cert } = req.body;
|
|
1793
1858
|
if (!key || !cert) {
|
|
@@ -1837,9 +1902,8 @@ export async function startServer(config, configPath) {
|
|
|
1837
1902
|
res.status(500).json({ error: getErrorMessage(error, "检查更新失败。") });
|
|
1838
1903
|
}
|
|
1839
1904
|
});
|
|
1840
|
-
let updateInFlight = false;
|
|
1841
1905
|
app.post("/api/update", async (_req, res) => {
|
|
1842
|
-
if (updateInFlight) {
|
|
1906
|
+
if (updateInFlight || providerCliUpdateInFlight) {
|
|
1843
1907
|
res.status(409).json({ error: "更新正在进行中,请稍候。" });
|
|
1844
1908
|
return;
|
|
1845
1909
|
}
|
|
@@ -2354,7 +2418,11 @@ export async function startServer(config, configPath) {
|
|
|
2354
2418
|
try {
|
|
2355
2419
|
const origin = parseSessionCreationOrigin(body);
|
|
2356
2420
|
const rawModel = typeof body.model === "string" ? body.model.trim() : "";
|
|
2357
|
-
const provider = body.provider === "codex" || /^codex\b/.test(body.command.trim())
|
|
2421
|
+
const provider = body.provider === "codex" || /^codex\b/.test(body.command.trim())
|
|
2422
|
+
? "codex"
|
|
2423
|
+
: body.provider === "opencode" || /^opencode\b/.test(body.command.trim())
|
|
2424
|
+
? "opencode"
|
|
2425
|
+
: "claude";
|
|
2358
2426
|
const effectiveModel = rawModel || getDefaultModelForProvider(config, provider) || undefined;
|
|
2359
2427
|
const reqCols = typeof body.cols === "number" && Number.isFinite(body.cols) ? body.cols : undefined;
|
|
2360
2428
|
const reqRows = typeof body.rows === "number" && Number.isFinite(body.rows) ? body.rows : undefined;
|
|
@@ -2545,10 +2613,11 @@ export async function startServer(config, configPath) {
|
|
|
2545
2613
|
const web = storage.getConfigValue("autoUpdateWeb") === "true";
|
|
2546
2614
|
const apk = storage.getConfigValue("autoUpdateApk") === "true";
|
|
2547
2615
|
const dmg = storage.getConfigValue("autoUpdateDmg") === "true";
|
|
2548
|
-
|
|
2616
|
+
const cli = storage.getConfigValue("autoUpdateProviderClis") === "true";
|
|
2617
|
+
res.json({ web, apk, dmg, cli });
|
|
2549
2618
|
});
|
|
2550
2619
|
app.post("/api/auto-update", express.json(), (req, res) => {
|
|
2551
|
-
const { web, apk, dmg } = req.body;
|
|
2620
|
+
const { web, apk, dmg, cli } = req.body;
|
|
2552
2621
|
if (typeof web === "boolean") {
|
|
2553
2622
|
storage.setConfigValue("autoUpdateWeb", String(web));
|
|
2554
2623
|
}
|
|
@@ -2558,10 +2627,14 @@ export async function startServer(config, configPath) {
|
|
|
2558
2627
|
if (typeof dmg === "boolean") {
|
|
2559
2628
|
storage.setConfigValue("autoUpdateDmg", String(dmg));
|
|
2560
2629
|
}
|
|
2630
|
+
if (typeof cli === "boolean") {
|
|
2631
|
+
storage.setConfigValue("autoUpdateProviderClis", String(cli));
|
|
2632
|
+
}
|
|
2561
2633
|
res.json({
|
|
2562
2634
|
web: storage.getConfigValue("autoUpdateWeb") === "true",
|
|
2563
2635
|
apk: storage.getConfigValue("autoUpdateApk") === "true",
|
|
2564
2636
|
dmg: storage.getConfigValue("autoUpdateDmg") === "true",
|
|
2637
|
+
cli: storage.getConfigValue("autoUpdateProviderClis") === "true",
|
|
2565
2638
|
});
|
|
2566
2639
|
});
|
|
2567
2640
|
// ── Update channel (stable / beta) ──
|
|
@@ -2660,7 +2733,35 @@ export async function startServer(config, configPath) {
|
|
|
2660
2733
|
});
|
|
2661
2734
|
}
|
|
2662
2735
|
}
|
|
2736
|
+
async function performProviderCliAutoUpdate() {
|
|
2737
|
+
if (storage.getConfigValue("autoUpdateProviderClis") !== "true" || providerCliUpdateInFlight || updateInFlight)
|
|
2738
|
+
return;
|
|
2739
|
+
providerCliUpdateInFlight = true;
|
|
2740
|
+
try {
|
|
2741
|
+
const before = await refreshProviderCliUpdates();
|
|
2742
|
+
const available = before.items.filter((item) => item.updateAvailable && item.updateSupported);
|
|
2743
|
+
if (!available.length)
|
|
2744
|
+
return;
|
|
2745
|
+
const commandResults = await updateProviderClis(before.items, available.map((item) => item.id), {
|
|
2746
|
+
inheritEnv: config.inheritEnv !== false,
|
|
2747
|
+
onLog: (line) => process.stdout.write(`[wand] ${line}\n`),
|
|
2748
|
+
});
|
|
2749
|
+
const after = await refreshProviderCliUpdates();
|
|
2750
|
+
const results = verifyProviderCliUpdateResults(commandResults, after.items);
|
|
2751
|
+
for (const result of results) {
|
|
2752
|
+
process.stdout.write(`[wand] CLI 自动更新 ${result.ok ? "完成" : "失败"}: ${result.message}\n`);
|
|
2753
|
+
}
|
|
2754
|
+
refreshModels().catch(() => { });
|
|
2755
|
+
}
|
|
2756
|
+
catch (error) {
|
|
2757
|
+
process.stdout.write(`[wand] CLI 自动更新失败: ${getErrorMessage(error)}\n`);
|
|
2758
|
+
}
|
|
2759
|
+
finally {
|
|
2760
|
+
providerCliUpdateInFlight = false;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2663
2763
|
let updateCheckTimer = null;
|
|
2764
|
+
let providerCliUpdateTimer = null;
|
|
2664
2765
|
if (updateChecksEnabled) {
|
|
2665
2766
|
// Background update check on startup
|
|
2666
2767
|
performAutoUpdate().catch(() => { });
|
|
@@ -2669,6 +2770,15 @@ export async function startServer(config, configPath) {
|
|
|
2669
2770
|
performAutoUpdate().catch(() => { });
|
|
2670
2771
|
}, 30 * 60 * 1000);
|
|
2671
2772
|
updateCheckTimer.unref();
|
|
2773
|
+
// 与 Wand 自身 npm 更新错峰,避免两个全局 updater 同时改 PATH / bin 链接。
|
|
2774
|
+
providerCliUpdateTimer = setTimeout(() => {
|
|
2775
|
+
performProviderCliAutoUpdate().catch(() => { });
|
|
2776
|
+
providerCliUpdateTimer = setInterval(() => {
|
|
2777
|
+
performProviderCliAutoUpdate().catch(() => { });
|
|
2778
|
+
}, 30 * 60 * 1000);
|
|
2779
|
+
providerCliUpdateTimer.unref();
|
|
2780
|
+
}, 2 * 60 * 1000);
|
|
2781
|
+
providerCliUpdateTimer.unref();
|
|
2672
2782
|
}
|
|
2673
2783
|
const close = () => new Promise((resolve) => {
|
|
2674
2784
|
let done = false;
|
|
@@ -2692,6 +2802,8 @@ export async function startServer(config, configPath) {
|
|
|
2692
2802
|
catch { /* ignore */ }
|
|
2693
2803
|
if (updateCheckTimer)
|
|
2694
2804
|
clearInterval(updateCheckTimer);
|
|
2805
|
+
if (providerCliUpdateTimer)
|
|
2806
|
+
clearInterval(providerCliUpdateTimer);
|
|
2695
2807
|
try {
|
|
2696
2808
|
server.close(() => finish());
|
|
2697
2809
|
}
|
|
@@ -12,6 +12,6 @@ export interface SessionAiContext {
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command">): SessionProvider;
|
|
14
14
|
/** Build the provider-specific settings used by session-adjacent AI actions. */
|
|
15
|
-
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
|
|
15
|
+
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
|
|
16
16
|
/** Build the AI context for quick-commit actions from their global preferences. */
|
|
17
|
-
export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultThinkingEffort" | "inheritEnv" | "commitCli" | "commitModel">): SessionAiContext;
|
|
17
|
+
export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultThinkingEffort" | "inheritEnv" | "commitCli" | "commitModel">): SessionAiContext;
|
|
@@ -5,18 +5,22 @@ import { getDefaultModelForProvider } from "./config.js";
|
|
|
5
5
|
* still identify Codex through structuredState, runner, or command.
|
|
6
6
|
*/
|
|
7
7
|
export function resolveSessionProvider(snapshot) {
|
|
8
|
-
if (snapshot.provider === "claude" || snapshot.provider === "codex") {
|
|
8
|
+
if (snapshot.provider === "claude" || snapshot.provider === "codex" || snapshot.provider === "opencode") {
|
|
9
9
|
return snapshot.provider;
|
|
10
10
|
}
|
|
11
|
-
if (snapshot.structuredState?.provider === "claude" || snapshot.structuredState?.provider === "codex") {
|
|
11
|
+
if (snapshot.structuredState?.provider === "claude" || snapshot.structuredState?.provider === "codex" || snapshot.structuredState?.provider === "opencode") {
|
|
12
12
|
return snapshot.structuredState.provider;
|
|
13
13
|
}
|
|
14
14
|
const runner = snapshot.runner ?? snapshot.structuredState?.runner;
|
|
15
15
|
if (runner === "codex-cli-exec")
|
|
16
16
|
return "codex";
|
|
17
|
+
if (runner === "opencode-cli-run")
|
|
18
|
+
return "opencode";
|
|
17
19
|
if (runner === "claude-cli" || runner === "claude-cli-print" || runner === "claude-sdk")
|
|
18
20
|
return "claude";
|
|
19
|
-
|
|
21
|
+
if (/^codex\b/i.test(snapshot.command.trim()))
|
|
22
|
+
return "codex";
|
|
23
|
+
return /^opencode\b/i.test(snapshot.command.trim()) ? "opencode" : "claude";
|
|
20
24
|
}
|
|
21
25
|
function normalizeModel(value) {
|
|
22
26
|
const model = value?.trim();
|
|
@@ -39,7 +43,7 @@ export function resolveCommitAiContext(snapshot, config) {
|
|
|
39
43
|
const sessionContext = resolveSessionAiContext(snapshot, config);
|
|
40
44
|
return {
|
|
41
45
|
...sessionContext,
|
|
42
|
-
provider: config.commitCli === "codex"
|
|
46
|
+
provider: config.commitCli === "codex" || config.commitCli === "opencode" ? config.commitCli : "claude",
|
|
43
47
|
model: normalizeModel(config.commitModel),
|
|
44
48
|
};
|
|
45
49
|
}
|
package/dist/storage.js
CHANGED
|
@@ -18,7 +18,7 @@ function parseQueuedMessages(raw) {
|
|
|
18
18
|
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : undefined;
|
|
19
19
|
}
|
|
20
20
|
function inferSessionProvider(row) {
|
|
21
|
-
if (row.provider === "claude" || row.provider === "codex") {
|
|
21
|
+
if (row.provider === "claude" || row.provider === "codex" || row.provider === "opencode") {
|
|
22
22
|
return row.provider;
|
|
23
23
|
}
|
|
24
24
|
if (row.runner === "claude-cli" || row.runner === "claude-cli-print") {
|
|
@@ -27,8 +27,13 @@ function inferSessionProvider(row) {
|
|
|
27
27
|
if (row.runner === "codex-cli-exec") {
|
|
28
28
|
return "codex";
|
|
29
29
|
}
|
|
30
|
+
if (row.runner === "opencode-cli-run") {
|
|
31
|
+
return "opencode";
|
|
32
|
+
}
|
|
30
33
|
if (/^codex\b/i.test(row.command.trim()))
|
|
31
34
|
return "codex";
|
|
35
|
+
if (/^opencode\b/i.test(row.command.trim()))
|
|
36
|
+
return "opencode";
|
|
32
37
|
return /^claude\b/i.test(row.command.trim()) ? "claude" : undefined;
|
|
33
38
|
}
|
|
34
39
|
function parseWorktreeInfo(raw) {
|
|
@@ -35,6 +35,8 @@ export declare function thinkingEffortToClaudeCliEffort(effort: SessionSnapshot[
|
|
|
35
35
|
export declare function thinkingEffortToClaudeSlashEffort(effort: SessionSnapshot["thinkingEffort"]): string;
|
|
36
36
|
/** Codex CLI 用:把 thinkingEffort 映射到 model_reasoning_effort 配置。off → 不覆盖 Codex 默认。 */
|
|
37
37
|
export declare function thinkingEffortToCodexReasoningEffort(effort: SessionSnapshot["thinkingEffort"]): string | null;
|
|
38
|
+
/** OpenCode exposes provider-specific reasoning presets through `--variant`. */
|
|
39
|
+
export declare function thinkingEffortToOpenCodeVariant(effort: SessionSnapshot["thinkingEffort"]): string | null;
|
|
38
40
|
/**
|
|
39
41
|
* Preserve both Responses content-part arrays and arbitrary structured tool output.
|
|
40
42
|
* Arrays without a `type` discriminator (for example Codex tool_search results)
|
|
@@ -179,6 +181,10 @@ export declare class StructuredSessionManager {
|
|
|
179
181
|
private incrementApprovalStats;
|
|
180
182
|
private buildCodexArgs;
|
|
181
183
|
private runCodexStreaming;
|
|
184
|
+
private buildOpenCodeArgs;
|
|
185
|
+
private openCodeToolName;
|
|
186
|
+
private applyOpenCodeEvent;
|
|
187
|
+
private runOpenCodeStreaming;
|
|
182
188
|
/**
|
|
183
189
|
* Spawn `claude -p --output-format stream-json` and parse NDJSON lines as
|
|
184
190
|
* they arrive, emitting incremental WebSocket events so the UI can render
|