@tea-agent/loop-agent 0.38.0-next.1 → 0.38.1
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 +95 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init-upgrade.js +97 -19
- package/dist/commands/init.js +14 -2
- package/dist/executors/dag-pi-executor.js +1 -0
- package/dist/governance/manifest-types.js +2 -0
- package/dist/infrastructure/harness/atomic-write.js +2 -1
- package/dist/shared/openspec-spec.js +76 -0
- package/dist/shared/operator/capabilities.js +65 -8
- package/dist/task/config-types.js +21 -0
- package/dist/task/frontend-project-capability.js +292 -38
- 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 +17 -30
- package/dist/worker/console/harness-pi-model-matrix.js +146 -0
- package/dist/worker/console/operator-actions.js +123 -0
- package/dist/worker/console/operator-user-error.js +19 -0
- package/dist/worker/console/pi-readiness.js +7 -1
- package/dist/worker/console/security.js +20 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-Hfsot6rP.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-YGwEASin.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-CmOM0F92.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-DDLuaHC5.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.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-WTEKTxin.js → chunk-2GRJ4B5K-DvBN4JZc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-B1UVgW4E.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-Cf0GgXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-DbNA24sj.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-Bi_0Be06.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-C8pPGvRd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-BitQY8Z-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-U67GCiEo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-IoJwXPBA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.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-BKJQnYVM.js → cose-bilkent-JH36ORCC-CUZgsiRC.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2-COmCh4pT.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-Ta6FSm68.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-UdcR-VW9.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-DybcO3Et.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-wErDFWLw.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-DkVHZWqz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-DdqWs7QN.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-C9dMVmDo.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-B2T2WcBK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-Bt9rjDFq.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-U8ic_tvi.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-BfQJdQoO.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.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-AsGIGKKB.js → infoDiagram-6WML65LV-DzqWXNO_.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-CJmGQ5Yp.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-ChPtKFLz.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-PRe7wB5u.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-DpKX6QNp.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-Dnl76b7Y.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-S9PCXc3J.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-Du8e-FSX.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-CY6OmHTc.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-C65GPhpj.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-U6IUiTCL.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-D-I_9Qhi.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-B57Fhed0.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-CJOtiw21.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-FyX8zxSh.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.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-xi7lcMyp.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-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-DH8FjnOK.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-beoTagJg.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-CQSfw4cK.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-CQgzQ6jn.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +51 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +44 -21
- 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/useChatSessions.js +15 -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/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/failure-routing.js +9 -4
- package/dist/workflows/dag/frontend-implementation-contract.js +158 -0
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +411 -20
- package/dist/workflows/dag/init-hybrid.js +272 -27
- package/dist/workflows/dag/lifecycle.js +4 -0
- package/dist/workflows/dag/node-execution.js +51 -9
- package/dist/workflows/dag/report.js +6 -0
- package/dist/workflows/dag/retry-policy.js +15 -0
- package/dist/workflows/dag/types.js +50 -4
- package/dist/workflows/dag/validate.js +7 -2
- 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/docs/templates/harness.schema.json +8 -0
- package/harness.json +11 -2
- 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
|
@@ -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).
|
|
@@ -505,6 +564,29 @@ export function projectRuntimeSnapshot(input) {
|
|
|
505
564
|
})),
|
|
506
565
|
diagnostics: pkg.diagnostics.map((diag) => field(diag, 400) ?? ""),
|
|
507
566
|
});
|
|
567
|
+
const projectMcpTool = (tool) => ({
|
|
568
|
+
name: field(tool.name, 200) ?? "",
|
|
569
|
+
description: field(tool.description, 600) ?? "",
|
|
570
|
+
...(tool.server ? { server: field(tool.server, 200) } : {}),
|
|
571
|
+
source: field(tool.source, 200) ?? "",
|
|
572
|
+
scope: tool.scope,
|
|
573
|
+
...(tool.path ? { path: field(tool.path, 400) } : {}),
|
|
574
|
+
active: tool.active,
|
|
575
|
+
});
|
|
576
|
+
const projectMcp = (mcp) => ({
|
|
577
|
+
servers: mcp.servers.map((server) => ({
|
|
578
|
+
name: field(server.name, 200) ?? "",
|
|
579
|
+
scope: server.scope,
|
|
580
|
+
status: server.status,
|
|
581
|
+
enabled: server.enabled,
|
|
582
|
+
...(server.transport ? { transport: field(server.transport, 80) } : {}),
|
|
583
|
+
...(server.command ? { command: field(server.command, 400) } : {}),
|
|
584
|
+
...(server.url ? { url: field(server.url, 400) } : {}),
|
|
585
|
+
...(server.configPath ? { configPath: field(server.configPath, 400) } : {}),
|
|
586
|
+
tools: server.tools.map(projectMcpTool),
|
|
587
|
+
})),
|
|
588
|
+
unassignedTools: mcp.unassignedTools.map(projectMcpTool),
|
|
589
|
+
});
|
|
508
590
|
const projectDiagnostic = (diag) => ({
|
|
509
591
|
type: diag.type,
|
|
510
592
|
...(diag.code ? { code: diag.code } : {}),
|
|
@@ -560,6 +642,7 @@ export function projectRuntimeSnapshot(input) {
|
|
|
560
642
|
skills: input.skills.map(projectSkill),
|
|
561
643
|
extensions: input.extensions.map(projectExtension),
|
|
562
644
|
packages: input.packages.map(projectPackage),
|
|
645
|
+
mcp: projectMcp(input.mcp ?? { servers: [], unassignedTools: [] }),
|
|
563
646
|
diagnostics: input.diagnostics.map(projectDiagnostic),
|
|
564
647
|
hasBash: input.hasBash,
|
|
565
648
|
};
|
|
@@ -903,36 +986,16 @@ export class ConsolePiRuntime {
|
|
|
903
986
|
const customTools = await this.loadCustomTools();
|
|
904
987
|
if (this.disposedSessions.has(sessionId))
|
|
905
988
|
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).
|
|
989
|
+
// pi-web parity: do NOT pass `tools`. A closed allowlist becomes
|
|
990
|
+
// SDK allowedToolNames and filters extension MCP tools out of the
|
|
991
|
+
// registry before Gate 2 can re-add them. Operator/safe customTools
|
|
992
|
+
// still register via customTools; non-Pi write channels stay blocked
|
|
993
|
+
// by excludeTools. After create, bindExtensions emits session_start
|
|
994
|
+
// so MCP adapters can register, then pin the active set.
|
|
919
995
|
const { session } = await this.bindings.createSessionFromServices({
|
|
920
996
|
services,
|
|
921
997
|
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
|
-
],
|
|
998
|
+
excludeTools: [...OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS],
|
|
936
999
|
...(customTools.length > 0 ? { customTools } : {}),
|
|
937
1000
|
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
938
1001
|
});
|
|
@@ -945,7 +1008,8 @@ export class ConsolePiRuntime {
|
|
|
945
1008
|
}
|
|
946
1009
|
return;
|
|
947
1010
|
}
|
|
948
|
-
|
|
1011
|
+
await bindSessionExtensions(session);
|
|
1012
|
+
pinOperatorChatTools(session);
|
|
949
1013
|
// Register then re-check dispose race: a concurrent dispose must win.
|
|
950
1014
|
this.sessions.set(sessionId, session);
|
|
951
1015
|
this.sessionManagers.set(sessionId, init.sessionManager);
|
|
@@ -1616,6 +1680,24 @@ export class ConsolePiRuntime {
|
|
|
1616
1680
|
retryable: true,
|
|
1617
1681
|
});
|
|
1618
1682
|
}
|
|
1683
|
+
const { readMcpInventory } = await import("./mcp-inventory.js");
|
|
1684
|
+
let mcp = { servers: [], unassignedTools: [] };
|
|
1685
|
+
try {
|
|
1686
|
+
mcp = await readMcpInventory({
|
|
1687
|
+
agentDir: await safeGetAgentDir(this.bindings),
|
|
1688
|
+
cwd: this.options.cwd,
|
|
1689
|
+
tools: session.getAllTools?.() ?? [],
|
|
1690
|
+
activeToolNames: session.getActiveToolNames(),
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
catch (error) {
|
|
1694
|
+
diagnostics.push({
|
|
1695
|
+
type: "error",
|
|
1696
|
+
code: "MCP_INVENTORY_FAILED",
|
|
1697
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1698
|
+
retryable: true,
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1619
1701
|
return projectRuntimeSnapshot({
|
|
1620
1702
|
sessionId,
|
|
1621
1703
|
revision: this.revisions.get(sessionId) ?? 0,
|
|
@@ -1651,6 +1733,7 @@ export class ConsolePiRuntime {
|
|
|
1651
1733
|
skills,
|
|
1652
1734
|
extensions,
|
|
1653
1735
|
packages,
|
|
1736
|
+
mcp,
|
|
1654
1737
|
diagnostics,
|
|
1655
1738
|
hasBash: true,
|
|
1656
1739
|
});
|
|
@@ -1833,29 +1916,15 @@ export class ConsolePiRuntime {
|
|
|
1833
1916
|
: undefined;
|
|
1834
1917
|
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
1835
1918
|
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
1919
|
const { session } = await this.bindings.createSessionFromServices({
|
|
1840
1920
|
services,
|
|
1841
1921
|
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
|
-
],
|
|
1922
|
+
excludeTools: [...OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS],
|
|
1856
1923
|
...(customTools.length > 0 ? { customTools } : {}),
|
|
1857
1924
|
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
1858
1925
|
});
|
|
1926
|
+
await bindSessionExtensions(session);
|
|
1927
|
+
pinOperatorChatTools(session);
|
|
1859
1928
|
return {
|
|
1860
1929
|
session,
|
|
1861
1930
|
modelRuntime,
|
|
@@ -1950,6 +2019,32 @@ export class ConsolePiRuntime {
|
|
|
1950
2019
|
if (options?.signal?.aborted) {
|
|
1951
2020
|
return;
|
|
1952
2021
|
}
|
|
2022
|
+
const emitContextUsage = () => {
|
|
2023
|
+
try {
|
|
2024
|
+
const contextUsage = session.getSessionStats?.()?.contextUsage;
|
|
2025
|
+
if (!contextUsage)
|
|
2026
|
+
return;
|
|
2027
|
+
if (contextUsage.contextWindow <= 0 &&
|
|
2028
|
+
contextUsage.percent === null &&
|
|
2029
|
+
contextUsage.tokens === null) {
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
onEvent({
|
|
2033
|
+
type: "usage",
|
|
2034
|
+
sessionId,
|
|
2035
|
+
usage: {
|
|
2036
|
+
inputTokens: 0,
|
|
2037
|
+
outputTokens: 0,
|
|
2038
|
+
totalTokens: 0,
|
|
2039
|
+
contextUsage,
|
|
2040
|
+
},
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
catch {
|
|
2044
|
+
// Stats getter is best-effort; never break the event stream.
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
emitContextUsage();
|
|
1953
2048
|
unsub = session.subscribe((event) => {
|
|
1954
2049
|
const mapped = mapSdkEvent(sessionId, event);
|
|
1955
2050
|
if (mapped)
|
|
@@ -1962,31 +2057,13 @@ export class ConsolePiRuntime {
|
|
|
1962
2057
|
const usage = extractUsageSample(event);
|
|
1963
2058
|
if (usage)
|
|
1964
2059
|
onEvent({ type: "usage", sessionId, usage });
|
|
2060
|
+
emitContextUsage();
|
|
1965
2061
|
}
|
|
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
|
-
}
|
|
2062
|
+
// Keep the context fill bar live through the turn (pi-web SessionInfoBar).
|
|
2063
|
+
if (event.type === "agent_start" ||
|
|
2064
|
+
event.type === "agent_settled" ||
|
|
2065
|
+
event.type === "tool_execution_end") {
|
|
2066
|
+
emitContextUsage();
|
|
1990
2067
|
}
|
|
1991
2068
|
});
|
|
1992
2069
|
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())
|
|
@@ -51,7 +51,7 @@ function turnSummary(turn) {
|
|
|
51
51
|
function isTerminalTurnState(state) {
|
|
52
52
|
return state === "settled" || state === "aborted" || state === "failed";
|
|
53
53
|
}
|
|
54
|
-
const DEFAULT_ABORT_WAIT_MS =
|
|
54
|
+
const DEFAULT_ABORT_WAIT_MS = 2_500;
|
|
55
55
|
/** Parse Chat's `<epoch>:<seq>` SSE cursor. Legacy `<sessionId>:<seq>` has
|
|
56
56
|
* the same wire shape; its prefix intentionally fails the current process
|
|
57
57
|
* epoch comparison and triggers safe snapshot recovery (M5.1). */
|
|
@@ -1642,16 +1642,27 @@ async function handleChatFiles(res, deps, sessionId, query) {
|
|
|
1642
1642
|
if (!(await requireChatSession(res, deps, sessionId)))
|
|
1643
1643
|
return;
|
|
1644
1644
|
const q = query.trim().toLowerCase();
|
|
1645
|
-
const
|
|
1645
|
+
const walked = await walkRepoFiles(deps.appData.repoRoot, {
|
|
1646
1646
|
skipSensitive: true,
|
|
1647
1647
|
maxFiles: 5000,
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
.
|
|
1648
|
+
includeDirs: true,
|
|
1649
|
+
});
|
|
1650
|
+
const entries = walked
|
|
1651
|
+
.filter((entry) => !isSensitivePath(entry.repoRelative) &&
|
|
1652
|
+
(!q || entry.repoRelative.toLowerCase().includes(q)))
|
|
1653
|
+
.sort((a, b) => {
|
|
1654
|
+
const aDir = a.repoRelative.endsWith("/");
|
|
1655
|
+
const bDir = b.repoRelative.endsWith("/");
|
|
1656
|
+
if (aDir !== bDir)
|
|
1657
|
+
return aDir ? -1 : 1;
|
|
1658
|
+
return a.repoRelative.localeCompare(b.repoRelative);
|
|
1659
|
+
})
|
|
1652
1660
|
.slice(0, 50)
|
|
1653
|
-
.map((
|
|
1654
|
-
|
|
1661
|
+
.map((entry) => ({
|
|
1662
|
+
path: entry.repoRelative,
|
|
1663
|
+
isDir: entry.repoRelative.endsWith("/"),
|
|
1664
|
+
}));
|
|
1665
|
+
sendJson(res, 200, { ok: true, files: entries });
|
|
1655
1666
|
}
|
|
1656
1667
|
export async function handleChatCommands(res, deps, sessionId) {
|
|
1657
1668
|
try {
|
|
@@ -39,9 +39,11 @@ const OPERATION_TITLES = {
|
|
|
39
39
|
dagDoctor: "执行诊断",
|
|
40
40
|
dagReport: "执行结果",
|
|
41
41
|
dagRerunPlan: "恢复评估",
|
|
42
|
+
dagRerun: "从节点续跑",
|
|
42
43
|
dagInterruptEligibility: "中止资格",
|
|
43
|
-
standaloneTaskRerun: "
|
|
44
|
-
workerTaskRetry: "
|
|
44
|
+
standaloneTaskRerun: "完整重跑任务",
|
|
45
|
+
workerTaskRetry: "重试 Worker 任务",
|
|
46
|
+
workerPoolDoctor: "任务池诊断",
|
|
45
47
|
promoteRun: "任务推进",
|
|
46
48
|
status: "任务状态",
|
|
47
49
|
};
|
|
@@ -96,10 +98,20 @@ const TOOL_SEMANTICS = {
|
|
|
96
98
|
success: "中止资格已经确认",
|
|
97
99
|
error: "当前运行不能协作中止",
|
|
98
100
|
},
|
|
101
|
+
dagRerun: {
|
|
102
|
+
running: "正在从失败节点续跑",
|
|
103
|
+
success: "已经从失败节点继续执行",
|
|
104
|
+
error: "从失败节点续跑时遇到问题",
|
|
105
|
+
},
|
|
99
106
|
standaloneTaskRerun: {
|
|
100
|
-
running: "
|
|
101
|
-
success: "
|
|
102
|
-
error: "
|
|
107
|
+
running: "正在完整重跑整单任务",
|
|
108
|
+
success: "已经启动完整重跑",
|
|
109
|
+
error: "完整重跑任务时遇到问题",
|
|
110
|
+
},
|
|
111
|
+
workerPoolDoctor: {
|
|
112
|
+
running: "正在读取任务池状态",
|
|
113
|
+
success: "任务池状态已经读取",
|
|
114
|
+
error: "读取任务池状态时遇到问题",
|
|
103
115
|
},
|
|
104
116
|
promoteRun: {
|
|
105
117
|
running: "正在推进任务状态",
|
|
@@ -92,36 +92,25 @@ function sortToolsStable(tools) {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
-
* Flush buffered consecutive tools into process items
|
|
96
|
-
*
|
|
97
|
-
*
|
|
95
|
+
* Flush buffered consecutive tools into process items (pi-web aligned:
|
|
96
|
+
* strict sequential flow, no synthetic grouping). Semantic operator tools
|
|
97
|
+
* stay as individual activity events; plain tools each render as their own
|
|
98
|
+
* card in stable server order (eventSeq first, createdAt fallback).
|
|
98
99
|
* Thinking/text/activity boundaries break the buffer upstream.
|
|
99
100
|
*/
|
|
100
101
|
function flushToolBuffer(processItems, buffer) {
|
|
101
102
|
if (buffer.length === 0)
|
|
102
103
|
return;
|
|
103
104
|
const sorted = sortToolsStable(buffer);
|
|
104
|
-
let plainGroup = [];
|
|
105
|
-
const flushPlain = () => {
|
|
106
|
-
if (plainGroup.length === 1) {
|
|
107
|
-
processItems.push({ kind: "tool", tool: plainGroup[0] });
|
|
108
|
-
}
|
|
109
|
-
else if (plainGroup.length > 1) {
|
|
110
|
-
processItems.push({ kind: "tool-group", tools: plainGroup });
|
|
111
|
-
}
|
|
112
|
-
plainGroup = [];
|
|
113
|
-
};
|
|
114
105
|
for (const tool of sorted) {
|
|
115
106
|
const activity = semanticActivityForTool(tool);
|
|
116
107
|
if (activity) {
|
|
117
|
-
flushPlain();
|
|
118
108
|
processItems.push({ kind: "activity", activity });
|
|
119
109
|
}
|
|
120
110
|
else {
|
|
121
|
-
|
|
111
|
+
processItems.push({ kind: "tool", tool });
|
|
122
112
|
}
|
|
123
113
|
}
|
|
124
|
-
flushPlain();
|
|
125
114
|
}
|
|
126
115
|
function emitProcessBlocks(blocks, group, processItems, usedToolIds) {
|
|
127
116
|
let buffer = [];
|
|
@@ -227,9 +216,6 @@ export function buildTurnProcessGroups(input) {
|
|
|
227
216
|
current = startGroup(`assistant-${message.id}`);
|
|
228
217
|
current.assistants.push(message);
|
|
229
218
|
}
|
|
230
|
-
if (groups.length === 0 && input.toolCalls.length > 0) {
|
|
231
|
-
groups.push(emptyGroup("orphan-tools"));
|
|
232
|
-
}
|
|
233
219
|
const assigned = new Set();
|
|
234
220
|
const assistantToGroup = new Map();
|
|
235
221
|
for (const group of groups) {
|
|
@@ -276,17 +262,12 @@ export function buildTurnProcessGroups(input) {
|
|
|
276
262
|
const end = next?.user?.createdAt ?? Number.POSITIVE_INFINITY;
|
|
277
263
|
return tool.createdAt >= start && tool.createdAt < end;
|
|
278
264
|
});
|
|
265
|
+
// Time window only. Never dump unmatched tools onto the latest group:
|
|
266
|
+
// that is how a previous turn's bash cards jump under a new prompt.
|
|
279
267
|
if (windowGroup) {
|
|
280
268
|
windowGroup.tools.push(tool);
|
|
281
269
|
assigned.add(tool.id);
|
|
282
270
|
}
|
|
283
|
-
else {
|
|
284
|
-
const lastGroup = groups.at(-1);
|
|
285
|
-
if (lastGroup) {
|
|
286
|
-
lastGroup.tools.push(tool);
|
|
287
|
-
assigned.add(tool.id);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
271
|
}
|
|
291
272
|
for (const group of groups) {
|
|
292
273
|
group.tools.sort((a, b) => {
|
|
@@ -303,6 +284,14 @@ export function buildTurnProcessGroups(input) {
|
|
|
303
284
|
}
|
|
304
285
|
return groups;
|
|
305
286
|
}
|
|
287
|
+
/** Keep the most recent complete turns. Slice groups, never raw messages. */
|
|
288
|
+
export function selectRecentTurnGroups(groups, limit) {
|
|
289
|
+
if (limit <= 0)
|
|
290
|
+
return [];
|
|
291
|
+
if (groups.length <= limit)
|
|
292
|
+
return [...groups];
|
|
293
|
+
return groups.slice(-limit);
|
|
294
|
+
}
|
|
306
295
|
export function processGroupSummary(tools, options) {
|
|
307
296
|
const count = tools.length;
|
|
308
297
|
const thinkingCount = options?.thinkingCount ?? 0;
|
|
@@ -324,7 +313,7 @@ export function processGroupSummary(tools, options) {
|
|
|
324
313
|
parts.push("运行中");
|
|
325
314
|
return { count, errorCount, runningCount, stepCount, label: parts.join(" · ") };
|
|
326
315
|
}
|
|
327
|
-
/** Summary over rendered process items
|
|
316
|
+
/** Summary over rendered process items. */
|
|
328
317
|
export function summarizeProcessItems(items) {
|
|
329
318
|
const tools = [];
|
|
330
319
|
let thinkingCount = 0;
|
|
@@ -344,10 +333,8 @@ export function summarizeProcessItems(items) {
|
|
|
344
333
|
else if (item.activity.kind === "progress")
|
|
345
334
|
activityRunningCount += 1;
|
|
346
335
|
}
|
|
347
|
-
else if (item.kind === "tool")
|
|
348
|
-
tools.push(item.tool);
|
|
349
336
|
else
|
|
350
|
-
tools.push(
|
|
337
|
+
tools.push(item.tool);
|
|
351
338
|
}
|
|
352
339
|
return processGroupSummary(tools, {
|
|
353
340
|
thinkingCount,
|