@tea-agent/loop-agent 0.39.0-next.11 → 0.39.0-next.13
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/CHANGELOG.md +15 -0
- package/dist/build-stamp.json +2 -2
- package/dist/shared/operator/capabilities.js +65 -8
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/pi-runtime.js +155 -78
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +19 -8
- package/dist/worker/console/chat/semantic-activity.js +17 -5
- package/dist/worker/console/chat/turn-process.js +10 -10
- package/dist/worker/console/operator-actions.js +74 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B1E-cK2K.js → abnfDiagram-N423BO3Z-Hfsot6rP.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BGzjO9rd.js → arc-YGwEASin.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DLdhmjAc.js → architectureDiagram-T3A2C74G-CmOM0F92.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-MYV8D865.js → blockDiagram-VBNYF7ZC-DDLuaHC5.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BzDorEwd.js → c4Diagram-5PPSVZJV-B0Um43W4.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dp_2Aqf1.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C1X8DD9m.js → chunk-2GRJ4B5K-DvBN4JZc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-_b3zpUSR.js → chunk-2Q5K7J3B-B1UVgW4E.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-B1nq6qNo.js → chunk-5RXB4S5H-Cf0GgXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BHy2jQUr.js → chunk-5VM5RSS4-DbNA24sj.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BDoLBPGY.js → chunk-6Q2QTUOP-Bi_0Be06.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-SDhf5BHa.js → chunk-GF5L2VYU-C8pPGvRd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-B2X9WLQ5.js → chunk-JWPE2WC7-BitQY8Z-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Y1pEsL6r.js → chunk-KBJHAD2P-U67GCiEo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BhlwtGnw.js → chunk-RYQCIY6F-IoJwXPBA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-lFslfIU9.js → chunk-XXDRQBXY-YDZ_UHgE.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL--CODy37j.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE--CODy37j.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ms_QSyzO.js → cose-bilkent-JH36ORCC-CUZgsiRC.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DVBoJPNo.js → cynefin-VYW2F7L2-COmCh4pT.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-ClesFZ8l.js → cynefinDiagram-MW4NZA55-Ta6FSm68.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CwcFFdXS.js → dagre-VZM6K2ZE-UdcR-VW9.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CK6fAQka.js → diagram-7IWD3JNH-DybcO3Et.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CxBy8J_n.js → diagram-B4RE2ZJO-wErDFWLw.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Cn6E66dO.js → diagram-LBJQPF4R-DkVHZWqz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Cep_FINW.js → diagram-Q27KOJAE-DdqWs7QN.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-gzv9ZY8Y.js → diagram-UB23O5K3-C9dMVmDo.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CZVlOlaj.js → ebnfDiagram-BXEA7PRR-B2T2WcBK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-EGJvKAH5.js → erDiagram-JOGREHBK-Bt9rjDFq.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DhfPgO79.js → flowDiagram-UKHOOZJN-U8ic_tvi.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BzJfLMVN.js → ganttDiagram-PKOTCBZU-BfQJdQoO.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-C5uHg3cm.js → gitGraphDiagram-DS77QQ5N-CpMftH4O.js} +1 -1
- package/dist/worker/console/static/assets/index-BA5-N5ER.css +1 -0
- package/dist/worker/console/static/assets/index-DKPXBrdc.js +325 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BFEs1rSc.js → infoDiagram-6WML65LV-DzqWXNO_.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DOY3aNcc.js → ishikawaDiagram-WSZJBQD7-CJmGQ5Yp.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CvEVBETJ.js → journeyDiagram-NVQOT4AX-ChPtKFLz.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CvRuqo46.js → kanban-definition-27J2QSJJ-PRe7wB5u.js} +1 -1
- package/dist/worker/console/static/assets/{linear-B8zO1HYx.js → linear-DpKX6QNp.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-FLK4IOPu.js → mermaid.core-Dnl76b7Y.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CwQ-RWrj.js → mindmap-definition-FAOFIHXS-S9PCXc3J.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-H_S6wWna.js → pegDiagram-VL7TDLO6-Du8e-FSX.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CZUOcDQQ.js → pieDiagram-7S7Q4E2Y-CY6OmHTc.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CeaPaSv9.js → quadrantDiagram-CIZ2JOQS-C65GPhpj.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Cpcs9ua-.js → railroadDiagram-AXF67PYL-U6IUiTCL.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D0_Aeyk6.js → requirementDiagram-LRYGKXZP-D-I_9Qhi.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-73kWjUq_.js → sankeyDiagram-W5VNT64P-B57Fhed0.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-41K29Ot4.js → sequenceDiagram-SI44F4Z6-CJOtiw21.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-D1nO0oh9.js → sizeCapture-X5ZJPWSS-FyX8zxSh.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-C77XHgDB.js → stateDiagram-OKZ733FA-eF-pbs9t.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B-mvnLEc.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DC0D7jEO.js → swimlanes-SLNWSIFB-CcfkA-Dq.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-JtpBmIv4.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BLDHd_Uf.js → timeline-definition-Z64GVDOM-DH8FjnOK.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-eh6Y_q96.js → vennDiagram-T6HMQDX7-beoTagJg.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Cj4Cy9tr.js → wardleyDiagram-T6FBY63Y-CQSfw4cK.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DUIjXY1m.js → xychartDiagram-ELKLHX3M-CQgzQ6jn.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +36 -25
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +17 -4
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +28 -22
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- package/package.json +1 -1
- package/dist/worker/console/static/assets/channel-jVvdIfCV.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-VqdMg0Mg.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-VqdMg0Mg.js +0 -1
- package/dist/worker/console/static/assets/index-0DWC8Vk9.js +0 -325
- package/dist/worker/console/static/assets/index-DAouGklf.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B9GimbZV.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-yxfxvdqm.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
### 改进
|
|
6
|
+
|
|
7
|
+
- Operator Chat 恢复工具对齐真实能力:系统提示改用 `dagReport` / `dagRerunPlan` / `dagRerun` / `standaloneTaskRerun` / `workerTaskRetry` 真名,不再写 `operator_*` 或把 `dagResume` 当成模型可执行项;`dagDoctor` / `dagStatus` / `workerPoolDoctor` 改为 typed `runId`/`taskId`,不再让模型猜 CLI `args[]`。ADR 0013 自定义 base prompt 上限由 1900 统一调整为 2500(2026-08-18,废止 0.8×2498 比例),预算用于写清节点续跑 / 整单重生 / Worker 重排队分流,以及多轮迭代恢复纪律(先一句话总结上一轮错误、修根因不修症状、禁止原样重交、同错两轮后停下报告)。
|
|
8
|
+
|
|
9
|
+
- Operator Chat 对齐 pi-web:建会话不再传封闭 `tools` 白名单(避免 SDK 把扩展 MCP 工具滤出 registry),创建后 `bindExtensions` 发 `session_start`,再钉激活集。扩展注册的 MCP 工具可被模型调用;`apply_patch` / `full-tools` / `shell` / `coding-chat` 仍排除。
|
|
10
|
+
- 对话侧栏 Plugins 右侧新增只读 MCP 弹窗:展示 `mcp.json` 配置的服务,以及当前会话真正注册/激活的 MCP 工具;没有配置也没有工具时保持真实空态。
|
|
11
|
+
- 对话侧栏原 Packages 入口改名为 Plugins,与 pi-web / Pi 用户面一致;内部仍是 package inventory。
|
|
12
|
+
- 对话进行中底部上下文占用百分比保持常驻:token 用量事件不再冲掉上一帧的 contextUsage,并在回合中持续刷新。
|
|
13
|
+
- 上一轮大量 bash 结束后再发新消息,旧工具条不再整批挂到新气泡下面:无主工具不再丢进最新一组,历史按完整回合裁切,空助手气泡若还挂着工具会留下来当锚点。
|
|
14
|
+
- 侧栏文件树点击目录行直接插入 `@目录/` 引用(箭头单独负责展开/收起);输入框 @ 补全也列出目录,排在文件前面。
|
|
15
|
+
- 对话进行中点「停止」立即安静下来(乐观停止,对齐 pi-web):不再等 POST /abort 往返;后端 abort 等待从 8s 降到 2.5s,未终结时由状态对账收敛。
|
|
16
|
+
- 侧栏文件树实时刷新:agent 回合结束、浏览器标签页回到前台、手动点「刷新」时重拉根目录+全部已展开目录(此前手动刷新只拉根目录,子目录永远旧缓存)。
|
|
17
|
+
- Skills / Packages / Models 面板不再把 Pi 的 warning / collision 原文堆在列表上方:改成标题条一颗摘要芯片(如「14 条提示」),点开按「需要处理 / 名称不规范 / 同名未生效」分组,文案用自然语言,路径只显示末两段;名称不规范的技能在列表旁标「提示」,详情里说明「现在这样也能用」。加载失败仍单独成组,可点「再读一次」。
|
|
18
|
+
- Skills / Packages 双栏改为左右各自滚动;资源弹窗挂到 `document.body`,按顶部对话导航的实时高度在剩余视口居中,不再被 sticky 顶栏挡住上沿。
|
|
19
|
+
|
|
5
20
|
### 新增
|
|
6
21
|
|
|
7
22
|
- 前端 plan 唯一机器契约化(JSON-only):`frontend-plan-pi` / `frontend-plan-revision-pi` 输出收敛为「一行导语 + 唯一 fenced `json` 契约 + 唯一 `openspec-citations` 引用块」,彻底移除「可选 Markdown 说明」;plan.md(`contracts/frontend-plan.md`)改由 prewrite gate 用纯函数 `renderFrontendPlanMarkdown` 确定性渲染(不新增 DAG 节点/不新增 Pi 调用)
|
package/dist/build-stamp.json
CHANGED
|
@@ -63,8 +63,6 @@ const officialAction = (action, cli, kind, coverage) => ({
|
|
|
63
63
|
});
|
|
64
64
|
const OFFICIAL_ACTIONS = [
|
|
65
65
|
...[
|
|
66
|
-
["dagDoctor", "loop-agent dag doctor", "read", "model-callable"],
|
|
67
|
-
["dagStatus", "loop-agent dag status", "read", "model-callable"],
|
|
68
66
|
[
|
|
69
67
|
"dagDecisionInspect",
|
|
70
68
|
"loop-agent dag decision inspect",
|
|
@@ -284,7 +282,6 @@ const OFFICIAL_ACTIONS = [
|
|
|
284
282
|
"long-running",
|
|
285
283
|
"advanced",
|
|
286
284
|
],
|
|
287
|
-
["workerPoolDoctor", "agent-worker pool doctor", "read", "model-callable"],
|
|
288
285
|
[
|
|
289
286
|
"workerPoolMarkFailed",
|
|
290
287
|
"agent-worker pool mark-failed",
|
|
@@ -1079,7 +1076,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1079
1076
|
"CONTROLLER_MISMATCH",
|
|
1080
1077
|
"INVALID_INPUT",
|
|
1081
1078
|
],
|
|
1082
|
-
description: "Consume a single-use
|
|
1079
|
+
description: "Consume a single-use executionId from prepareDagExecution and execute the reviewed DAG. Do not pass a DAG path and do not bash `task advance --approve-gate`. accepted/queued/running/operationId are NOT completion — supervise via operationGet/status/dagReport/dagDoctor/operationWait.",
|
|
1083
1080
|
inputParams: [
|
|
1084
1081
|
{
|
|
1085
1082
|
name: "executionId",
|
|
@@ -1102,7 +1099,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1102
1099
|
resultSchemaVersion: 1,
|
|
1103
1100
|
envelopeSchemaVersion: 1,
|
|
1104
1101
|
requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
|
|
1105
|
-
description: "Read-only
|
|
1102
|
+
description: "Read-only check: can this failed run continue from fromNode? Call after dagReport; pass primaryFailure.nodeId. Does not execute. eligible=true returns planHash for dagRerun. eligible=false: Worker/Task Pool Failed → workerTaskRetry; standalone Console run → standaloneTaskRerun after any required contract adopt. Do not call dagRerun or invent a new task.",
|
|
1106
1103
|
inputParams: [
|
|
1107
1104
|
{
|
|
1108
1105
|
name: "runId",
|
|
@@ -1132,7 +1129,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1132
1129
|
"BINDING_DRIFT",
|
|
1133
1130
|
"INVALID_INPUT",
|
|
1134
1131
|
],
|
|
1135
|
-
description: "
|
|
1132
|
+
description: "Continue the same DAG from fromNode. Requires a fresh dagRerunPlan with eligible=true and that exact planHash. Prefer this for provider flake / safe downstream failures. Not a full task regenerate (that is standaloneTaskRerun). Server rejects stale planHash or binding drift.",
|
|
1136
1133
|
inputParams: [
|
|
1137
1134
|
{
|
|
1138
1135
|
name: "runId",
|
|
@@ -1498,7 +1495,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1498
1495
|
"INVALID_INPUT",
|
|
1499
1496
|
"HUMAN_CONFIRMATION_REQUIRED",
|
|
1500
1497
|
],
|
|
1501
|
-
description: "
|
|
1498
|
+
description: "Regenerate the whole standalone DAG from the CURRENT contract, then validate and execute a new run (parent lineage kept). This is NOT continue-from-failed-node — that is dagRerun. Server rejects the call when dagRerunPlan is eligible, when primaryRecovery.humanRequired=true, or when run facts do not recommend rerun-task. Adopt contract changes before calling or the new DAG repeats the same failure.",
|
|
1502
1499
|
inputParams: [
|
|
1503
1500
|
{
|
|
1504
1501
|
name: "runId",
|
|
@@ -1542,7 +1539,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1542
1539
|
"INVALID_INPUT",
|
|
1543
1540
|
"HUMAN_CONFIRMATION_REQUIRED",
|
|
1544
1541
|
],
|
|
1545
|
-
description: "Requeue
|
|
1542
|
+
description: "Requeue a Worker Task Pool task Failed → Ready. Not a DAG node rerun (use dagRerun) and not a standalone DAG regenerate (use standaloneTaskRerun). Confirm with workerPoolDoctor first; server accepts only Failed pool tasks. Pass featureId when known — it must match the pool record.",
|
|
1546
1543
|
inputParams: [
|
|
1547
1544
|
{
|
|
1548
1545
|
name: "taskId",
|
|
@@ -1568,6 +1565,66 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1568
1565
|
modelCallable: "always",
|
|
1569
1566
|
humanConfirmation: "none",
|
|
1570
1567
|
},
|
|
1568
|
+
{
|
|
1569
|
+
action: "dagDoctor",
|
|
1570
|
+
cli: "loop-agent dag doctor [--run-id <id>] --json",
|
|
1571
|
+
kind: "read",
|
|
1572
|
+
inputSchemaVersion: 1,
|
|
1573
|
+
resultSchemaVersion: 1,
|
|
1574
|
+
envelopeSchemaVersion: 1,
|
|
1575
|
+
requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
|
|
1576
|
+
description: "Diagnose DAG run health. Omit runId for a repo-wide inventory (active/paused/completed + health issues). Pass runId for one run's liveness, heartbeat, failure node, and recovery hints. Use this to supervise stalls; it does not mutate the run.",
|
|
1577
|
+
inputParams: [
|
|
1578
|
+
{
|
|
1579
|
+
name: "runId",
|
|
1580
|
+
type: "string",
|
|
1581
|
+
required: false,
|
|
1582
|
+
description: "optional dag run id; omit to inventory all runs, pass to diagnose one run",
|
|
1583
|
+
},
|
|
1584
|
+
],
|
|
1585
|
+
modelCallable: "always",
|
|
1586
|
+
humanConfirmation: "none",
|
|
1587
|
+
},
|
|
1588
|
+
{
|
|
1589
|
+
action: "dagStatus",
|
|
1590
|
+
cli: "loop-agent dag status --run-id <id>",
|
|
1591
|
+
kind: "read",
|
|
1592
|
+
inputSchemaVersion: 1,
|
|
1593
|
+
resultSchemaVersion: 1,
|
|
1594
|
+
envelopeSchemaVersion: 1,
|
|
1595
|
+
requiredErrorCodes: ["NOT_FOUND", "INVALID_INPUT"],
|
|
1596
|
+
description: "Read one DAG run's live status, current node, and liveness. Requires runId. Complements dagReport (terminal handoff) and dagDoctor (health diagnosis). Does not mutate the run.",
|
|
1597
|
+
inputParams: [
|
|
1598
|
+
{
|
|
1599
|
+
name: "runId",
|
|
1600
|
+
type: "string",
|
|
1601
|
+
required: true,
|
|
1602
|
+
description: "dag run id",
|
|
1603
|
+
},
|
|
1604
|
+
],
|
|
1605
|
+
modelCallable: "always",
|
|
1606
|
+
humanConfirmation: "none",
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
action: "workerPoolDoctor",
|
|
1610
|
+
cli: "agent-worker pool doctor --repo . --json",
|
|
1611
|
+
kind: "read",
|
|
1612
|
+
inputSchemaVersion: 1,
|
|
1613
|
+
resultSchemaVersion: 1,
|
|
1614
|
+
envelopeSchemaVersion: 1,
|
|
1615
|
+
requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
|
|
1616
|
+
description: "Read-only Task Pool inventory (legacy + v2). Use before workerTaskRetry to confirm the task exists and status is Failed. Optional taskId filters the returned inventory to that task.",
|
|
1617
|
+
inputParams: [
|
|
1618
|
+
{
|
|
1619
|
+
name: "taskId",
|
|
1620
|
+
type: "string",
|
|
1621
|
+
required: false,
|
|
1622
|
+
description: "optional task id to focus the inventory",
|
|
1623
|
+
},
|
|
1624
|
+
],
|
|
1625
|
+
modelCallable: "always",
|
|
1626
|
+
humanConfirmation: "none",
|
|
1627
|
+
},
|
|
1571
1628
|
...OFFICIAL_ACTIONS,
|
|
1572
1629
|
];
|
|
1573
1630
|
return {
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only MCP inventory for Operator Chat.
|
|
3
|
+
*
|
|
4
|
+
* Pi has no built-in MCP catalog. Console projects two real facts:
|
|
5
|
+
* 1. mcp.json server specs (configured, may or may not have registered tools)
|
|
6
|
+
* 2. tools already on the live session that look like MCP registrations
|
|
7
|
+
*
|
|
8
|
+
* Never fabricate servers or tools. Missing files stay empty.
|
|
9
|
+
*/
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
const MCP_CONFIG_NAMES = ["mcp.json"];
|
|
13
|
+
export function isMcpToolName(name) {
|
|
14
|
+
const lower = name.trim().toLowerCase();
|
|
15
|
+
return lower === "mcp" || lower.startsWith("mcp_") || lower.startsWith("mcp-");
|
|
16
|
+
}
|
|
17
|
+
export function inferMcpServerName(toolName, knownServers = []) {
|
|
18
|
+
const name = toolName.trim();
|
|
19
|
+
if (/^mcp$/i.test(name))
|
|
20
|
+
return undefined;
|
|
21
|
+
const rest = name.replace(/^mcp[_-]/i, "");
|
|
22
|
+
if (!rest || rest === name || knownServers.length === 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
const ranked = [...knownServers].sort((left, right) => right.length - left.length);
|
|
25
|
+
const lower = rest.toLowerCase();
|
|
26
|
+
return ranked.find((server) => {
|
|
27
|
+
const key = server.toLowerCase();
|
|
28
|
+
return lower === key || lower.startsWith(`${key}_`) || lower.startsWith(`${key}-`);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function looksLikeMcpTool(tool) {
|
|
32
|
+
const name = typeof tool.name === "string" ? tool.name : "";
|
|
33
|
+
if (!name)
|
|
34
|
+
return false;
|
|
35
|
+
if (isMcpToolName(name))
|
|
36
|
+
return true;
|
|
37
|
+
const source = `${tool.sourceInfo?.source ?? ""} ${tool.sourceInfo?.path ?? ""}`.toLowerCase();
|
|
38
|
+
return source.includes("mcp");
|
|
39
|
+
}
|
|
40
|
+
function asRecord(value) {
|
|
41
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
42
|
+
? value
|
|
43
|
+
: undefined;
|
|
44
|
+
}
|
|
45
|
+
function readEnabled(entry) {
|
|
46
|
+
return entry.enabled !== false;
|
|
47
|
+
}
|
|
48
|
+
function readTransport(entry) {
|
|
49
|
+
if (typeof entry.transport === "string" && entry.transport.trim()) {
|
|
50
|
+
return entry.transport.trim();
|
|
51
|
+
}
|
|
52
|
+
if (typeof entry.type === "string" && entry.type.trim()) {
|
|
53
|
+
return entry.type.trim();
|
|
54
|
+
}
|
|
55
|
+
if (typeof entry.url === "string")
|
|
56
|
+
return "http";
|
|
57
|
+
if (Array.isArray(entry.command) || typeof entry.command === "string") {
|
|
58
|
+
return "stdio";
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
function readCommand(entry) {
|
|
63
|
+
if (typeof entry.command === "string" && entry.command.trim()) {
|
|
64
|
+
const args = Array.isArray(entry.args)
|
|
65
|
+
? entry.args.filter((item) => typeof item === "string").join(" ")
|
|
66
|
+
: "";
|
|
67
|
+
return args ? `${entry.command} ${args}` : entry.command;
|
|
68
|
+
}
|
|
69
|
+
if (Array.isArray(entry.command)) {
|
|
70
|
+
return entry.command
|
|
71
|
+
.filter((item) => typeof item === "string")
|
|
72
|
+
.join(" ")
|
|
73
|
+
.trim();
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
function extractServerMap(parsed) {
|
|
78
|
+
const root = asRecord(parsed);
|
|
79
|
+
if (!root)
|
|
80
|
+
return {};
|
|
81
|
+
const mcpServers = asRecord(root.mcpServers);
|
|
82
|
+
if (mcpServers)
|
|
83
|
+
return mcpServers;
|
|
84
|
+
const mcp = asRecord(root.mcp);
|
|
85
|
+
if (mcp)
|
|
86
|
+
return mcp;
|
|
87
|
+
const servers = asRecord(root.servers);
|
|
88
|
+
if (servers)
|
|
89
|
+
return servers;
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
export function parseMcpConfig(raw, scope, configPath) {
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(raw);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const servers = [];
|
|
101
|
+
for (const [name, value] of Object.entries(extractServerMap(parsed))) {
|
|
102
|
+
if (!name.trim())
|
|
103
|
+
continue;
|
|
104
|
+
const entry = asRecord(value) ?? {};
|
|
105
|
+
const command = readCommand(entry);
|
|
106
|
+
const url = typeof entry.url === "string" ? entry.url.trim() : undefined;
|
|
107
|
+
servers.push({
|
|
108
|
+
name: name.trim(),
|
|
109
|
+
scope,
|
|
110
|
+
enabled: readEnabled(entry),
|
|
111
|
+
...(readTransport(entry) ? { transport: readTransport(entry) } : {}),
|
|
112
|
+
...(command ? { command } : {}),
|
|
113
|
+
...(url ? { url } : {}),
|
|
114
|
+
configPath,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return servers;
|
|
118
|
+
}
|
|
119
|
+
export function projectSessionMcpTools(tools, activeToolNames = [], knownServers = []) {
|
|
120
|
+
const active = new Set(activeToolNames.map((name) => name.toLowerCase()));
|
|
121
|
+
const seen = new Set();
|
|
122
|
+
const projected = [];
|
|
123
|
+
for (const tool of tools) {
|
|
124
|
+
if (!looksLikeMcpTool(tool) || typeof tool.name !== "string")
|
|
125
|
+
continue;
|
|
126
|
+
const name = tool.name.trim();
|
|
127
|
+
if (!name || seen.has(name))
|
|
128
|
+
continue;
|
|
129
|
+
seen.add(name);
|
|
130
|
+
const server = inferMcpServerName(name, knownServers);
|
|
131
|
+
projected.push({
|
|
132
|
+
name,
|
|
133
|
+
description: typeof tool.description === "string" ? tool.description : "",
|
|
134
|
+
...(server ? { server } : {}),
|
|
135
|
+
source: tool.sourceInfo?.source ?? "",
|
|
136
|
+
scope: tool.sourceInfo?.scope ?? "session",
|
|
137
|
+
...(tool.sourceInfo?.path ? { path: tool.sourceInfo.path } : {}),
|
|
138
|
+
active: active.has(name.toLowerCase()),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return projected;
|
|
142
|
+
}
|
|
143
|
+
export function assembleMcpInventory(servers, tools) {
|
|
144
|
+
const byServer = new Map();
|
|
145
|
+
const unassigned = [];
|
|
146
|
+
for (const tool of tools) {
|
|
147
|
+
const key = tool.server?.toLowerCase();
|
|
148
|
+
if (!key) {
|
|
149
|
+
unassigned.push(tool);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const list = byServer.get(key) ?? [];
|
|
153
|
+
list.push(tool);
|
|
154
|
+
byServer.set(key, list);
|
|
155
|
+
}
|
|
156
|
+
const views = servers.map((server) => {
|
|
157
|
+
const matched = byServer.get(server.name.toLowerCase()) ?? [];
|
|
158
|
+
byServer.delete(server.name.toLowerCase());
|
|
159
|
+
const status = !server.enabled
|
|
160
|
+
? "disabled"
|
|
161
|
+
: matched.length > 0
|
|
162
|
+
? "loaded"
|
|
163
|
+
: "configured";
|
|
164
|
+
return {
|
|
165
|
+
name: server.name,
|
|
166
|
+
scope: server.scope,
|
|
167
|
+
status,
|
|
168
|
+
enabled: server.enabled,
|
|
169
|
+
...(server.transport ? { transport: server.transport } : {}),
|
|
170
|
+
...(server.command ? { command: server.command } : {}),
|
|
171
|
+
...(server.url ? { url: server.url } : {}),
|
|
172
|
+
configPath: server.configPath,
|
|
173
|
+
tools: matched,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
for (const [name, matched] of byServer) {
|
|
177
|
+
views.push({
|
|
178
|
+
name,
|
|
179
|
+
scope: "session",
|
|
180
|
+
status: "loaded",
|
|
181
|
+
enabled: true,
|
|
182
|
+
tools: matched,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return { servers: views, unassignedTools: unassigned };
|
|
186
|
+
}
|
|
187
|
+
async function readConfigFile(filePath) {
|
|
188
|
+
try {
|
|
189
|
+
return await readFile(filePath, "utf8");
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export async function loadMcpServerSpecs(options) {
|
|
196
|
+
const candidates = [
|
|
197
|
+
{ path: path.join(options.agentDir, ...MCP_CONFIG_NAMES), scope: "user" },
|
|
198
|
+
{ path: path.join(options.cwd, ".pi", ...MCP_CONFIG_NAMES), scope: "project" },
|
|
199
|
+
{ path: path.join(options.cwd, ".mcp.json"), scope: "project" },
|
|
200
|
+
];
|
|
201
|
+
const servers = [];
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
for (const candidate of candidates) {
|
|
204
|
+
const raw = await readConfigFile(candidate.path);
|
|
205
|
+
if (!raw)
|
|
206
|
+
continue;
|
|
207
|
+
for (const server of parseMcpConfig(raw, candidate.scope, candidate.path)) {
|
|
208
|
+
const key = `${server.scope}:${server.name.toLowerCase()}`;
|
|
209
|
+
if (seen.has(key))
|
|
210
|
+
continue;
|
|
211
|
+
seen.add(key);
|
|
212
|
+
servers.push(server);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return servers;
|
|
216
|
+
}
|
|
217
|
+
export async function readMcpInventory(options) {
|
|
218
|
+
const servers = await loadMcpServerSpecs(options);
|
|
219
|
+
const tools = projectSessionMcpTools(options.tools ?? [], options.activeToolNames ?? [], servers.map((server) => server.name));
|
|
220
|
+
return assembleMcpInventory(servers, tools);
|
|
221
|
+
}
|