@tea-agent/loop-agent 0.38.0 → 0.38.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/CHANGELOG.md +191 -0
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init-upgrade.js +97 -19
- package/dist/executors/dag-pi-executor.js +42 -4
- package/dist/executors/shell-executor.js +269 -15
- package/dist/infrastructure/harness/atomic-write.js +2 -1
- package/dist/shared/openspec-spec.js +76 -0
- package/dist/shared/operator/capabilities.js +149 -27
- package/dist/task/config-types.js +43 -0
- package/dist/task/frontend-project-capability.js +292 -38
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/pi-runtime.js +165 -83
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +477 -55
- package/dist/worker/console/chat/semantic-activity.js +20 -8
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/chat/turn-process.js +17 -30
- package/dist/worker/console/harness-pi-model-matrix.js +137 -0
- package/dist/worker/console/operator-actions.js +128 -4
- package/dist/worker/console/operator-user-error.js +19 -0
- package/dist/worker/console/pi-readiness.js +8 -1
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/security.js +20 -1
- package/dist/worker/console/server.js +6 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-C4bmo3iY.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-SAD7McOS.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-DAVLUdK1.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-CPPdv4vj.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.js → c4Diagram-5PPSVZJV-D1GRGZax.js} +1 -1
- package/dist/worker/console/static/assets/channel-R6oQIDkw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-WTEKTxin.js → chunk-2GRJ4B5K-D_Bq2ZqK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-CPLVtamK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-DnaZWcL3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-BRTmFgnq.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-BRlfYZoH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-CjNybD4u.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-D3e-74fG.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-Br3ir6f2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-1NgVuRbh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.js → chunk-XXDRQBXY-BTYuvuWN.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BKJQnYVM.js → cose-bilkent-JH36ORCC-BC6Bgosf.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2-DV-HypfL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-DhTaddO-.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-BnEcOmiI.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-xphGRVKp.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-CrdD1mRG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-D4YdLsaF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-LCKyGZ5U.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-BeiTe7t0.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-BO2cq8GS.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-CmWww3Nt.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-C2kkUYg5.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-DY_JZ2iQ.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js → gitGraphDiagram-DS77QQ5N-BizhCSw9.js} +1 -1
- package/dist/worker/console/static/assets/index-BXN89VPX.js +325 -0
- package/dist/worker/console/static/assets/index-CuH5CNFu.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-AsGIGKKB.js → infoDiagram-6WML65LV-Dlo2eK_d.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-DiMDe5Yh.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-sN553oIN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-D9Py31Ld.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-D7Occzk_.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-DjDRboiR.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-Cgc4qlzp.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-DxZj8pKd.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-CJ50MngR.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-BY-OD8Tr.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-CQFButfK.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-BB_QUK_b.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-CqhXZjyx.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-2l1U4rgJ.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.js → stateDiagram-OKZ733FA-BX1FbrhJ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-xi7lcMyp.js → swimlanes-SLNWSIFB-DOSnT7hb.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-DVJQA7AN.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-74uxl9gx.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-DsBxlcg_.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-snuW9Mjw.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +39 -0
- package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +82 -22
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +42 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +216 -10
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +755 -51
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
- package/dist/workflows/dag/contract-validator-registrations.js +3 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-routing.js +9 -4
- package/dist/workflows/dag/frontend-implementation-contract.js +386 -0
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +479 -28
- package/dist/workflows/dag/init-hybrid.js +920 -110
- package/dist/workflows/dag/lifecycle.js +4 -0
- package/dist/workflows/dag/node-execution.js +77 -8
- package/dist/workflows/dag/prompt.js +45 -0
- package/dist/workflows/dag/report.js +6 -0
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +93 -27
- package/dist/workflows/dag/rerun-run.js +3 -0
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +33 -0
- package/dist/workflows/dag/types.js +148 -4
- package/dist/workflows/dag/validate.js +7 -2
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-design-contract.md +40 -14
- package/docs/templates/frontend-implementation-contract.schema.json +9 -2
- package/docs/templates/frontend-task-constraints.md +40 -13
- package/docs/templates/frontend-task-requirement.md +54 -20
- package/harness.json +3 -1
- package/package.json +1 -1
- package/skills/frontend-design-review/references/review-checklist.md +2 -0
- package/skills/frontend-implementation/references/design-spec.md +16 -19
- package/skills/frontend-implementation/references/node-contracts.md +15 -11
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
|
@@ -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
|
+
}
|
|
@@ -45,24 +45,26 @@ import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.
|
|
|
45
45
|
* Project governance (operator-only policy, allowedPaths/forbiddenPaths,
|
|
46
46
|
* writer writeSet, taskKind routing, verification matrix, DAG recovery
|
|
47
47
|
* commands, closeout) lives in the loaded target-project AGENTS.md. The
|
|
48
|
-
* prompt below covers: Operator identity, the real tool surface (
|
|
49
|
-
* first + Pi tools, obey AGENTS.md), denied non-Pi runtimes,
|
|
50
|
-
* init/init-upgrade fallback for uninitialized repos, DAG
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
48
|
+
* prompt below covers: Operator identity, the real tool surface (typed
|
|
49
|
+
* operator tools first + Pi tools, obey AGENTS.md), denied non-Pi runtimes,
|
|
50
|
+
* safe-* probes, init/init-upgrade fallback for uninitialized repos, DAG
|
|
51
|
+
* supervision with 60/90/120/180s adaptive polling
|
|
52
|
+
* (operationGet/status/dagReport/dagDoctor/operationWait), R2 recovery
|
|
53
|
+
* (dagReport → dagRerunPlan → dagRerun; ineligible → standaloneTaskRerun or
|
|
54
|
+
* workerTaskRetry, never dagResume) and G2 bounded writeSet auto-approval.
|
|
54
55
|
*
|
|
55
56
|
* The old Human Gate tutorial paragraphs (prepare + browser Human Gate,
|
|
56
57
|
* "confirmDagConfirmation is a human-only action", the long "Recovery default
|
|
57
58
|
* order" table) are REMOVED — bounded DAGs start and recover autonomously
|
|
58
|
-
* (AC-01: ≤
|
|
59
|
+
* (AC-01: ≤ 2500 chars; 2498-slimming ratio retired 2026-08-18).
|
|
59
60
|
*/
|
|
60
61
|
export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
|
|
61
62
|
"You are General Operator Chat.",
|
|
62
|
-
"Use
|
|
63
|
+
"Use operator tools first (dagReport, dagRerunPlan, dagRerun). Pi read/write/edit/bash/grep/find/ls are available; obey repository AGENTS.md. apply_patch, full-tools, shell and coding-chat are denied. Prefer safe-read/safe-grep for probes.",
|
|
63
64
|
"No harness and asked to init: `loop-agent init instructions --repo-root .`, then `loop-agent init --repo-root . --profile full --merge`; verify with doctor, inspect and docs audit. Updates: `loop-agent init upgrade --repo-root . --json` until stable; `loop-agent init check-update` is read-only.",
|
|
64
65
|
"Long DAGs: prepareDagExecution → runDag → operationId. Never foreground-Bash task advance, tail/head pipes or nohup/Start-Process/start; do not end on accepted/queued/running or an operationId — NOT completion. Supervise operationGet/status/dagReport/dagDoctor/operationWait with 60s → 90s → 120s → 180s backoff; reset to 60s on state change; 30-60s re-checks when stall suspected. Every operationWait continuation MUST use afterSeq=previous nextSeq (prefer recommendedNextCall); never reuse an older cursor. Heartbeats are not meaningful progress.",
|
|
65
|
-
"On failure consume terminal runSummary first. If progress exists, wait; else
|
|
66
|
+
"On failure consume terminal runSummary first. If progress exists, wait; else dagReport then dagRerunPlan(fromNode=primaryFailure.nodeId); eligible → dagRerun (node rerun only: reruns the failed subgraph, keeps prior successes). If plan ineligible (unsafe-shell subgraph, binding/workspace drift): Worker Failed → workerTaskRetry; standalone → standaloneTaskRerun after contract adopt (regenerates the whole task, not node continue). Do not call dagResume. A run-failed task without an open gate is terminal: do not re-advance a run-failed task. Before run start, two identical lifecycleState/blocker/revision/DAG-hash observations mean no progress: stop and report after one bounded probe; never loop identical bash/read/grep. Auto-approve only bounded writeSet inside allowedPaths, outside forbiddenPaths, with structured verification.",
|
|
67
|
+
"Iterate toward the goal in bounded rounds: first summarize the previous round's error in one line (wrong fix, wrong node, or environment), fix its root cause — not the symptom — then rerun the failed node with a fresh plan. Never resubmit an unchanged fix; after two rounds failing the same way, stop and report evidence and remaining routes.",
|
|
66
68
|
"Continue until success, user stop, or no safe recovery remains; then stop and report failure/evidence. Never create a new task for a transient failure or replace repair-pi with direct edits.",
|
|
67
69
|
].join("\n");
|
|
68
70
|
/** Compose the inspectable system prompt actually injected into Operator Chat. */
|
|
@@ -151,6 +153,63 @@ export function computeOperatorChatActiveToolNames(session) {
|
|
|
151
153
|
}
|
|
152
154
|
return extras.length > 0 ? [...baseline, ...extras] : baseline;
|
|
153
155
|
}
|
|
156
|
+
const OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS = Object.freeze([
|
|
157
|
+
"apply_patch",
|
|
158
|
+
"apply-patch",
|
|
159
|
+
"full-tools",
|
|
160
|
+
"full_tools",
|
|
161
|
+
"coding-chat",
|
|
162
|
+
"coding_chat",
|
|
163
|
+
"shell",
|
|
164
|
+
]);
|
|
165
|
+
function createConsoleExtensionBindings() {
|
|
166
|
+
return {
|
|
167
|
+
mode: "rpc",
|
|
168
|
+
onError: (error) => {
|
|
169
|
+
const message = error instanceof Error
|
|
170
|
+
? error.message
|
|
171
|
+
: typeof error === "object" &&
|
|
172
|
+
error &&
|
|
173
|
+
"error" in error &&
|
|
174
|
+
typeof error.error === "string"
|
|
175
|
+
? String(error.error)
|
|
176
|
+
: String(error);
|
|
177
|
+
process.stderr.write(`[console] extension: ${message}\n`);
|
|
178
|
+
},
|
|
179
|
+
uiContext: {
|
|
180
|
+
select: async () => undefined,
|
|
181
|
+
confirm: async () => false,
|
|
182
|
+
input: async () => undefined,
|
|
183
|
+
editor: async () => undefined,
|
|
184
|
+
notify: () => undefined,
|
|
185
|
+
onTerminalInput: () => () => undefined,
|
|
186
|
+
setStatus: () => undefined,
|
|
187
|
+
setWorkingMessage: () => undefined,
|
|
188
|
+
setWorkingVisible: () => undefined,
|
|
189
|
+
setWorkingIndicator: () => undefined,
|
|
190
|
+
setHiddenThinkingLabel: () => undefined,
|
|
191
|
+
setWidget: () => undefined,
|
|
192
|
+
setFooter: () => undefined,
|
|
193
|
+
setHeader: () => undefined,
|
|
194
|
+
setTitle: () => undefined,
|
|
195
|
+
custom: async () => undefined,
|
|
196
|
+
pasteToEditor: () => undefined,
|
|
197
|
+
setEditorText: () => undefined,
|
|
198
|
+
getEditorText: () => "",
|
|
199
|
+
addAutocompleteProvider: () => undefined,
|
|
200
|
+
setEditorComponent: () => undefined,
|
|
201
|
+
getEditorComponent: () => undefined,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async function bindSessionExtensions(session) {
|
|
206
|
+
if (typeof session.bindExtensions !== "function")
|
|
207
|
+
return;
|
|
208
|
+
await session.bindExtensions(createConsoleExtensionBindings());
|
|
209
|
+
}
|
|
210
|
+
function pinOperatorChatTools(session) {
|
|
211
|
+
session.setActiveToolsByName(computeOperatorChatActiveToolNames(session));
|
|
212
|
+
}
|
|
154
213
|
/**
|
|
155
214
|
* Build Pi custom ToolDefinition objects for every whitelisted operator action.
|
|
156
215
|
* Each tool's execute() routes into the operator action dispatcher (Gate 3).
|
|
@@ -169,19 +228,24 @@ async function buildCustomOperatorTools(options) {
|
|
|
169
228
|
// TypeBox schema is built dynamically; cast through unknown to the builder
|
|
170
229
|
// since Static<TParams> is structural and cannot be statically inferred here.
|
|
171
230
|
const parameters = Type.Object(schema.inputParams.reduce((acc, param) => {
|
|
231
|
+
// Carry the registry's param description into the tool schema so
|
|
232
|
+
// the model sees parameter semantics, not just names/types.
|
|
233
|
+
const desc = param.description?.trim()
|
|
234
|
+
? { description: param.description }
|
|
235
|
+
: {};
|
|
172
236
|
let t;
|
|
173
237
|
switch (param.type) {
|
|
174
238
|
case "string":
|
|
175
|
-
t = Type.String();
|
|
239
|
+
t = Type.String(desc);
|
|
176
240
|
break;
|
|
177
241
|
case "boolean":
|
|
178
|
-
t = Type.Boolean();
|
|
242
|
+
t = Type.Boolean(desc);
|
|
179
243
|
break;
|
|
180
244
|
case "number":
|
|
181
|
-
t = Type.Number();
|
|
245
|
+
t = Type.Number(desc);
|
|
182
246
|
break;
|
|
183
247
|
case "array":
|
|
184
|
-
t = Type.Array(Type.String());
|
|
248
|
+
t = Type.Array(Type.String({}), Object.keys(desc).length ? desc : {});
|
|
185
249
|
break;
|
|
186
250
|
default:
|
|
187
251
|
t = Type.Object({}, { additionalProperties: true });
|
|
@@ -193,7 +257,7 @@ async function buildCustomOperatorTools(options) {
|
|
|
193
257
|
name: schema.toolId,
|
|
194
258
|
label: schema.toolId,
|
|
195
259
|
description: `${schema.description} (kind=${schema.kind}; cli: ${schema.cli})`,
|
|
196
|
-
promptSnippet:
|
|
260
|
+
promptSnippet: schema.description,
|
|
197
261
|
parameters: parameters,
|
|
198
262
|
async execute(toolCallId, params) {
|
|
199
263
|
const decision = authorizeOperatorChatTool(schema.toolId);
|
|
@@ -505,6 +569,29 @@ export function projectRuntimeSnapshot(input) {
|
|
|
505
569
|
})),
|
|
506
570
|
diagnostics: pkg.diagnostics.map((diag) => field(diag, 400) ?? ""),
|
|
507
571
|
});
|
|
572
|
+
const projectMcpTool = (tool) => ({
|
|
573
|
+
name: field(tool.name, 200) ?? "",
|
|
574
|
+
description: field(tool.description, 600) ?? "",
|
|
575
|
+
...(tool.server ? { server: field(tool.server, 200) } : {}),
|
|
576
|
+
source: field(tool.source, 200) ?? "",
|
|
577
|
+
scope: tool.scope,
|
|
578
|
+
...(tool.path ? { path: field(tool.path, 400) } : {}),
|
|
579
|
+
active: tool.active,
|
|
580
|
+
});
|
|
581
|
+
const projectMcp = (mcp) => ({
|
|
582
|
+
servers: mcp.servers.map((server) => ({
|
|
583
|
+
name: field(server.name, 200) ?? "",
|
|
584
|
+
scope: server.scope,
|
|
585
|
+
status: server.status,
|
|
586
|
+
enabled: server.enabled,
|
|
587
|
+
...(server.transport ? { transport: field(server.transport, 80) } : {}),
|
|
588
|
+
...(server.command ? { command: field(server.command, 400) } : {}),
|
|
589
|
+
...(server.url ? { url: field(server.url, 400) } : {}),
|
|
590
|
+
...(server.configPath ? { configPath: field(server.configPath, 400) } : {}),
|
|
591
|
+
tools: server.tools.map(projectMcpTool),
|
|
592
|
+
})),
|
|
593
|
+
unassignedTools: mcp.unassignedTools.map(projectMcpTool),
|
|
594
|
+
});
|
|
508
595
|
const projectDiagnostic = (diag) => ({
|
|
509
596
|
type: diag.type,
|
|
510
597
|
...(diag.code ? { code: diag.code } : {}),
|
|
@@ -560,6 +647,7 @@ export function projectRuntimeSnapshot(input) {
|
|
|
560
647
|
skills: input.skills.map(projectSkill),
|
|
561
648
|
extensions: input.extensions.map(projectExtension),
|
|
562
649
|
packages: input.packages.map(projectPackage),
|
|
650
|
+
mcp: projectMcp(input.mcp ?? { servers: [], unassignedTools: [] }),
|
|
563
651
|
diagnostics: input.diagnostics.map(projectDiagnostic),
|
|
564
652
|
hasBash: input.hasBash,
|
|
565
653
|
};
|
|
@@ -903,36 +991,16 @@ export class ConsolePiRuntime {
|
|
|
903
991
|
const customTools = await this.loadCustomTools();
|
|
904
992
|
if (this.disposedSessions.has(sessionId))
|
|
905
993
|
return;
|
|
906
|
-
//
|
|
907
|
-
//
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
|
-
// operationWait / safe-read / git-status / ...) from the registry, so the
|
|
913
|
-
// model could only ever see bash/read/write/edit/grep/find/ls. The
|
|
914
|
-
// allowlist must cover the full tool surface that is later activated via
|
|
915
|
-
// setActiveToolsByName(computeOperatorChatActiveToolNames(session)) —
|
|
916
|
-
// operator actions + built-in explore tools + safe explore tools.
|
|
917
|
-
// Hard write/coding channels stay blocked via excludeTools below (they
|
|
918
|
-
// are removed even when present in the allowed list).
|
|
994
|
+
// pi-web parity: do NOT pass `tools`. A closed allowlist becomes
|
|
995
|
+
// SDK allowedToolNames and filters extension MCP tools out of the
|
|
996
|
+
// registry before Gate 2 can re-add them. Operator/safe customTools
|
|
997
|
+
// still register via customTools; non-Pi write channels stay blocked
|
|
998
|
+
// by excludeTools. After create, bindExtensions emits session_start
|
|
999
|
+
// so MCP adapters can register, then pin the active set.
|
|
919
1000
|
const { session } = await this.bindings.createSessionFromServices({
|
|
920
1001
|
services,
|
|
921
1002
|
sessionManager: init.sessionManager,
|
|
922
|
-
|
|
923
|
-
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
924
|
-
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
925
|
-
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
926
|
-
],
|
|
927
|
-
excludeTools: [
|
|
928
|
-
"apply_patch",
|
|
929
|
-
"apply-patch",
|
|
930
|
-
"full-tools",
|
|
931
|
-
"full_tools",
|
|
932
|
-
"coding-chat",
|
|
933
|
-
"coding_chat",
|
|
934
|
-
"shell",
|
|
935
|
-
],
|
|
1003
|
+
excludeTools: [...OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS],
|
|
936
1004
|
...(customTools.length > 0 ? { customTools } : {}),
|
|
937
1005
|
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
938
1006
|
});
|
|
@@ -945,7 +1013,8 @@ export class ConsolePiRuntime {
|
|
|
945
1013
|
}
|
|
946
1014
|
return;
|
|
947
1015
|
}
|
|
948
|
-
|
|
1016
|
+
await bindSessionExtensions(session);
|
|
1017
|
+
pinOperatorChatTools(session);
|
|
949
1018
|
// Register then re-check dispose race: a concurrent dispose must win.
|
|
950
1019
|
this.sessions.set(sessionId, session);
|
|
951
1020
|
this.sessionManagers.set(sessionId, init.sessionManager);
|
|
@@ -1616,6 +1685,24 @@ export class ConsolePiRuntime {
|
|
|
1616
1685
|
retryable: true,
|
|
1617
1686
|
});
|
|
1618
1687
|
}
|
|
1688
|
+
const { readMcpInventory } = await import("./mcp-inventory.js");
|
|
1689
|
+
let mcp = { servers: [], unassignedTools: [] };
|
|
1690
|
+
try {
|
|
1691
|
+
mcp = await readMcpInventory({
|
|
1692
|
+
agentDir: await safeGetAgentDir(this.bindings),
|
|
1693
|
+
cwd: this.options.cwd,
|
|
1694
|
+
tools: session.getAllTools?.() ?? [],
|
|
1695
|
+
activeToolNames: session.getActiveToolNames(),
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
catch (error) {
|
|
1699
|
+
diagnostics.push({
|
|
1700
|
+
type: "error",
|
|
1701
|
+
code: "MCP_INVENTORY_FAILED",
|
|
1702
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1703
|
+
retryable: true,
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1619
1706
|
return projectRuntimeSnapshot({
|
|
1620
1707
|
sessionId,
|
|
1621
1708
|
revision: this.revisions.get(sessionId) ?? 0,
|
|
@@ -1651,6 +1738,7 @@ export class ConsolePiRuntime {
|
|
|
1651
1738
|
skills,
|
|
1652
1739
|
extensions,
|
|
1653
1740
|
packages,
|
|
1741
|
+
mcp,
|
|
1654
1742
|
diagnostics,
|
|
1655
1743
|
hasBash: true,
|
|
1656
1744
|
});
|
|
@@ -1833,29 +1921,15 @@ export class ConsolePiRuntime {
|
|
|
1833
1921
|
: undefined;
|
|
1834
1922
|
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
1835
1923
|
const customTools = await this.loadCustomTools();
|
|
1836
|
-
// Same allowlist rationale as materializeSession: the SDK filters
|
|
1837
|
-
// customTools against `tools`, so the allowed names must cover the full
|
|
1838
|
-
// operator + explore surface or every custom tool is silently dropped.
|
|
1839
1924
|
const { session } = await this.bindings.createSessionFromServices({
|
|
1840
1925
|
services,
|
|
1841
1926
|
sessionManager: init.sessionManager,
|
|
1842
|
-
|
|
1843
|
-
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
1844
|
-
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
1845
|
-
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
1846
|
-
],
|
|
1847
|
-
excludeTools: [
|
|
1848
|
-
"apply_patch",
|
|
1849
|
-
"apply-patch",
|
|
1850
|
-
"full-tools",
|
|
1851
|
-
"full_tools",
|
|
1852
|
-
"coding-chat",
|
|
1853
|
-
"coding_chat",
|
|
1854
|
-
"shell",
|
|
1855
|
-
],
|
|
1927
|
+
excludeTools: [...OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS],
|
|
1856
1928
|
...(customTools.length > 0 ? { customTools } : {}),
|
|
1857
1929
|
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
1858
1930
|
});
|
|
1931
|
+
await bindSessionExtensions(session);
|
|
1932
|
+
pinOperatorChatTools(session);
|
|
1859
1933
|
return {
|
|
1860
1934
|
session,
|
|
1861
1935
|
modelRuntime,
|
|
@@ -1950,6 +2024,32 @@ export class ConsolePiRuntime {
|
|
|
1950
2024
|
if (options?.signal?.aborted) {
|
|
1951
2025
|
return;
|
|
1952
2026
|
}
|
|
2027
|
+
const emitContextUsage = () => {
|
|
2028
|
+
try {
|
|
2029
|
+
const contextUsage = session.getSessionStats?.()?.contextUsage;
|
|
2030
|
+
if (!contextUsage)
|
|
2031
|
+
return;
|
|
2032
|
+
if (contextUsage.contextWindow <= 0 &&
|
|
2033
|
+
contextUsage.percent === null &&
|
|
2034
|
+
contextUsage.tokens === null) {
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
onEvent({
|
|
2038
|
+
type: "usage",
|
|
2039
|
+
sessionId,
|
|
2040
|
+
usage: {
|
|
2041
|
+
inputTokens: 0,
|
|
2042
|
+
outputTokens: 0,
|
|
2043
|
+
totalTokens: 0,
|
|
2044
|
+
contextUsage,
|
|
2045
|
+
},
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
catch {
|
|
2049
|
+
// Stats getter is best-effort; never break the event stream.
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
emitContextUsage();
|
|
1953
2053
|
unsub = session.subscribe((event) => {
|
|
1954
2054
|
const mapped = mapSdkEvent(sessionId, event);
|
|
1955
2055
|
if (mapped)
|
|
@@ -1962,31 +2062,13 @@ export class ConsolePiRuntime {
|
|
|
1962
2062
|
const usage = extractUsageSample(event);
|
|
1963
2063
|
if (usage)
|
|
1964
2064
|
onEvent({ type: "usage", sessionId, usage });
|
|
2065
|
+
emitContextUsage();
|
|
1965
2066
|
}
|
|
1966
|
-
//
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
const contextUsage = stats?.contextUsage;
|
|
1972
|
-
if (contextUsage) {
|
|
1973
|
-
onEvent({
|
|
1974
|
-
type: "usage",
|
|
1975
|
-
sessionId,
|
|
1976
|
-
usage: {
|
|
1977
|
-
inputTokens: 0,
|
|
1978
|
-
outputTokens: 0,
|
|
1979
|
-
totalTokens: 0,
|
|
1980
|
-
...(contextUsage.tokens === null
|
|
1981
|
-
? {}
|
|
1982
|
-
: { contextUsage }),
|
|
1983
|
-
},
|
|
1984
|
-
});
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
catch {
|
|
1988
|
-
// Stats getter is best-effort; never break the event stream.
|
|
1989
|
-
}
|
|
2067
|
+
// Keep the context fill bar live through the turn (pi-web SessionInfoBar).
|
|
2068
|
+
if (event.type === "agent_start" ||
|
|
2069
|
+
event.type === "agent_settled" ||
|
|
2070
|
+
event.type === "tool_execution_end") {
|
|
2071
|
+
emitContextUsage();
|
|
1990
2072
|
}
|
|
1991
2073
|
});
|
|
1992
2074
|
options?.signal?.addEventListener("abort", onAbort);
|
|
@@ -77,6 +77,7 @@ function isBinaryExt(p) {
|
|
|
77
77
|
export async function walkRepoFiles(repoRoot, options) {
|
|
78
78
|
const skipSensitive = options?.skipSensitive ?? true;
|
|
79
79
|
const maxFiles = options?.maxFiles ?? 5000;
|
|
80
|
+
const includeDirs = options?.includeDirs ?? false;
|
|
80
81
|
const filter = options?.glob ? globToRe(options.glob) : undefined;
|
|
81
82
|
const out = [];
|
|
82
83
|
async function walk(dir, prefix) {
|
|
@@ -96,7 +97,15 @@ export async function walkRepoFiles(repoRoot, options) {
|
|
|
96
97
|
if (entry.isDirectory()) {
|
|
97
98
|
if (SKIP_DIRS.has(name))
|
|
98
99
|
continue;
|
|
99
|
-
|
|
100
|
+
const dirRelative = `${prefix}${name}/`;
|
|
101
|
+
if (includeDirs) {
|
|
102
|
+
const dirEntry = {
|
|
103
|
+
abs: path.join(dir, name),
|
|
104
|
+
repoRelative: dirRelative,
|
|
105
|
+
};
|
|
106
|
+
out.push(dirEntry);
|
|
107
|
+
}
|
|
108
|
+
await walk(path.join(dir, name), dirRelative);
|
|
100
109
|
continue;
|
|
101
110
|
}
|
|
102
111
|
if (!entry.isFile())
|