oh-my-agent-web 0.4.0 → 0.6.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 +29 -2
- package/dist/server/engine/builtin.js +23 -0
- package/dist/server/engine/jsonrpc-stdio.js +1 -1
- package/dist/server/engine/narrate-guidance.js +22 -0
- package/dist/server/engine/opencode-map.js +499 -0
- package/dist/server/engine/opencode-projects.js +61 -0
- package/dist/server/engine/opencode-sessions.js +195 -0
- package/dist/server/engine/opencode-transport.js +48 -0
- package/dist/server/engine/opencode.js +394 -0
- package/dist/server/engine/pi-plugins.js +310 -0
- package/dist/server/engine/pi-sdk.js +163 -3
- package/dist/server/engine/pi-subagent-tool.js +166 -0
- package/dist/server/engine/pi-subagents.js +372 -0
- package/dist/server/engine/registry.js +1 -1
- package/dist/server/engine/subagent-guidance.js +22 -0
- package/dist/server/engine/wiring.js +59 -0
- package/dist/server/files/files-routes.js +29 -1
- package/dist/server/files/git-info.js +176 -1
- package/dist/server/http/aux-routes.js +16 -0
- package/dist/server/http/compress.js +2 -0
- package/dist/server/index.js +22 -1
- package/dist/server/mcp/mcp-auth.js +184 -0
- package/dist/server/mcp/mcp-command.js +94 -0
- package/dist/server/mcp/mcp-import.js +290 -0
- package/dist/server/mcp/mcp-market-sources.js +192 -0
- package/dist/server/mcp/mcp-market-types.js +5 -0
- package/dist/server/mcp/mcp-market.js +175 -0
- package/dist/server/mcp/mcp-probe.js +134 -0
- package/dist/server/mcp/mcp-routes.js +70 -0
- package/dist/server/mcp/mcp-store.js +286 -0
- package/dist/server/mcp/pi-mcp-config.js +280 -0
- package/dist/server/plugins/plugin-routes.js +21 -0
- package/dist/server/protocol-parse.js +64 -3
- package/dist/server/providers/codex-models.js +96 -0
- package/dist/server/providers/codex-test.js +124 -0
- package/dist/server/providers/import-routes.js +99 -32
- package/dist/server/providers/provider-handlers.js +26 -6
- package/dist/server/providers/provider-store.js +49 -17
- package/dist/server/providers/subscription-routes.js +181 -0
- package/dist/server/session/model-controller.js +9 -1
- package/dist/server/session/runtime.js +28 -2
- package/dist/server/session/session-stats.js +3 -0
- package/dist/server/session/subagents.js +116 -0
- package/dist/server/session/turn-queue.js +101 -18
- package/dist/server/session/turn-send.js +27 -3
- package/dist/server/skills/skill-install.js +186 -0
- package/dist/server/skills/skill-routes.js +107 -1
- package/dist/server/skills/skill-store.js +33 -4
- package/dist/server/skills/skillhub.js +214 -0
- package/dist/server/skills/zip.js +121 -0
- package/dist/server/snapshot/state.js +10 -1
- package/dist/server/subagents/profile-store.js +287 -0
- package/dist/server/util/env.js +6 -0
- package/dist/server/util/upstream-version.js +47 -8
- package/dist/server/ws/handle-message.js +12 -0
- package/package.json +2 -1
- package/web/dist/assets/{abnfDiagram-WEYOIA3C-NLp09iEC.js → abnfDiagram-WEYOIA3C-C0scvgfK.js} +1 -1
- package/web/dist/assets/{arc-DviL9o3c.js → arc-DQWQXZ0D.js} +1 -1
- package/web/dist/assets/{architecture-WOLXFQ4H-fjsM_Sy3.js → architecture-WOLXFQ4H-DRYAlsMA.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZHVYKT53-Cn3B4TE8.js → architectureDiagram-ZHVYKT53-DURI6MT_.js} +1 -1
- package/web/dist/assets/{blockDiagram-RJAE7QA3-CIuvxWvi.js → blockDiagram-RJAE7QA3-4SqX2RCZ.js} +1 -1
- package/web/dist/assets/{c4Diagram-XDPPPE54-DvdKmEpF.js → c4Diagram-XDPPPE54-CyzS6It3.js} +1 -1
- package/web/dist/assets/channel-sSfIOMg7.js +1 -0
- package/web/dist/assets/{chunk-2E7WHVMD-HNNASGCS.js → chunk-2E7WHVMD-B-50G064.js} +1 -1
- package/web/dist/assets/{chunk-4EMNKR2T-Cg-eRwBE.js → chunk-4EMNKR2T-DsXkPvfB.js} +1 -1
- package/web/dist/assets/{chunk-55XCU76K-BweiisZL.js → chunk-55XCU76K-BAQkUXw9.js} +1 -1
- package/web/dist/assets/{chunk-7S6XSTFO-BCzIiP-6.js → chunk-7S6XSTFO-MNTmDz5n.js} +1 -1
- package/web/dist/assets/{chunk-AQDTSKZB-Dl0NRFuU.js → chunk-AQDTSKZB-DxchdDrD.js} +1 -1
- package/web/dist/assets/{chunk-DSNLSHJZ-zhHCjjN9.js → chunk-DSNLSHJZ-CQfLJsEG.js} +1 -1
- package/web/dist/assets/{chunk-HAGFOTED-BDQqU2EH.js → chunk-HAGFOTED-BaVCqyAB.js} +1 -1
- package/web/dist/assets/{chunk-HNE4Q36P-B-qRzMjX.js → chunk-HNE4Q36P-BachKm72.js} +1 -1
- package/web/dist/assets/{chunk-NQOKRVIF-CNcvhMn_.js → chunk-NQOKRVIF-D38mfZCv.js} +1 -1
- package/web/dist/assets/{chunk-UHW7PFBP-Dwr8LUzr.js → chunk-UHW7PFBP-DzvEYnNC.js} +1 -1
- package/web/dist/assets/{chunk-VQ5W62QI-O8sN6jJu.js → chunk-VQ5W62QI-D7Rb-hYZ.js} +1 -1
- package/web/dist/assets/{chunk-WQQL4CDK-B6-59Zg2.js → chunk-WQQL4CDK-Dt2FUoIk.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-DvSbeUlZ.js → chunk-XXDRQBXY-DasKl82Z.js} +1 -1
- package/web/dist/assets/{chunk-YZO67HFG-BXpKYKsw.js → chunk-YZO67HFG-3YefBeUz.js} +1 -1
- package/web/dist/assets/{chunk-ZAUX2LTX-B1Z5VSkz.js → chunk-ZAUX2LTX-CUoEknd1.js} +1 -1
- package/web/dist/assets/{chunk-ZCHMQSOR-h_5mBLIA.js → chunk-ZCHMQSOR-Da3GBE09.js} +1 -1
- package/web/dist/assets/{classDiagram-v2-4MYWR3UA-Dr6JnaaV.js → classDiagram-v2-4MYWR3UA-ByZcqRlk.js} +1 -1
- package/web/dist/assets/{cose-bilkent-JH36ORCC-36ShkR7R.js → cose-bilkent-JH36ORCC-PGAAEchc.js} +1 -1
- package/web/dist/assets/{cynefin-EF2NZ3EQ-Dg6bI7pC.js → cynefin-EF2NZ3EQ-BBkeqM17.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-WF24UDRP-B0YHpBlR.js → cynefinDiagram-WF24UDRP-DOEytVty.js} +1 -1
- package/web/dist/assets/{dagre-NK4GIZVX-Cx3mn7YS.js → dagre-NK4GIZVX-Bei2BCmp.js} +1 -1
- package/web/dist/assets/{diagram-CBK3MWTC-l8aZG_Bm.js → diagram-CBK3MWTC-BdeyWllS.js} +1 -1
- package/web/dist/assets/{diagram-I5XIIV2D-Bd5f14_h.js → diagram-I5XIIV2D-BmNMrWWj.js} +1 -1
- package/web/dist/assets/{diagram-ODIL2UCW-hBsxCYUj.js → diagram-ODIL2UCW-D8Kz7g8s.js} +1 -1
- package/web/dist/assets/{diagram-PFBEJUXE-BgpIBUYl.js → diagram-PFBEJUXE-BNSD58Rk.js} +1 -1
- package/web/dist/assets/{diagram-RJA3I4VV-CDU1gUVD.js → diagram-RJA3I4VV-D-1dWVjH.js} +1 -1
- package/web/dist/assets/{diagram-TYUNFSNR-Iuxh7Tqf.js → diagram-TYUNFSNR-8YHZJhel.js} +1 -1
- package/web/dist/assets/{dist-C86cKTuE.js → dist-BBxVlYHc.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-V4P3TTPT-Cms0HFKh.js → ebnfDiagram-V4P3TTPT-CtgdOqHX.js} +1 -1
- package/web/dist/assets/{elk-QONTBCTQ-B7yEy0PQ.js → elk-QONTBCTQ-DM5bxyWI.js} +1 -1
- package/web/dist/assets/{erDiagram-KYQIGQAA-CL48j4aU.js → erDiagram-KYQIGQAA-BkQvVdNn.js} +1 -1
- package/web/dist/assets/{eventmodeling-K75KTNOO-CFvkaejh.js → eventmodeling-K75KTNOO-DWnHI7Q0.js} +1 -1
- package/web/dist/assets/flowDiagram-IUGLLWBU-DYv3kVGg.js +1 -0
- package/web/dist/assets/{ganttDiagram-R7GPNTJY-vdMr-0ZV.js → ganttDiagram-R7GPNTJY-hF6RKzBy.js} +1 -1
- package/web/dist/assets/{gitGraph-VSP46ZUC-2z85Wuwo.js → gitGraph-VSP46ZUC-Dg0BaeiY.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-PDYL7XHY-BY4GPiqC.js → gitGraphDiagram-PDYL7XHY-CEJB8ISX.js} +1 -1
- package/web/dist/assets/index-BLsWuXKg.js +104 -0
- package/web/dist/assets/index-Bph00xIt.css +1 -0
- package/web/dist/assets/{info-OHQRW6UA-ECzkYTx-.js → info-OHQRW6UA-X0qorfja.js} +1 -1
- package/web/dist/assets/{infoDiagram-MVDRKAX5-DDk5EAeb.js → infoDiagram-MVDRKAX5-Ct4k2rOv.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-HPEHV2GE-9yPH4J94.js → ishikawaDiagram-HPEHV2GE-JsH1yFOe.js} +1 -1
- package/web/dist/assets/{journeyDiagram-BOSHW7O7-BPTIap3l.js → journeyDiagram-BOSHW7O7-B0ommDDF.js} +1 -1
- package/web/dist/assets/{kanban-definition-FNSA4HC5-BbADHrA8.js → kanban-definition-FNSA4HC5-C5IHOEO9.js} +1 -1
- package/web/dist/assets/{line-CHV9_4lL.js → line-XIlcDZrk.js} +1 -1
- package/web/dist/assets/{linear-C-eZsBTv.js → linear-BXwFphKE.js} +1 -1
- package/web/dist/assets/{mermaid-parser.core-BiAhX1l1.js → mermaid-parser.core-9OmqIOQj.js} +3 -3
- package/web/dist/assets/{mermaid.core-BxP_A_zj.js → mermaid.core-B9yDV3Hs.js} +4 -4
- package/web/dist/assets/{mindmap-definition-6OY57UG3-BMWlit95.js → mindmap-definition-6OY57UG3-Bc8uZ5T9.js} +1 -1
- package/web/dist/assets/{packet-JDAUHWVQ-FVApuKOp.js → packet-JDAUHWVQ-9EbXSBeV.js} +1 -1
- package/web/dist/assets/{pegDiagram-ECIUJNBX-WjWJvDHM.js → pegDiagram-ECIUJNBX-nYdgRfAO.js} +1 -1
- package/web/dist/assets/{pie-XZMESJXO-BGCZnvhp.js → pie-XZMESJXO-DNvb9i1G.js} +1 -1
- package/web/dist/assets/{pieDiagram-GNMPY74T-DJpAC_JC.js → pieDiagram-GNMPY74T-BUKhe6YN.js} +1 -1
- package/web/dist/assets/{quadrantDiagram-D57K4MC3-hxx5c7oq.js → quadrantDiagram-D57K4MC3-COMDA2G7.js} +1 -1
- package/web/dist/assets/{radar-ABXABTNO-BU9Shc-O.js → radar-ABXABTNO-DAG-ygGV.js} +1 -1
- package/web/dist/assets/{railroad-I3PHUGI6-pWj8d-G4.js → railroad-I3PHUGI6-CjnKFfHL.js} +1 -1
- package/web/dist/assets/{railroad-abnf-LNEFI6M7-D7HZBTAj.js → railroad-abnf-LNEFI6M7-DOpu4hdH.js} +1 -1
- package/web/dist/assets/{railroad-ebnf-DWYD2UWJ-CKiXJSuM.js → railroad-ebnf-DWYD2UWJ-B7kV2hyB.js} +1 -1
- package/web/dist/assets/{railroad-peg-JR7BVNR7-DB3VJHgR.js → railroad-peg-JR7BVNR7-Cy4mKxpf.js} +1 -1
- package/web/dist/assets/{railroadDiagram-Y72NRBL7-Cev7Y65k.js → railroadDiagram-Y72NRBL7-CJek641i.js} +1 -1
- package/web/dist/assets/{requirementDiagram-EQYSW5RA-CqbwMLD8.js → requirementDiagram-EQYSW5RA-DilR-z_d.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-SY24JHO5-V2cyVi8q.js → sankeyDiagram-SY24JHO5-mnLwPz1t.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-BLEHNHQU-By6NJOPM.js → sequenceDiagram-BLEHNHQU-BdH-2WXg.js} +1 -1
- package/web/dist/assets/{src-Da91fG3g.js → src-DrBGN2Yi.js} +1 -1
- package/web/dist/assets/{stateDiagram-v2-A6IESKWZ-BnPGX5UI.js → stateDiagram-v2-A6IESKWZ-Byl9Wc56.js} +1 -1
- package/web/dist/assets/{swimlanes-B3ENRKFQ-C58BpIpm.js → swimlanes-B3ENRKFQ-D_Mnemnr.js} +1 -1
- package/web/dist/assets/swimlanesDiagram-AYQRV4AK-D6uQfcsx.js +8 -0
- package/web/dist/assets/{timeline-definition-JXYKGLGB-Hgo1DhnS.js → timeline-definition-JXYKGLGB-D2DwVY9P.js} +1 -1
- package/web/dist/assets/{treeView-D4JQ5SDB-DF0dfjqB.js → treeView-D4JQ5SDB-BPseZx68.js} +1 -1
- package/web/dist/assets/{treemap-SAJKECNS-CSSM5FqB.js → treemap-SAJKECNS-DbkR3mjV.js} +1 -1
- package/web/dist/assets/{usecaseDiagram-WPOLBNNX-BkBBD1NY.js → usecaseDiagram-WPOLBNNX-ClThWCPc.js} +1 -1
- package/web/dist/assets/{vennDiagram-Q3Y3IDCW-Bm28keX_.js → vennDiagram-Q3Y3IDCW-B50-oQjz.js} +1 -1
- package/web/dist/assets/{wardley-7MLQ67FV-By7KgEQX.js → wardley-7MLQ67FV-Ct_6HXw3.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-IVAIDKWA-oVtIrnGa.js → wardleyDiagram-IVAIDKWA-B7Z8k44J.js} +1 -1
- package/web/dist/assets/{xychartDiagram-4URNHDU4-aI8FH4g2.js → xychartDiagram-4URNHDU4-CUJ-AhMv.js} +1 -1
- package/web/dist/index.html +15 -2
- package/web/dist/assets/channel-C3AQ6nAs.js +0 -1
- package/web/dist/assets/flowDiagram-IUGLLWBU-6UyKXpqG.js +0 -1
- package/web/dist/assets/index-Bjn56a2i.js +0 -76
- package/web/dist/assets/index-Drp6e5iU.css +0 -1
- package/web/dist/assets/swimlanesDiagram-AYQRV4AK-RaivxSsc.js +0 -8
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件(pi package / 本机扩展)的读取与操作。
|
|
3
|
+
*
|
|
4
|
+
* 「插件」在 pi 里就是 **package**:`settings.json` 的 `packages`(npm / git / 本地目录),
|
|
5
|
+
* 一个包可以同时带扩展、技能、提示词模板、主题;另外还有散装放在
|
|
6
|
+
* `~/.pi/agent/extensions/`(或项目 `.pi/extensions/`)里的单文件扩展。
|
|
7
|
+
*
|
|
8
|
+
* 与参考实现 `/data/git/pi-web`(`app/api/plugins/route.ts`)对齐的几条:
|
|
9
|
+
* - **谁说了算:pi 自己**。哪些资源、开没开,全部取自 `DefaultPackageManager.resolve()`;
|
|
10
|
+
* `onMissing` 一律回 `skip` —— 查询时绝不顺手跑 npm install。
|
|
11
|
+
* - **两个作用域**:全局(`~/.pi/agent/settings.json`)+ 项目(`<workspace>/.pi/settings.json`),
|
|
12
|
+
* 跟会话加载时用的 cwd 是同一个,所以面板显示的就是这个项目里真正加载的东西。
|
|
13
|
+
* OMAW 没有「项目信任」的交互(ADR 未引入),与 SDK 的默认值一致:项目资源按已信任处理。
|
|
14
|
+
* - **停用 ≠ 卸载**:写 `autoload: false`(pi 文档:先清空、再按 patterns 加回),
|
|
15
|
+
* 不删文件、不动 npm 目录、**不丢用户手写的资源过滤**;开回来只删这一个键。
|
|
16
|
+
* `pi config` 那种「四类数组都置空」的停用也认,一并显示成已停用。
|
|
17
|
+
* - **状态四态**:loaded(有资源真的加载了)/ installed(装了但没加载出资源)/
|
|
18
|
+
* missing(配置里有、本地没装)/ disabled(已停用)。
|
|
19
|
+
*/
|
|
20
|
+
import { readFile } from "node:fs/promises";
|
|
21
|
+
import { basename, dirname, extname, join, relative, sep } from "node:path";
|
|
22
|
+
import { DefaultPackageManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
23
|
+
export const RESOURCE_KEYS = ["extensions", "skills", "prompts", "themes"];
|
|
24
|
+
const EMPTY_COUNTS = { extensions: 0, skills: 0, prompts: 0, themes: 0 };
|
|
25
|
+
const KIND_OF = {
|
|
26
|
+
extensions: "extension",
|
|
27
|
+
skills: "skill",
|
|
28
|
+
prompts: "prompt",
|
|
29
|
+
themes: "theme",
|
|
30
|
+
};
|
|
31
|
+
function describe(error) {
|
|
32
|
+
return error instanceof Error ? error.message : String(error);
|
|
33
|
+
}
|
|
34
|
+
function scopeOf(scope) {
|
|
35
|
+
return scope === "project" ? "project" : "global";
|
|
36
|
+
}
|
|
37
|
+
function sourceOf(entry) {
|
|
38
|
+
return typeof entry === "string" ? entry : entry.source;
|
|
39
|
+
}
|
|
40
|
+
/** `pi config` 的停用写法:四类资源数组全部置空 */
|
|
41
|
+
function isEmptiedEntry(entry) {
|
|
42
|
+
if (typeof entry === "string")
|
|
43
|
+
return false;
|
|
44
|
+
return RESOURCE_KEYS.every((key) => Array.isArray(entry[key]) && entry[key].length === 0);
|
|
45
|
+
}
|
|
46
|
+
function isDisabledEntry(entry) {
|
|
47
|
+
if (typeof entry === "string")
|
|
48
|
+
return false;
|
|
49
|
+
return entry.autoload === false || isEmptiedEntry(entry);
|
|
50
|
+
}
|
|
51
|
+
/** 源里钉住的版本:`npm:@scope/pkg@1.2.3` → `1.2.3`;git 的 `#v1` / `@v1` 同理 */
|
|
52
|
+
function configuredVersionOf(source) {
|
|
53
|
+
if (source.startsWith("npm:")) {
|
|
54
|
+
const spec = source.slice(4).trim();
|
|
55
|
+
const lastAt = spec.lastIndexOf("@");
|
|
56
|
+
const nameEnd = spec.startsWith("@") ? spec.indexOf("/", 1) : 0;
|
|
57
|
+
return lastAt > nameEnd ? spec.slice(lastAt + 1) || undefined : undefined;
|
|
58
|
+
}
|
|
59
|
+
if (source.startsWith("git:") || /^[a-z]+:\/\//i.test(source)) {
|
|
60
|
+
const lastAt = Math.max(source.lastIndexOf("@"), source.lastIndexOf("#"));
|
|
61
|
+
const lastSlash = source.lastIndexOf("/");
|
|
62
|
+
return lastAt > lastSlash ? source.slice(lastAt + 1) || undefined : undefined;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
async function readPackageMetadata(root) {
|
|
67
|
+
if (!root)
|
|
68
|
+
return {};
|
|
69
|
+
try {
|
|
70
|
+
const raw = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
71
|
+
if (typeof raw !== "object" || raw === null)
|
|
72
|
+
return {};
|
|
73
|
+
const record = raw;
|
|
74
|
+
return {
|
|
75
|
+
name: typeof record.name === "string" ? record.name : undefined,
|
|
76
|
+
version: typeof record.version === "string" ? record.version : undefined,
|
|
77
|
+
description: typeof record.description === "string" ? record.description : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function resourceName(path, kind) {
|
|
85
|
+
const file = basename(path);
|
|
86
|
+
const ext = extname(file);
|
|
87
|
+
if (kind === "skill" && file.toLowerCase() === "skill.md")
|
|
88
|
+
return basename(dirname(path));
|
|
89
|
+
if (kind === "extension" && /^index\.[cm]?[jt]s$/.test(file))
|
|
90
|
+
return basename(dirname(path));
|
|
91
|
+
return ext ? file.slice(0, -ext.length) : file;
|
|
92
|
+
}
|
|
93
|
+
function resourceRelativePath(resource) {
|
|
94
|
+
const base = resource.metadata.baseDir;
|
|
95
|
+
if (!base)
|
|
96
|
+
return resource.path;
|
|
97
|
+
const rel = relative(base, resource.path);
|
|
98
|
+
return rel !== "" && !rel.startsWith("..") ? rel.split(sep).join("/") : resource.path;
|
|
99
|
+
}
|
|
100
|
+
function toResourceInfo(resource, key) {
|
|
101
|
+
const kind = KIND_OF[key];
|
|
102
|
+
return {
|
|
103
|
+
kind,
|
|
104
|
+
name: resourceName(resource.path, kind),
|
|
105
|
+
path: resource.path,
|
|
106
|
+
relativePath: resourceRelativePath(resource),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** 插件面板的 key:scope + source(同一个包可能在两个作用域各配一份) */
|
|
110
|
+
export function pluginKey(source, scope) {
|
|
111
|
+
return `${scope}\0${source}`;
|
|
112
|
+
}
|
|
113
|
+
export function createPluginStore(options) {
|
|
114
|
+
const agentDir = options.agentDir;
|
|
115
|
+
/** 安装 / 卸载 / 更新会跑 npm,串起来做,别让两个 npm 抢同一个目录 */
|
|
116
|
+
let queue = Promise.resolve();
|
|
117
|
+
const serialize = (task) => {
|
|
118
|
+
const run = queue.then(task, task);
|
|
119
|
+
queue = run.catch(() => undefined);
|
|
120
|
+
return run;
|
|
121
|
+
};
|
|
122
|
+
function open(workspace) {
|
|
123
|
+
// 与 SDK 会话同一个 cwd + 默认信任:面板看到的就是这个项目里真正加载的
|
|
124
|
+
const settings = SettingsManager.create(workspace, agentDir);
|
|
125
|
+
const manager = new DefaultPackageManager({ cwd: workspace, agentDir, settingsManager: settings });
|
|
126
|
+
return { settings, manager };
|
|
127
|
+
}
|
|
128
|
+
async function readView(workspace) {
|
|
129
|
+
let opened;
|
|
130
|
+
try {
|
|
131
|
+
opened = open(workspace);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
packages: [],
|
|
136
|
+
extensions: [],
|
|
137
|
+
totals: { ...EMPTY_COUNTS },
|
|
138
|
+
diagnostics: [],
|
|
139
|
+
projectLoaded: false,
|
|
140
|
+
error: `读不到 pi 的设置:${describe(error)}`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const { settings, manager } = opened;
|
|
144
|
+
const diagnostics = [];
|
|
145
|
+
const loadError = settings.drainErrors()[0];
|
|
146
|
+
if (loadError) {
|
|
147
|
+
diagnostics.push({
|
|
148
|
+
type: "error",
|
|
149
|
+
message: `${loadError.path ?? loadError.scope}:${describe(loadError.error)}`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const countsByPackage = new Map();
|
|
153
|
+
const resourcesByPackage = new Map();
|
|
154
|
+
const totals = { ...EMPTY_COUNTS };
|
|
155
|
+
const extensions = [];
|
|
156
|
+
let resolved;
|
|
157
|
+
try {
|
|
158
|
+
resolved = await manager.resolve(async (source) => {
|
|
159
|
+
diagnostics.push({ type: "warning", source, message: "配置了这个插件包,但本地还没装" });
|
|
160
|
+
return "skip";
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
diagnostics.push({ type: "error", message: `读取已装插件失败:${describe(error)}` });
|
|
165
|
+
resolved = { extensions: [], skills: [], prompts: [], themes: [] };
|
|
166
|
+
}
|
|
167
|
+
for (const key of RESOURCE_KEYS) {
|
|
168
|
+
for (const resource of resolved[key]) {
|
|
169
|
+
if (resource.metadata.origin === "top-level") {
|
|
170
|
+
if (key !== "extensions")
|
|
171
|
+
continue; // 散装技能/模板/主题归各自的 panel
|
|
172
|
+
extensions.push({
|
|
173
|
+
path: resource.path,
|
|
174
|
+
name: resourceName(resource.path, "extension"),
|
|
175
|
+
scope: scopeOf(resource.metadata.scope),
|
|
176
|
+
enabled: resource.enabled,
|
|
177
|
+
});
|
|
178
|
+
if (resource.enabled)
|
|
179
|
+
totals.extensions += 1;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (!resource.enabled)
|
|
183
|
+
continue;
|
|
184
|
+
const scope = scopeOf(resource.metadata.scope);
|
|
185
|
+
const id = pluginKey(resource.metadata.source, scope);
|
|
186
|
+
const counts = countsByPackage.get(id) ?? { ...EMPTY_COUNTS };
|
|
187
|
+
counts[key] += 1;
|
|
188
|
+
totals[key] += 1;
|
|
189
|
+
countsByPackage.set(id, counts);
|
|
190
|
+
const list = resourcesByPackage.get(id) ?? [];
|
|
191
|
+
list.push(toResourceInfo(resource, key));
|
|
192
|
+
resourcesByPackage.set(id, list);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const disabled = new Map();
|
|
196
|
+
const filtered = new Map();
|
|
197
|
+
for (const scope of ["global", "project"]) {
|
|
198
|
+
const entries = scope === "global" ? settings.getGlobalSettings().packages : settings.getProjectSettings().packages;
|
|
199
|
+
for (const entry of entries ?? []) {
|
|
200
|
+
const id = pluginKey(sourceOf(entry), scope);
|
|
201
|
+
disabled.set(id, isDisabledEntry(entry));
|
|
202
|
+
filtered.set(id, typeof entry === "object" && !isEmptiedEntry(entry));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const packages = [];
|
|
206
|
+
for (const configured of manager.listConfiguredPackages()) {
|
|
207
|
+
const scope = scopeOf(configured.scope);
|
|
208
|
+
const id = pluginKey(configured.source, scope);
|
|
209
|
+
const counts = countsByPackage.get(id) ?? { ...EMPTY_COUNTS };
|
|
210
|
+
const metadata = await readPackageMetadata(configured.installedPath);
|
|
211
|
+
const isDisabled = disabled.get(id) ?? false;
|
|
212
|
+
const loaded = counts.extensions + counts.skills + counts.prompts + counts.themes;
|
|
213
|
+
packages.push({
|
|
214
|
+
source: configured.source,
|
|
215
|
+
scope,
|
|
216
|
+
name: metadata.name,
|
|
217
|
+
version: metadata.version,
|
|
218
|
+
description: metadata.description,
|
|
219
|
+
configuredVersion: configuredVersionOf(configured.source),
|
|
220
|
+
disabled: isDisabled,
|
|
221
|
+
filtered: filtered.get(id) ?? false,
|
|
222
|
+
installedPath: configured.installedPath,
|
|
223
|
+
status: isDisabled ? "disabled" : loaded > 0 ? "loaded" : configured.installedPath ? "installed" : "missing",
|
|
224
|
+
counts,
|
|
225
|
+
resources: resourcesByPackage.get(id) ?? [],
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const projectLoaded = settings.isProjectTrusted();
|
|
229
|
+
return { packages, extensions, totals, diagnostics, projectLoaded };
|
|
230
|
+
}
|
|
231
|
+
/** 改一条 package 的启停;只动目标条目,其它条目与管理器里的其它键原样保留 */
|
|
232
|
+
function setDisabled(settings, source, scope, wanted) {
|
|
233
|
+
const current = scope === "global" ? [...(settings.getGlobalSettings().packages ?? [])] : [...(settings.getProjectSettings().packages ?? [])];
|
|
234
|
+
const index = current.findIndex((entry) => sourceOf(entry) === source);
|
|
235
|
+
if (index === -1)
|
|
236
|
+
return false;
|
|
237
|
+
const entry = current[index];
|
|
238
|
+
if (isDisabledEntry(entry) === wanted)
|
|
239
|
+
return false;
|
|
240
|
+
let next;
|
|
241
|
+
if (wanted) {
|
|
242
|
+
// 停用:autoload:false = 「先清空,再按 patterns 加回」。保留用户手写的过滤规则,
|
|
243
|
+
// 否则「停一下再开」会把过滤弄丢(参考实现在这点上会丢,这里刻意不跟)。
|
|
244
|
+
next = { ...(typeof entry === "string" ? { source: entry } : entry), autoload: false };
|
|
245
|
+
}
|
|
246
|
+
else if (typeof entry === "string") {
|
|
247
|
+
next = entry;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
const restored = { ...entry };
|
|
251
|
+
delete restored.autoload;
|
|
252
|
+
// `pi config` 那种「四类数组全空」的停用:开回来就把空数组删掉
|
|
253
|
+
if (isEmptiedEntry(entry))
|
|
254
|
+
for (const key of RESOURCE_KEYS)
|
|
255
|
+
delete restored[key];
|
|
256
|
+
next = RESOURCE_KEYS.some((key) => restored[key] !== undefined) ? restored : restored.source;
|
|
257
|
+
}
|
|
258
|
+
current[index] = next;
|
|
259
|
+
if (scope === "global")
|
|
260
|
+
settings.setPackages(current);
|
|
261
|
+
else
|
|
262
|
+
settings.setProjectPackages(current);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
async function act(input) {
|
|
266
|
+
const scope = input.scope ?? "global";
|
|
267
|
+
const source = input.source?.trim() ?? "";
|
|
268
|
+
let actionError;
|
|
269
|
+
let notice;
|
|
270
|
+
if (input.action !== "update" && source === "") {
|
|
271
|
+
return { ...(await readView(input.workspace)), actionError: "没有指定插件包" };
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
if (input.action === "install" || input.action === "remove" || input.action === "update") {
|
|
275
|
+
await serialize(async () => {
|
|
276
|
+
const { manager } = open(input.workspace);
|
|
277
|
+
if (input.action === "install") {
|
|
278
|
+
await manager.installAndPersist(source, { local: scope === "project" });
|
|
279
|
+
notice = "装好了(对新会话生效)";
|
|
280
|
+
}
|
|
281
|
+
else if (input.action === "remove") {
|
|
282
|
+
await manager.removeAndPersist(source, { local: scope === "project" });
|
|
283
|
+
notice = "已卸载";
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
await manager.update(source === "" ? undefined : source);
|
|
287
|
+
notice = "已更新(对新会话生效)";
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const { settings } = open(input.workspace);
|
|
293
|
+
const changed = setDisabled(settings, source, scope, input.action === "disable");
|
|
294
|
+
await settings.flush();
|
|
295
|
+
const writeError = settings.drainErrors()[0];
|
|
296
|
+
if (writeError)
|
|
297
|
+
throw new Error(describe(writeError.error));
|
|
298
|
+
if (!changed)
|
|
299
|
+
actionError = "这个插件包已经不在配置里了(刷新一下再看)";
|
|
300
|
+
else
|
|
301
|
+
notice = input.action === "disable" ? "已停用(对新会话生效)" : "已启用(对新会话生效)";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
actionError = describe(error);
|
|
306
|
+
}
|
|
307
|
+
return { ...(await readView(input.workspace)), actionError, notice };
|
|
308
|
+
}
|
|
309
|
+
return { view: readView, act };
|
|
310
|
+
}
|
|
@@ -9,15 +9,72 @@
|
|
|
9
9
|
import { DefaultResourceLoader, SessionManager, createAgentSession, getAgentDir, } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { classifyScope } from "../approval/scope.js";
|
|
11
11
|
import { mapSessionEvent } from "./pi-events.js";
|
|
12
|
+
import { createPiSubagents } from "./pi-subagents.js";
|
|
13
|
+
import { makeSubagentToolExtension } from "./pi-subagent-tool.js";
|
|
14
|
+
import { enabledSubagentSpecs, readSubagentSettings } from "../subagents/profile-store.js";
|
|
12
15
|
import { readSessionModel } from "./pi-session-model.js";
|
|
13
16
|
import { entriesToUiMessages } from "./pi-transcript.js";
|
|
14
17
|
function asRecord(value) {
|
|
15
18
|
return typeof value === "object" && value !== null ? value : {};
|
|
16
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* provider 的显示名:只覆盖 SDK 名字太长/不直观的两个(和 pi-web 的选择一致),
|
|
22
|
+
* 其余直接用引擎给的 OAuth 名称(`Anthropic (Claude Pro/Max)`、`xAI (Grok/X subscription)` 等)。
|
|
23
|
+
*/
|
|
24
|
+
const OAUTH_LABELS = {
|
|
25
|
+
"openai-codex": "ChatGPT Plus/Pro",
|
|
26
|
+
"github-copilot": "GitHub Copilot",
|
|
27
|
+
};
|
|
28
|
+
/** SDK 的 AuthPrompt → 端口的 ProviderAuthPrompt */
|
|
29
|
+
function toAuthPrompt(prompt) {
|
|
30
|
+
const record = asRecord(prompt);
|
|
31
|
+
const type = record.type;
|
|
32
|
+
const options = Array.isArray(record.options)
|
|
33
|
+
? record.options.map((option) => {
|
|
34
|
+
const item = asRecord(option);
|
|
35
|
+
return {
|
|
36
|
+
id: String(item.id ?? ""),
|
|
37
|
+
label: String(item.label ?? item.id ?? ""),
|
|
38
|
+
...(typeof item.description === "string" ? { description: item.description } : {}),
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
: undefined;
|
|
42
|
+
return {
|
|
43
|
+
type: type === "select" || type === "secret" || type === "manual_code" || type === "text" ? type : "text",
|
|
44
|
+
message: String(record.message ?? ""),
|
|
45
|
+
...(typeof record.placeholder === "string" ? { placeholder: record.placeholder } : {}),
|
|
46
|
+
...(options ? { options } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** SDK 的 AuthEvent → 端口的 ProviderAuthEvent(未知事件当 info,不静默丢) */
|
|
50
|
+
function toAuthEvent(event) {
|
|
51
|
+
const record = asRecord(event);
|
|
52
|
+
if (record.type === "auth_url" && typeof record.url === "string") {
|
|
53
|
+
return {
|
|
54
|
+
type: "auth_url",
|
|
55
|
+
url: record.url,
|
|
56
|
+
...(typeof record.instructions === "string" ? { instructions: record.instructions } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (record.type === "device_code" && typeof record.userCode === "string") {
|
|
60
|
+
return {
|
|
61
|
+
type: "device_code",
|
|
62
|
+
userCode: record.userCode,
|
|
63
|
+
verificationUri: String(record.verificationUri ?? ""),
|
|
64
|
+
...(typeof record.intervalSeconds === "number" ? { intervalSeconds: record.intervalSeconds } : {}),
|
|
65
|
+
...(typeof record.expiresInSeconds === "number" ? { expiresInSeconds: record.expiresInSeconds } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (record.type === "progress")
|
|
69
|
+
return { type: "progress", message: String(record.message ?? "") };
|
|
70
|
+
return { type: "info", message: String(record.message ?? "") };
|
|
71
|
+
}
|
|
17
72
|
/** 教模型怎么写「下一步建议」(ADR-0022):刻意写得短(每次请求都付 token) */
|
|
18
73
|
import { reloadModelRegistry } from "./model-registry-reload.js";
|
|
74
|
+
import { NARRATE_GUIDANCE } from "./narrate-guidance.js";
|
|
19
75
|
import { SUGGESTION_GUIDANCE } from "./suggestion-guidance.js";
|
|
20
|
-
|
|
76
|
+
import { SUBAGENT_GUIDANCE } from "./subagent-guidance.js";
|
|
77
|
+
export { NARRATE_GUIDANCE, SUGGESTION_GUIDANCE, SUBAGENT_GUIDANCE };
|
|
21
78
|
import { createPiThinking } from "./pi-thinking.js";
|
|
22
79
|
import { piSdkVersion } from "./pi-version.js";
|
|
23
80
|
import { renameSessionIn } from "./pi-rename.js";
|
|
@@ -26,6 +83,17 @@ export function createPiSdkEngine(options = {}) {
|
|
|
26
83
|
const thinking = createPiThinking(sessions);
|
|
27
84
|
let approvalHandler = null;
|
|
28
85
|
const notice = (text, level = "warn") => options.onNotice?.(text, level);
|
|
86
|
+
// 子代理运行时(ADR:多开 + 面板管理):会话文件放在父会话旁边,工具白名单来自模板
|
|
87
|
+
const resolvedAgentDir = options.agentDir ?? getAgentDir();
|
|
88
|
+
const subagentSettings = readSubagentSettings(resolvedAgentDir);
|
|
89
|
+
const subagents = createPiSubagents({
|
|
90
|
+
agentDir: resolvedAgentDir,
|
|
91
|
+
sessionDir: options.sessionDir,
|
|
92
|
+
concurrency: subagentSettings.concurrency,
|
|
93
|
+
onNotice: (text, level) => notice(text, level ?? "warn"),
|
|
94
|
+
// 子代理沿用父会话的模型运行时 / 当前模型(不然扩展提供的 provider 在子会话里不存在)
|
|
95
|
+
getParentSession: (conversationId) => sessions.get(conversationId)?.session ?? null,
|
|
96
|
+
});
|
|
29
97
|
/** 审批用的 inline 扩展:拦在工具真正执行之前(SDK 的公开拦截点) */
|
|
30
98
|
function makeApprovalExtension(workspace) {
|
|
31
99
|
return {
|
|
@@ -61,12 +129,34 @@ export function createPiSdkEngine(options = {}) {
|
|
|
61
129
|
/** 建 resourceLoader 并拉起一个 AgentSession(createSession 与 openSession 共用) */
|
|
62
130
|
async function startSession(sessionId, workspace, sessionManager, sessionPath = null) {
|
|
63
131
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
132
|
+
// 子会话要按父会话归档:会话文件名在 createAgentSession 之后才确定,所以用一个懒引用
|
|
133
|
+
const parentSessionPath = () => sessionManager.getSessionFile() ?? sessionPath;
|
|
134
|
+
const extensionFactories = [makeApprovalExtension(workspace)];
|
|
135
|
+
// 子代理开着、且有可用模板时,才把「什么时候派活」这段拼进系统提示
|
|
136
|
+
const subagentReady = sessionId !== PREWARM_ID
|
|
137
|
+
&& readSubagentSettings(agentDir).enabled
|
|
138
|
+
&& enabledSubagentSpecs(agentDir).length > 0;
|
|
139
|
+
// 预热会话不暴露 Agent 工具:它不下发提示词,给它一个委派入口只会误伤
|
|
140
|
+
if (sessionId !== PREWARM_ID) {
|
|
141
|
+
extensionFactories.push(makeSubagentToolExtension({
|
|
142
|
+
port: subagents,
|
|
143
|
+
agentDir,
|
|
144
|
+
conversationId: sessionId,
|
|
145
|
+
workspace,
|
|
146
|
+
parentSessionPath,
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
64
149
|
const resourceLoader = new DefaultResourceLoader({
|
|
65
150
|
cwd: workspace,
|
|
66
151
|
agentDir,
|
|
67
|
-
extensionFactories
|
|
152
|
+
extensionFactories,
|
|
68
153
|
// 教模型「下一步建议」的写法(ADR-0022):纯文本标记,不需要调用任何工具
|
|
69
|
-
|
|
154
|
+
// 「动手前先说话」:过程默认折叠,不写正文用户就不知道它在干什么
|
|
155
|
+
appendSystemPrompt: [
|
|
156
|
+
SUGGESTION_GUIDANCE,
|
|
157
|
+
NARRATE_GUIDANCE,
|
|
158
|
+
...(subagentReady ? [SUBAGENT_GUIDANCE] : []),
|
|
159
|
+
],
|
|
70
160
|
});
|
|
71
161
|
await resourceLoader.reload();
|
|
72
162
|
const { session } = await createAgentSession({
|
|
@@ -104,6 +194,49 @@ export function createPiSdkEngine(options = {}) {
|
|
|
104
194
|
options.onNotice?.(`模型表预热失败:${error instanceof Error ? error.message : String(error)}`, "warn");
|
|
105
195
|
}
|
|
106
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* 订阅服务要一个 ModelRuntime:优先用预热会话的那个(内存会话,不落盘),
|
|
199
|
+
* 没有就建一个 —— 不带任何会话也让用户先登录。
|
|
200
|
+
*/
|
|
201
|
+
async function authRuntime() {
|
|
202
|
+
const runtimeOf = (handle) => {
|
|
203
|
+
if (!handle)
|
|
204
|
+
return null;
|
|
205
|
+
return handle.session.modelRuntime ?? null;
|
|
206
|
+
};
|
|
207
|
+
const existing = runtimeOf(sessions.get(PREWARM_ID) ?? [...sessions.values()].at(-1));
|
|
208
|
+
if (existing)
|
|
209
|
+
return existing;
|
|
210
|
+
await warmUp();
|
|
211
|
+
return runtimeOf(sessions.get(PREWARM_ID));
|
|
212
|
+
}
|
|
213
|
+
/** 接口给界面的一行:能 OAuth 登录的 provider + 当前连上了没 */
|
|
214
|
+
async function listProviderAuth() {
|
|
215
|
+
const runtime = await authRuntime();
|
|
216
|
+
if (!runtime)
|
|
217
|
+
return [];
|
|
218
|
+
const credentials = new Map();
|
|
219
|
+
try {
|
|
220
|
+
for (const credential of await runtime.listCredentials()) {
|
|
221
|
+
credentials.set(credential.providerId, credential.type);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// auth.json 坏了不该让整个列表空掉:退回「都算未连接」
|
|
226
|
+
}
|
|
227
|
+
return runtime
|
|
228
|
+
.getProviders()
|
|
229
|
+
.filter((provider) => Boolean(provider.auth.oauth))
|
|
230
|
+
.map((provider) => ({
|
|
231
|
+
id: provider.id,
|
|
232
|
+
name: OAUTH_LABELS[provider.id] ?? provider.auth.oauth?.name ?? provider.name,
|
|
233
|
+
subscription: provider.auth.oauth?.isSubscription === true,
|
|
234
|
+
connected: credentials.get(provider.id) === "oauth",
|
|
235
|
+
supportsApiKey: Boolean(provider.auth.apiKey),
|
|
236
|
+
modelCount: runtime.getModels(provider.id).length,
|
|
237
|
+
}))
|
|
238
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
239
|
+
}
|
|
107
240
|
return {
|
|
108
241
|
/** 预热:pi 的 modelRuntime 挂在会话上,没会话就列不出模型(内存会话,不落盘) */
|
|
109
242
|
async prewarm() {
|
|
@@ -118,6 +251,33 @@ export function createPiSdkEngine(options = {}) {
|
|
|
118
251
|
},
|
|
119
252
|
version: piSdkVersion,
|
|
120
253
|
reloadModels: () => reloadModelRegistry(sessions, PREWARM_ID, warmUp, options.agentDir ?? getAgentDir()),
|
|
254
|
+
/** 子代理:起子会话 / 停掉它 / 状态订阅(宿主投影进快照) */
|
|
255
|
+
subagents,
|
|
256
|
+
providerAuth: {
|
|
257
|
+
list: () => listProviderAuth(),
|
|
258
|
+
async login(providerId, interaction) {
|
|
259
|
+
const runtime = await authRuntime();
|
|
260
|
+
if (!runtime)
|
|
261
|
+
throw new Error("引擎还没拿到模型运行时(先开一次会话,或稍后重试)");
|
|
262
|
+
if (!runtime.getProviders().find((provider) => provider.id === providerId)?.auth.oauth) {
|
|
263
|
+
throw new Error(`${providerId} 不支持订阅登录`);
|
|
264
|
+
}
|
|
265
|
+
await runtime.login(providerId, "oauth", {
|
|
266
|
+
prompt: (prompt) => interaction.prompt(toAuthPrompt(prompt)),
|
|
267
|
+
notify: (event) => interaction.notify(toAuthEvent(event)),
|
|
268
|
+
...(interaction.signal ? { signal: interaction.signal } : {}),
|
|
269
|
+
});
|
|
270
|
+
// 凭据已写进引擎的 auth.json:重载模型表,不然新 provider 的模型要等下次重启才出现
|
|
271
|
+
await reloadModelRegistry(sessions, PREWARM_ID, warmUp, options.agentDir ?? getAgentDir());
|
|
272
|
+
},
|
|
273
|
+
async logout(providerId) {
|
|
274
|
+
const runtime = await authRuntime();
|
|
275
|
+
if (!runtime)
|
|
276
|
+
throw new Error("引擎还没拿到模型运行时");
|
|
277
|
+
await runtime.logout(providerId);
|
|
278
|
+
await reloadModelRegistry(sessions, PREWARM_ID, warmUp, options.agentDir ?? getAgentDir());
|
|
279
|
+
},
|
|
280
|
+
},
|
|
121
281
|
async createSession(sessionOptions) {
|
|
122
282
|
// ADR-0021 §2:新建会话就落在 **pi 的会话存储**里(不再用 inMemory + 自建 jsonl),
|
|
123
283
|
// 用我们的 sessionId 当 SDK 的会话 id → 两边 id 空间完全一致。
|