@tea-agent/loop-agent 0.43.0-next.17 → 0.43.0-next.3
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 +9 -140
- package/dist/application/task-lifecycle/advance.js +4 -17
- package/dist/application/task-lifecycle/observe.js +29 -43
- package/dist/application/task-lifecycle/plan-transitions.js +4 -5
- package/dist/build-stamp.json +3 -3
- package/dist/commands/client-recovery.js +55 -20
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/task-contract.js +269 -0
- package/dist/executors/dag-pi-executor.js +230 -269
- package/dist/executors/pi-executor.js +0 -2
- package/dist/executors/pi-sdk-executor.js +39 -77
- package/dist/executors/shell-executor.js +2 -3
- package/dist/executors/shell-write-guard.js +32 -27
- package/dist/shared/pi-context-pressure/extension.js +16 -26
- package/dist/shared/pi-retry-settings.js +0 -40
- package/dist/shared/prompts.js +26 -0
- package/dist/task/config.js +2 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/index.js +12 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
- package/dist/task/source-references.js +23 -48
- package/dist/task/source-state.js +1 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/worker/console/chat/assistant-content.js +2 -23
- package/dist/worker/console/chat/explore-tools.js +0 -13
- package/dist/worker/console/chat/pi-runtime.js +1182 -221
- package/dist/worker/console/chat/routes.js +71 -213
- package/dist/worker/console/chat/sdd-data-alignment.js +9 -65
- package/dist/worker/console/chat/session-catalog.js +0 -32
- package/dist/worker/console/chat/session-store.js +6 -22
- package/dist/worker/console/chat/shortcuts.js +0 -6
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/tool-preview.js +0 -11
- package/dist/worker/console/chat/tools.js +0 -6
- package/dist/worker/console/console-update-and-init.js +19 -1
- package/dist/worker/console/console-update-runtime.js +20 -5
- package/dist/worker/console/index.js +20 -0
- package/dist/worker/console/operator-actions.js +1 -13
- package/dist/worker/console/routes.js +0 -8
- package/dist/worker/console/server.js +29 -11
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4NXyedy.js → abnfDiagram-N423BO3Z-hUmM_GRf.js} +1 -1
- package/dist/worker/console/static/assets/{arc-NZ3qXvBI.js → arc-Bt-xuieW.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COZSRvml.js → architectureDiagram-T3A2C74G-DTqkL4DJ.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Ck0Gxi9C.js → blockDiagram-VBNYF7ZC-Cy0MNnzV.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BX5HQ4Ox.js → c4Diagram-5PPSVZJV-DDdlT0Pd.js} +1 -1
- package/dist/worker/console/static/assets/channel-BpzRnZmU.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CoviemSL.js → chunk-2GRJ4B5K-3JawwNZ6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DbOdOkbZ.js → chunk-2Q5K7J3B-DoTp7Cri.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BsG2pmdQ.js → chunk-5RXB4S5H-CcirhZaS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DGdvrNm8.js → chunk-5VM5RSS4-VrG21gnP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BAy7Yd6y.js → chunk-6Q2QTUOP-DDOgIS70.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D9UA7hUb.js → chunk-GF5L2VYU-CBjmgvJ_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DPz7SZ8V.js → chunk-JWPE2WC7-CCQSht8M.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BmuEOIh7.js → chunk-KBJHAD2P-BpGMkUUV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CIhBWnd8.js → chunk-RYQCIY6F-DNDT41QL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CScR6mDD.js → chunk-XXDRQBXY-euXnDFbn.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BhlMVqCc.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BhlMVqCc.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BjpfWaUr.js → cose-bilkent-JH36ORCC-BJ6NAeql.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BFQohJgT.js → cynefin-VYW2F7L2-F5JKIvIq.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D3gzRJnS.js → cynefinDiagram-MW4NZA55-Cwmkl_Gi.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-5eojkhoA.js → dagre-VZM6K2ZE-BmCZ8815.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CpD8LWu_.js → diagram-7IWD3JNH-zWA3hUnX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8_4qJ2a.js → diagram-B4RE2ZJO-BzA-888F.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-HOsXf0eu.js → diagram-LBJQPF4R-tPNheoch.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BqwRwiWP.js → diagram-Q27KOJAE-Cdi7Fyqp.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BRVCNZe1.js → diagram-UB23O5K3-DFK9VBf1.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Cvb1TgGh.js → ebnfDiagram-BXEA7PRR-xCUr1kpg.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DS0O0qG9.js → erDiagram-JOGREHBK-BANQHY_A.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BHIRvRr3.js → flowDiagram-UKHOOZJN-VHJYEMkP.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DcCJ1JWx.js → ganttDiagram-PKOTCBZU-DiyZQ0C6.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-B9ia5ony.js → gitGraphDiagram-DS77QQ5N-CZtIeBBa.js} +1 -1
- package/dist/worker/console/static/assets/index-BlN6ytLr.css +1 -0
- package/dist/worker/console/static/assets/index-CA-jfWlE.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-3ogpgegn.js → infoDiagram-6WML65LV-BuSspC0X.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-4tgpQ5SL.js → ishikawaDiagram-WSZJBQD7-DRJZyPFy.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DNfgHqFx.js → journeyDiagram-NVQOT4AX-DIfXIVuE.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Dz7ngC8z.js → kanban-definition-27J2QSJJ-CT10_7Lt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI7Jv62E.js → linear-ufemj74H.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CQA4Nzmo.js → mermaid.core-DEQe8-_m.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CtsMc8Mf.js → mindmap-definition-FAOFIHXS-BRLUrbrC.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-ByppE9yH.js → pegDiagram-VL7TDLO6-BgibKtiF.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B5PtgFgt.js → pieDiagram-7S7Q4E2Y-D3eyfdV9.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Cq4p0k2K.js → quadrantDiagram-CIZ2JOQS-DXeTk5Px.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DjfkyljN.js → railroadDiagram-AXF67PYL-BgXdFEe7.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DzynNRSl.js → requirementDiagram-LRYGKXZP-CseC2A7R.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cj7TCpTJ.js → sankeyDiagram-W5VNT64P-D80PFzfJ.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C73C6ykA.js → sequenceDiagram-SI44F4Z6-BWzHW7LS.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BiNLgN2x.js → sizeCapture-X5ZJPWSS-xWQYTLAn.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-UwvjS64-.js → stateDiagram-OKZ733FA-CLxtpZeL.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CiVoq4G-.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CC7DH98r.js → swimlanes-SLNWSIFB-DwugtmWV.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BkdgdpfR.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-LSBXTR70.js → timeline-definition-Z64GVDOM-DHEOwHIe.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BAuxWwGP.js → vennDiagram-T6HMQDX7-pmbZKlBK.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y--E5_gjZx.js → wardleyDiagram-T6FBY63Y-CivHoM5q.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-dJWNmyzF.js → xychartDiagram-ELKLHX3M-BfHIE7o6.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +10 -122
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +0 -7
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +15 -35
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +62 -136
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +68 -159
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +56 -75
- package/dist/worker/console/static-src/operator-chat/useComposer.js +78 -206
- package/dist/worker/console/workspace-context.js +1 -21
- package/dist/worker/observe/static/router.js +1 -1
- package/dist/worker/observe/static/styles.css +4 -7
- package/dist/worker/observe/static/views/dag-inspector.js +19 -12
- package/dist/worker/observe/static/views/dag.js +2 -2
- package/dist/worker/scheduler/index.js +25 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/frontend-closeout.js +1 -1
- package/dist/workflows/dag/frontend-design-policy.js +1 -2
- package/dist/workflows/dag/frontend-implementation-contract.js +4 -8
- package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
- package/dist/workflows/dag/frontend-repair.js +76 -55
- package/dist/workflows/dag/frontend-review-context.js +1 -2
- package/dist/workflows/dag/frontend-review-scopes.js +12 -34
- package/dist/workflows/dag/frontend-shape-facts.js +2 -12
- package/dist/workflows/dag/frontend-verification-trace.js +42 -8
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +160 -97
- package/dist/workflows/dynamic/index.js +7 -0
- package/docs/architecture/runtime-boundaries.md +3 -3
- package/docs/templates/frontend-implementation-dag.json +8 -9
- package/package.json +3 -7
- package/skills/frontend-bounded-implement/SKILL.md +3 -4
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/references/contract-protocol.md +1 -3
- package/skills/frontend-design-review/SKILL.md +7 -12
- package/skills/frontend-design-review/references/review-checklist.md +7 -7
- package/skills/frontend-plan/SKILL.md +0 -19
- package/skills/frontend-plan/references/decision-contract.md +19 -26
- package/skills/frontend-review/SKILL.md +18 -12
- package/skills/frontend-review/references/review-findings.md +43 -24
- package/dist/shared/pi-provider-recovery.js +0 -182
- package/dist/worker/console/chat/browser-automation.js +0 -363
- package/dist/worker/console/chat/browser-policy.js +0 -113
- package/dist/worker/console/chat/browser-routes.js +0 -162
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +0 -44
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +0 -89
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +0 -202
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +0 -79
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +0 -86
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +0 -38
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +0 -28
- package/dist/worker/console/chat/pi-runtime/inspection.js +0 -439
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +0 -153
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +0 -5
- package/dist/worker/console/chat/scm-routes.js +0 -217
- package/dist/worker/console/chat/scm-service.js +0 -283
- package/dist/worker/console/chat/scm-tools.js +0 -111
- package/dist/worker/console/chat/session-mode-view.js +0 -49
- package/dist/worker/console/chat/session-mode.js +0 -220
- package/dist/worker/console/chat/terminal-routes.js +0 -216
- package/dist/worker/console/chat/terminal-sessions.js +0 -348
- package/dist/worker/console/chat/terminal-tools.js +0 -211
- package/dist/worker/console/static/assets/channel-BxdTlb_5.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/index-B9goN3S5.css +0 -1
- package/dist/worker/console/static/assets/index-wXlQohNs.js +0 -469
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-nLgMZ0D3.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BcYqTady.js +0 -8
- package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +0 -10
- package/dist/worker/console/static-src/operator-chat/chat-link.js +0 -94
- package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +0 -24
- package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +0 -192
- package/dist/worker/console/static-src/operator-chat/process-label.js +0 -41
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +0 -13
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +0 -10
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +0 -51
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +0 -21
- package/dist/worker/console/workspace-initialization.js +0 -209
- package/dist/workflows/dag/frontend-capacity.js +0 -9
- package/dist/workflows/dag/frontend-plan-completeness.js +0 -186
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +0 -18
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Operator Chat — source control HTTP API (Console UI side).
|
|
3
|
-
*
|
|
4
|
-
* Endpoints (all under /api/operator/v1/chat):
|
|
5
|
-
* GET /scm/status branch + changes + counts
|
|
6
|
-
* GET /scm/diff?path=&staged= unified diff (one path or --stat)
|
|
7
|
-
* POST /scm/stage { paths: string[] } | { all: true }
|
|
8
|
-
* POST /scm/unstage { paths: string[] }
|
|
9
|
-
* POST /scm/discard { paths: string[] } [dangerous]
|
|
10
|
-
* POST /scm/commit { message }
|
|
11
|
-
* GET /scm/log?limit= recent commits
|
|
12
|
-
* GET /scm/branches branch list + current
|
|
13
|
-
* POST /scm/branches { name, create? } switch or create
|
|
14
|
-
* POST /scm/push push to upstream
|
|
15
|
-
* POST /scm/pull pull from upstream
|
|
16
|
-
*
|
|
17
|
-
* Reads are open (same-origin console); every mutation passes gateMutation.
|
|
18
|
-
* The ScmService is workspace-scoped (repoRoot fixed at construction).
|
|
19
|
-
*/
|
|
20
|
-
function sendJson(res, status, body) {
|
|
21
|
-
res.writeHead(status, {
|
|
22
|
-
"content-type": "application/json; charset=utf-8",
|
|
23
|
-
"cache-control": "no-store",
|
|
24
|
-
});
|
|
25
|
-
res.end(JSON.stringify(body));
|
|
26
|
-
}
|
|
27
|
-
function mapScmError(res, error) {
|
|
28
|
-
const status = error.code === "NO_PATHS" || error.code === "EMPTY_MESSAGE" || error.code === "INVALID_NAME" || error.code === "INVALID_HASH"
|
|
29
|
-
? 400
|
|
30
|
-
: error.code === "GIT_TIMEOUT"
|
|
31
|
-
? 504
|
|
32
|
-
: 500;
|
|
33
|
-
sendJson(res, status, {
|
|
34
|
-
ok: false,
|
|
35
|
-
error: { code: error.code, message: error.message, ...(error.stderr ? { stderr: error.stderr } : {}) },
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
async function readJsonBody(req, maxBytes = 256 * 1024) {
|
|
39
|
-
const chunks = [];
|
|
40
|
-
let total = 0;
|
|
41
|
-
for await (const chunk of req) {
|
|
42
|
-
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
43
|
-
total += buf.length;
|
|
44
|
-
if (total > maxBytes)
|
|
45
|
-
throw new Error("request body too large");
|
|
46
|
-
chunks.push(buf);
|
|
47
|
-
}
|
|
48
|
-
if (chunks.length === 0)
|
|
49
|
-
return {};
|
|
50
|
-
const raw = Buffer.concat(chunks).toString("utf8");
|
|
51
|
-
if (!raw.trim())
|
|
52
|
-
return {};
|
|
53
|
-
return JSON.parse(raw);
|
|
54
|
-
}
|
|
55
|
-
function parsePaths(body) {
|
|
56
|
-
if (!Array.isArray(body.paths))
|
|
57
|
-
return [];
|
|
58
|
-
return body.paths
|
|
59
|
-
.filter((p) => typeof p === "string" && p.length > 0 && !p.includes("\0"))
|
|
60
|
-
.slice(0, 500);
|
|
61
|
-
}
|
|
62
|
-
/** Route dispatcher; returns true when the request was handled. */
|
|
63
|
-
export async function handleScmRequest(req, res, deps, pathname) {
|
|
64
|
-
if (!pathname.startsWith("/api/operator/v1/chat/scm"))
|
|
65
|
-
return false;
|
|
66
|
-
const method = (req.method ?? "GET").toUpperCase();
|
|
67
|
-
const rest = pathname.slice("/api/operator/v1/chat/scm".length);
|
|
68
|
-
const parts = rest.split("/").filter(Boolean);
|
|
69
|
-
const sub = parts[0] ?? "";
|
|
70
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
71
|
-
const gateFailed = (gate) => {
|
|
72
|
-
sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
|
|
73
|
-
return true;
|
|
74
|
-
};
|
|
75
|
-
try {
|
|
76
|
-
if (method === "GET" && sub === "status" && parts.length === 1) {
|
|
77
|
-
const r = await deps.scm.status();
|
|
78
|
-
if (!r.ok)
|
|
79
|
-
return mapScmError(res, r), true;
|
|
80
|
-
sendJson(res, 200, { ok: true, data: r });
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
if (method === "GET" && sub === "diff" && parts.length === 1) {
|
|
84
|
-
const path = url.searchParams.get("path") ?? undefined;
|
|
85
|
-
const staged = url.searchParams.get("staged") === "1";
|
|
86
|
-
const r = await deps.scm.diff({ path, staged });
|
|
87
|
-
if (!r.ok)
|
|
88
|
-
return mapScmError(res, r), true;
|
|
89
|
-
sendJson(res, 200, { ok: true, data: r });
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
if (method === "GET" && sub === "commit-diff" && parts.length === 1) {
|
|
93
|
-
const hash = url.searchParams.get("hash") ?? "";
|
|
94
|
-
if (!hash) {
|
|
95
|
-
sendJson(res, 400, { ok: false, error: { code: "INVALID_INPUT", message: "hash is required" } });
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
const r = await deps.scm.commitDiff(hash);
|
|
99
|
-
if (!r.ok)
|
|
100
|
-
return mapScmError(res, r), true;
|
|
101
|
-
sendJson(res, 200, { ok: true, data: r });
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
if (method === "GET" && sub === "untracked-diff" && parts.length === 1) {
|
|
105
|
-
const path = url.searchParams.get("path") ?? "";
|
|
106
|
-
if (!path) {
|
|
107
|
-
sendJson(res, 400, { ok: false, error: { code: "NO_PATHS", message: "path is required" } });
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
const r = await deps.scm.untrackedDiff(path);
|
|
111
|
-
if (!r.ok)
|
|
112
|
-
return mapScmError(res, r), true;
|
|
113
|
-
sendJson(res, 200, { ok: true, data: r });
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
if (method === "GET" && sub === "log" && parts.length === 1) {
|
|
117
|
-
const limit = Number(url.searchParams.get("limit") ?? 30);
|
|
118
|
-
const r = await deps.scm.log(Number.isFinite(limit) ? Math.min(Math.max(limit, 1), 200) : 30);
|
|
119
|
-
if (!r.ok)
|
|
120
|
-
return mapScmError(res, r), true;
|
|
121
|
-
sendJson(res, 200, { ok: true, data: r });
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
if (method === "GET" && sub === "branches" && parts.length === 1) {
|
|
125
|
-
const r = await deps.scm.branchList();
|
|
126
|
-
if (!r.ok)
|
|
127
|
-
return mapScmError(res, r), true;
|
|
128
|
-
sendJson(res, 200, { ok: true, data: r });
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
// Everything below mutates → gate first.
|
|
132
|
-
const gate = deps.gateMutation(req);
|
|
133
|
-
if (!gate.ok)
|
|
134
|
-
return gateFailed(gate);
|
|
135
|
-
if (method === "POST" && sub === "stage" && parts.length === 1) {
|
|
136
|
-
const body = await readJsonBody(req);
|
|
137
|
-
const r = body.all === true
|
|
138
|
-
? await deps.scm.stage([], true)
|
|
139
|
-
: await deps.scm.stage(parsePaths(body));
|
|
140
|
-
if (!r.ok)
|
|
141
|
-
return mapScmError(res, r), true;
|
|
142
|
-
sendJson(res, 200, { ok: true });
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
if (method === "POST" && sub === "unstage" && parts.length === 1) {
|
|
146
|
-
const body = await readJsonBody(req);
|
|
147
|
-
const r = await deps.scm.unstage(parsePaths(body));
|
|
148
|
-
if (!r.ok)
|
|
149
|
-
return mapScmError(res, r), true;
|
|
150
|
-
sendJson(res, 200, { ok: true });
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
if (method === "POST" && sub === "discard" && parts.length === 1) {
|
|
154
|
-
const body = await readJsonBody(req);
|
|
155
|
-
const r = await deps.scm.discard(parsePaths(body));
|
|
156
|
-
if (!r.ok)
|
|
157
|
-
return mapScmError(res, r), true;
|
|
158
|
-
sendJson(res, 200, { ok: true });
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
if (method === "POST" && sub === "commit" && parts.length === 1) {
|
|
162
|
-
const body = await readJsonBody(req);
|
|
163
|
-
const message = typeof body.message === "string" ? body.message : "";
|
|
164
|
-
const r = await deps.scm.commit(message);
|
|
165
|
-
if (!r.ok)
|
|
166
|
-
return mapScmError(res, r), true;
|
|
167
|
-
sendJson(res, 200, { ok: true, data: r });
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
if (method === "POST" && sub === "branches" && parts.length === 1) {
|
|
171
|
-
const body = await readJsonBody(req);
|
|
172
|
-
const name = typeof body.name === "string" ? body.name : "";
|
|
173
|
-
if (!name) {
|
|
174
|
-
sendJson(res, 400, { ok: false, error: { code: "INVALID_INPUT", message: "name is required" } });
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
const r = body.create === true
|
|
178
|
-
? await deps.scm.branchCreate(name)
|
|
179
|
-
: await deps.scm.branchSwitch(name);
|
|
180
|
-
if (!r.ok)
|
|
181
|
-
return mapScmError(res, r), true;
|
|
182
|
-
sendJson(res, 200, { ok: true });
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
if (method === "POST" && sub === "push" && parts.length === 1) {
|
|
186
|
-
const r = await deps.scm.push();
|
|
187
|
-
if (!r.ok)
|
|
188
|
-
return mapScmError(res, r), true;
|
|
189
|
-
sendJson(res, 200, { ok: true, data: r });
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
if (method === "POST" && sub === "pull" && parts.length === 1) {
|
|
193
|
-
const r = await deps.scm.pull();
|
|
194
|
-
if (!r.ok)
|
|
195
|
-
return mapScmError(res, r), true;
|
|
196
|
-
sendJson(res, 200, { ok: true, data: r });
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
sendJson(res, 404, { ok: false, error: { code: "NOT_FOUND", message: `unknown scm route: ${method} ${pathname}` } });
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
202
|
-
catch (error) {
|
|
203
|
-
if (!res.headersSent) {
|
|
204
|
-
sendJson(res, 500, {
|
|
205
|
-
ok: false,
|
|
206
|
-
error: {
|
|
207
|
-
code: "SCM_INTERNAL",
|
|
208
|
-
message: error instanceof Error ? error.message : String(error),
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
res.end();
|
|
214
|
-
}
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Operator Chat — source control service (DSH-style SCM).
|
|
3
|
-
*
|
|
4
|
-
* Workspace-scoped git operations shared by the Console UI panel and the
|
|
5
|
-
* model-facing tools:
|
|
6
|
-
* - status: porcelain + branch + ahead/behind, parsed into typed changes
|
|
7
|
-
* - diff: working tree / staged, per-path or whole tree (bounded)
|
|
8
|
-
* - stage / unstage / discard (path-scoped)
|
|
9
|
-
* - commit (message required), log (bounded), branch list/switch/create
|
|
10
|
-
* - push / pull (remote ops, explicit user/model intent only)
|
|
11
|
-
*
|
|
12
|
-
* All git invocations are fixed-argv execFile (no shell), bounded output and
|
|
13
|
-
* timeout, scoped to the workspace repoRoot. Failures return typed errors
|
|
14
|
-
* instead of throwing raw.
|
|
15
|
-
*/
|
|
16
|
-
import { execFile } from "node:child_process";
|
|
17
|
-
import { promisify } from "node:util";
|
|
18
|
-
const execFileAsync = promisify(execFile);
|
|
19
|
-
const GIT_TIMEOUT_MS = 20_000;
|
|
20
|
-
const MAX_OUTPUT_CHARS = 200_000;
|
|
21
|
-
function gitEnv() {
|
|
22
|
-
return {
|
|
23
|
-
...process.env,
|
|
24
|
-
// Deterministic porcelain output regardless of user locale config.
|
|
25
|
-
GIT_PAGER: "cat",
|
|
26
|
-
LC_ALL: "C",
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
export class ScmService {
|
|
30
|
-
repoRoot;
|
|
31
|
-
constructor(repoRoot) {
|
|
32
|
-
this.repoRoot = repoRoot;
|
|
33
|
-
}
|
|
34
|
-
async git(args, opts) {
|
|
35
|
-
try {
|
|
36
|
-
const { stdout } = await execFileAsync("git", args, {
|
|
37
|
-
cwd: this.repoRoot,
|
|
38
|
-
encoding: "utf8",
|
|
39
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
40
|
-
timeout: opts?.timeoutMs ?? GIT_TIMEOUT_MS,
|
|
41
|
-
windowsHide: true,
|
|
42
|
-
env: gitEnv(),
|
|
43
|
-
});
|
|
44
|
-
const cap = opts?.maxOutputChars ?? MAX_OUTPUT_CHARS;
|
|
45
|
-
return { ok: true, stdout: stdout.length > cap ? stdout.slice(0, cap) : stdout };
|
|
46
|
-
}
|
|
47
|
-
catch (e) {
|
|
48
|
-
const err = e;
|
|
49
|
-
return {
|
|
50
|
-
ok: false,
|
|
51
|
-
code: err.killed ? "GIT_TIMEOUT" : "GIT_FAILED",
|
|
52
|
-
message: err.killed
|
|
53
|
-
? `git ${args[0]} timed out`
|
|
54
|
-
: err.message ?? String(e),
|
|
55
|
-
stderr: typeof err.stderr === "string" ? err.stderr.slice(0, 2000) : undefined,
|
|
56
|
-
// `git diff --no-index` exits 1 when a diff exists; callers that
|
|
57
|
-
// expect that carry the partial stdout through.
|
|
58
|
-
stdout: typeof err.stdout === "string" ? err.stdout : undefined,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/** `git rev-parse --is-inside-work-tree` — cheap repo probe. */
|
|
63
|
-
async isRepo() {
|
|
64
|
-
const r = await this.git(["rev-parse", "--is-inside-work-tree"], { timeoutMs: 5_000 });
|
|
65
|
-
return r.ok && r.stdout.trim() === "true";
|
|
66
|
-
}
|
|
67
|
-
async status() {
|
|
68
|
-
// -uall: expand untracked directories to individual files (VS Code style);
|
|
69
|
-
// renames surface as R rows (with fromPath) only when git's rename
|
|
70
|
-
// detection is active, which is the default for status since git 2.9.
|
|
71
|
-
const porcelain = await this.git([
|
|
72
|
-
"status", "--porcelain=v1", "-z", "--branch", "-uall",
|
|
73
|
-
]);
|
|
74
|
-
if (!porcelain.ok)
|
|
75
|
-
return porcelain;
|
|
76
|
-
const changes = [];
|
|
77
|
-
// porcelain v1 -z: NUL-separated fields. Header is
|
|
78
|
-
// "## branch...upstream [ahead N, behind M]"; status rows "XY path";
|
|
79
|
-
// rename/copy rows "XY newpath" followed by a bare "oldpath" field.
|
|
80
|
-
const fields = porcelain.stdout.split("\0").filter((s) => s !== "");
|
|
81
|
-
let branch = "";
|
|
82
|
-
let upstream;
|
|
83
|
-
let ahead;
|
|
84
|
-
let behind;
|
|
85
|
-
for (const field of fields) {
|
|
86
|
-
if (field.startsWith("## ")) {
|
|
87
|
-
const header = field.slice(3);
|
|
88
|
-
const track = header.split(/\s+/)[0] ?? "";
|
|
89
|
-
const dot = track.indexOf("...");
|
|
90
|
-
if (dot >= 0) {
|
|
91
|
-
branch = track.slice(0, dot);
|
|
92
|
-
upstream = track.slice(dot + 3);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
branch = track;
|
|
96
|
-
}
|
|
97
|
-
const aheadM = /\[ahead (\d+)/.exec(header);
|
|
98
|
-
const behindM = /behind (\d+)\]/.exec(header);
|
|
99
|
-
if (aheadM)
|
|
100
|
-
ahead = Number(aheadM[1]);
|
|
101
|
-
if (behindM)
|
|
102
|
-
behind = Number(behindM[1]);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (field.startsWith("#"))
|
|
106
|
-
continue;
|
|
107
|
-
// Bare path field: from-path of the previous rename/copy row.
|
|
108
|
-
const prev = changes[changes.length - 1];
|
|
109
|
-
if (prev && prev.xy.match(/[RC]/) && !prev.fromPath && !/^[ MADRCU?!]{1,2} /.test(field)) {
|
|
110
|
-
prev.fromPath = field;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
const xy = field.slice(0, 2);
|
|
114
|
-
const path = field.slice(3);
|
|
115
|
-
changes.push({
|
|
116
|
-
path,
|
|
117
|
-
xy,
|
|
118
|
-
staged: xy[0] !== " " && xy[0] !== "?" && xy[0] !== "!",
|
|
119
|
-
unstaged: xy[1] !== " " && xy[1] !== "?",
|
|
120
|
-
untracked: xy === "??",
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
const head = await this.git(["rev-parse", "--short", "HEAD"], { timeoutMs: 5_000 });
|
|
124
|
-
return {
|
|
125
|
-
ok: true,
|
|
126
|
-
repoRoot: this.repoRoot,
|
|
127
|
-
branch,
|
|
128
|
-
head: head.ok ? head.stdout.trim() : "",
|
|
129
|
-
upstream,
|
|
130
|
-
ahead,
|
|
131
|
-
behind,
|
|
132
|
-
changes,
|
|
133
|
-
stagedCount: changes.filter((c) => c.staged).length,
|
|
134
|
-
unstagedCount: changes.filter((c) => c.unstaged).length,
|
|
135
|
-
untrackedCount: changes.filter((c) => c.untracked).length,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
/** Unified diff for one path (or whole tree when path omitted). */
|
|
139
|
-
async diff(options) {
|
|
140
|
-
const args = ["diff"];
|
|
141
|
-
if (options.staged)
|
|
142
|
-
args.push("--cached");
|
|
143
|
-
args.push(`-U${options.contextLines ?? 3}`);
|
|
144
|
-
if (options.path) {
|
|
145
|
-
args.push("--", options.path);
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
args.push("--stat");
|
|
149
|
-
}
|
|
150
|
-
const r = await this.git(args);
|
|
151
|
-
if (!r.ok)
|
|
152
|
-
return r;
|
|
153
|
-
return { ok: true, diff: r.stdout };
|
|
154
|
-
}
|
|
155
|
-
/** Full patch of one commit (`git show` without the commit header). */
|
|
156
|
-
async commitDiff(hash) {
|
|
157
|
-
if (!/^[0-9a-f]{4,40}$/i.test(hash)) {
|
|
158
|
-
return { ok: false, code: "INVALID_HASH", message: "invalid commit hash" };
|
|
159
|
-
}
|
|
160
|
-
const r = await this.git(["show", "--format=", "--no-color", hash]);
|
|
161
|
-
if (!r.ok)
|
|
162
|
-
return r;
|
|
163
|
-
return { ok: true, diff: r.stdout };
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Untracked-file content as a full-addition diff (`git diff --no-index
|
|
167
|
-
* /dev/null <path>`): git diff never lists untracked files, and this
|
|
168
|
-
* renders them like any other change in the diff view.
|
|
169
|
-
*/
|
|
170
|
-
async untrackedDiff(path) {
|
|
171
|
-
if (path.includes("\0") || path.includes("..")) {
|
|
172
|
-
return { ok: false, code: "NO_PATHS", message: "invalid path" };
|
|
173
|
-
}
|
|
174
|
-
const r = await this.git(["diff", "--no-index", "--no-color", "-U3", "--", "/dev/null", path]);
|
|
175
|
-
// `git diff --no-index` exits 1 when there IS a diff — carry stdout.
|
|
176
|
-
if (!r.ok && r.code === "GIT_FAILED")
|
|
177
|
-
return { ok: true, diff: r.stdout ?? "" };
|
|
178
|
-
if (!r.ok)
|
|
179
|
-
return r;
|
|
180
|
-
return { ok: true, diff: r.stdout };
|
|
181
|
-
}
|
|
182
|
-
/** Stage paths (`git add --` / `git add -A --` for "."). */
|
|
183
|
-
async stage(paths, all = false) {
|
|
184
|
-
if (!all && paths.length === 0) {
|
|
185
|
-
return { ok: false, code: "NO_PATHS", message: "stage requires paths or all=true" };
|
|
186
|
-
}
|
|
187
|
-
const args = ["add"];
|
|
188
|
-
if (all) {
|
|
189
|
-
args.push("-A");
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
args.push("--");
|
|
193
|
-
args.push(...paths);
|
|
194
|
-
}
|
|
195
|
-
return this.git(args);
|
|
196
|
-
}
|
|
197
|
-
/** Unstage paths (`git reset HEAD --`). */
|
|
198
|
-
async unstage(paths) {
|
|
199
|
-
if (paths.length === 0) {
|
|
200
|
-
return { ok: false, code: "NO_PATHS", message: "unstage requires paths" };
|
|
201
|
-
}
|
|
202
|
-
return this.git(["reset", "HEAD", "--", ...paths]);
|
|
203
|
-
}
|
|
204
|
-
/** Discard worktree changes for paths (`git checkout --`). DANGEROUS. */
|
|
205
|
-
async discard(paths) {
|
|
206
|
-
if (paths.length === 0) {
|
|
207
|
-
return { ok: false, code: "NO_PATHS", message: "discard requires paths" };
|
|
208
|
-
}
|
|
209
|
-
return this.git(["checkout", "--", ...paths]);
|
|
210
|
-
}
|
|
211
|
-
async commit(message) {
|
|
212
|
-
const trimmed = message.trim();
|
|
213
|
-
if (!trimmed) {
|
|
214
|
-
return { ok: false, code: "EMPTY_MESSAGE", message: "commit message required" };
|
|
215
|
-
}
|
|
216
|
-
const r = await this.git(["commit", "-m", trimmed]);
|
|
217
|
-
if (!r.ok)
|
|
218
|
-
return r;
|
|
219
|
-
const sha = await this.git(["rev-parse", "--short", "HEAD"], { timeoutMs: 5_000 });
|
|
220
|
-
return { ok: true, commit: sha.ok ? sha.stdout.trim() : "" };
|
|
221
|
-
}
|
|
222
|
-
async log(limit = 30) {
|
|
223
|
-
const r = await this.git([
|
|
224
|
-
"log",
|
|
225
|
-
`--max-count=${limit}`,
|
|
226
|
-
"--pretty=format:%H\u0001%h\u0001%s\u0001%an\u0001%aI\u0001%D",
|
|
227
|
-
]);
|
|
228
|
-
if (!r.ok)
|
|
229
|
-
return r;
|
|
230
|
-
const commits = [];
|
|
231
|
-
for (const line of r.stdout.split("\n")) {
|
|
232
|
-
if (!line.trim())
|
|
233
|
-
continue;
|
|
234
|
-
const [sha, short, subject, author, date, refs] = line.split("\u0001");
|
|
235
|
-
if (!sha)
|
|
236
|
-
continue;
|
|
237
|
-
commits.push({
|
|
238
|
-
sha: short ?? sha,
|
|
239
|
-
subject: subject ?? "",
|
|
240
|
-
author: author ?? "",
|
|
241
|
-
date: date ?? "",
|
|
242
|
-
refs: refs?.trim() || undefined,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
return { ok: true, commits };
|
|
246
|
-
}
|
|
247
|
-
async branchList() {
|
|
248
|
-
const r = await this.git(["branch", "--list", "--format=%(HEAD)%02%(refname:short)"]);
|
|
249
|
-
if (!r.ok)
|
|
250
|
-
return r;
|
|
251
|
-
const branches = [];
|
|
252
|
-
for (const line of r.stdout.split("\n")) {
|
|
253
|
-
if (!line.trim())
|
|
254
|
-
continue;
|
|
255
|
-
const [headMark, name] = line.split("\x02");
|
|
256
|
-
if (!name)
|
|
257
|
-
continue;
|
|
258
|
-
branches.push({ name: name.trim(), current: headMark?.trim() === "*" });
|
|
259
|
-
}
|
|
260
|
-
const current = branches.find((b) => b.current)?.name ?? "";
|
|
261
|
-
return { ok: true, current, branches };
|
|
262
|
-
}
|
|
263
|
-
async branchCreate(name) {
|
|
264
|
-
if (!/^[\w.\-/]+$/.test(name)) {
|
|
265
|
-
return { ok: false, code: "INVALID_NAME", message: `invalid branch name: ${name}` };
|
|
266
|
-
}
|
|
267
|
-
return this.git(["checkout", "-b", name]);
|
|
268
|
-
}
|
|
269
|
-
async branchSwitch(name) {
|
|
270
|
-
if (!/^[\w.\-/]+$/.test(name)) {
|
|
271
|
-
return { ok: false, code: "INVALID_NAME", message: `invalid branch name: ${name}` };
|
|
272
|
-
}
|
|
273
|
-
return this.git(["checkout", name]);
|
|
274
|
-
}
|
|
275
|
-
async push() {
|
|
276
|
-
const r = await this.git(["push"], { timeoutMs: 120_000 });
|
|
277
|
-
return r.ok ? { ok: true, output: r.stdout } : r;
|
|
278
|
-
}
|
|
279
|
-
async pull() {
|
|
280
|
-
const r = await this.git(["pull", "--no-edit"], { timeoutMs: 120_000 });
|
|
281
|
-
return r.ok ? { ok: true, output: r.stdout } : r;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Operator Chat — model-facing source control tool (DSH-style SCM).
|
|
3
|
-
*
|
|
4
|
-
* Registers `scm` (single tool, multi-action) so the model can inspect and
|
|
5
|
-
* drive git in the workspace repo: status / diff / stage / unstage / commit /
|
|
6
|
-
* log / branch / push / pull. Read actions are cheap and safe; write actions
|
|
7
|
-
* (commit/push) are the model's explicit intent, mirroring DSH's SCM tool.
|
|
8
|
-
*
|
|
9
|
-
* All operations go through ScmService (fixed-argv git, no shell, bounded),
|
|
10
|
-
* scoped to the workspace repoRoot.
|
|
11
|
-
*/
|
|
12
|
-
const TOOL_DESCRIPTION = "Inspect and drive git source control in the workspace repo. " +
|
|
13
|
-
"Actions: 'status' (branch + working-tree changes), 'diff' (one path or whole tree; staged=1 for the index), " +
|
|
14
|
-
"'stage'/'unstage' (paths array; all=true stages everything), 'discard' (DESTRUCTIVE: revert worktree changes of paths), " +
|
|
15
|
-
"'commit' (message required), 'log' (recent commits), 'branch' (list; name+create to create/switch), 'push', 'pull'. " +
|
|
16
|
-
"Use status/diff before proposing commits; prefer committing focused changesets. push/pull contact the remote — call them only when the user asks.";
|
|
17
|
-
export async function buildScmTools(deps) {
|
|
18
|
-
const [{ Type }, { defineTool }] = await Promise.all([
|
|
19
|
-
import("typebox"),
|
|
20
|
-
import("@earendil-works/pi-coding-agent"),
|
|
21
|
-
]);
|
|
22
|
-
function result(body) {
|
|
23
|
-
return { content: [{ type: "text", text: JSON.stringify(body) }], details: body };
|
|
24
|
-
}
|
|
25
|
-
function errText(e) {
|
|
26
|
-
return JSON.stringify({ ok: false, code: e.code, message: e.message, ...(e.stderr ? { stderr: e.stderr } : {}) });
|
|
27
|
-
}
|
|
28
|
-
const tool = defineTool({
|
|
29
|
-
name: "scm",
|
|
30
|
-
label: "SCM",
|
|
31
|
-
description: TOOL_DESCRIPTION,
|
|
32
|
-
promptSnippet: "scm: git source control (status/diff/stage/unstage/discard/commit/log/branch/push/pull) for the workspace repo.",
|
|
33
|
-
parameters: Type.Object({
|
|
34
|
-
action: Type.Union([
|
|
35
|
-
Type.Literal("status"),
|
|
36
|
-
Type.Literal("diff"),
|
|
37
|
-
Type.Literal("stage"),
|
|
38
|
-
Type.Literal("unstage"),
|
|
39
|
-
Type.Literal("discard"),
|
|
40
|
-
Type.Literal("commit"),
|
|
41
|
-
Type.Literal("log"),
|
|
42
|
-
Type.Literal("branch"),
|
|
43
|
-
Type.Literal("push"),
|
|
44
|
-
Type.Literal("pull"),
|
|
45
|
-
], { description: "Which git operation to run." }),
|
|
46
|
-
path: Type.Optional(Type.String({ description: "For diff: repo-relative path (default whole tree --stat)." })),
|
|
47
|
-
staged: Type.Optional(Type.Boolean({ description: "For diff: diff the staging area (index) instead of the worktree." })),
|
|
48
|
-
paths: Type.Optional(Type.Array(Type.String(), { description: "For stage/unstage/discard: repo-relative paths." })),
|
|
49
|
-
all: Type.Optional(Type.Boolean({ description: "For stage: stage all changes (git add -A)." })),
|
|
50
|
-
message: Type.Optional(Type.String({ description: "For commit: commit message (required)." })),
|
|
51
|
-
limit: Type.Optional(Type.Number({ description: "For log: max commits (default 30)." })),
|
|
52
|
-
name: Type.Optional(Type.String({ description: "For branch: branch name to create or switch to." })),
|
|
53
|
-
create: Type.Optional(Type.Boolean({ description: "For branch: create the branch instead of switching." })),
|
|
54
|
-
}, { additionalProperties: false }),
|
|
55
|
-
async execute(_toolCallId, params) {
|
|
56
|
-
const paths = Array.isArray(params.paths) ? params.paths.slice(0, 500) : [];
|
|
57
|
-
switch (params.action) {
|
|
58
|
-
case "status":
|
|
59
|
-
return result(await deps.scm.status());
|
|
60
|
-
case "diff": {
|
|
61
|
-
const r = await deps.scm.diff({
|
|
62
|
-
...(params.path ? { path: params.path } : {}),
|
|
63
|
-
...(params.staged ? { staged: true } : {}),
|
|
64
|
-
});
|
|
65
|
-
return r.ok ? result(r) : result({ error: errText(r) });
|
|
66
|
-
}
|
|
67
|
-
case "stage": {
|
|
68
|
-
const r = params.all === true ? await deps.scm.stage([], true) : await deps.scm.stage(paths);
|
|
69
|
-
return r.ok ? result({ ok: true, staged: params.all === true ? "all" : paths }) : result({ error: errText(r) });
|
|
70
|
-
}
|
|
71
|
-
case "unstage": {
|
|
72
|
-
const r = await deps.scm.unstage(paths);
|
|
73
|
-
return r.ok ? result({ ok: true, unstaged: paths }) : result({ error: errText(r) });
|
|
74
|
-
}
|
|
75
|
-
case "discard": {
|
|
76
|
-
const r = await deps.scm.discard(paths);
|
|
77
|
-
return r.ok ? result({ ok: true, discarded: paths }) : result({ error: errText(r) });
|
|
78
|
-
}
|
|
79
|
-
case "commit": {
|
|
80
|
-
const r = await deps.scm.commit(params.message ?? "");
|
|
81
|
-
return r.ok ? result(r) : result({ error: errText(r) });
|
|
82
|
-
}
|
|
83
|
-
case "log": {
|
|
84
|
-
const r = await deps.scm.log(params.limit ?? 30);
|
|
85
|
-
return r.ok ? result(r) : result({ error: errText(r) });
|
|
86
|
-
}
|
|
87
|
-
case "branch": {
|
|
88
|
-
if (params.name) {
|
|
89
|
-
const r = params.create === true
|
|
90
|
-
? await deps.scm.branchCreate(params.name)
|
|
91
|
-
: await deps.scm.branchSwitch(params.name);
|
|
92
|
-
return r.ok ? result({ ok: true, ...(params.create === true ? { created: params.name } : { switched: params.name }) }) : result({ error: errText(r) });
|
|
93
|
-
}
|
|
94
|
-
const r = await deps.scm.branchList();
|
|
95
|
-
return r.ok ? result(r) : result({ error: errText(r) });
|
|
96
|
-
}
|
|
97
|
-
case "push": {
|
|
98
|
-
const r = await deps.scm.push();
|
|
99
|
-
return r.ok ? result(r) : result({ error: errText(r) });
|
|
100
|
-
}
|
|
101
|
-
case "pull": {
|
|
102
|
-
const r = await deps.scm.pull();
|
|
103
|
-
return r.ok ? result(r) : result({ error: errText(r) });
|
|
104
|
-
}
|
|
105
|
-
default:
|
|
106
|
-
return result({ error: `unknown action: ${params.action}` });
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
return [tool];
|
|
111
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-safe session-mode types and copy.
|
|
3
|
-
*
|
|
4
|
-
* Keep this module free of Node/runtime tool policy so the Console UI
|
|
5
|
-
* bundle can import it without pulling operator registries.
|
|
6
|
-
*/
|
|
7
|
-
export const CONSOLE_CHAT_SESSION_MODES = ["workflow", "pi"];
|
|
8
|
-
export const DEFAULT_CONSOLE_CHAT_SESSION_MODE = "workflow";
|
|
9
|
-
export const SESSION_MODE_COPY = {
|
|
10
|
-
workflow: {
|
|
11
|
-
id: "workflow",
|
|
12
|
-
label: "工作流",
|
|
13
|
-
shortLabel: "工作流",
|
|
14
|
-
description: "规划并推进工作流",
|
|
15
|
-
createLabel: "新建工作流会话",
|
|
16
|
-
bannerTitle: "工作流",
|
|
17
|
-
bannerBody: "用自然语言编排 loop-agent / agent-worker 的全部能力。这是 Operator 面:默认具备完整 Pi 工具与受策略约束的 operator action。软约束:大改与高风险变更优先走受治 DAG。",
|
|
18
|
-
landingTitle: "开始一段新的对话",
|
|
19
|
-
landingBody: "用自然语言编排 loop-agent:查询状态、诊断问题、创建与恢复任务。可先点选下方建议,或在输入框描述目标。工作流或 Pi 在输入框上方选择。",
|
|
20
|
-
emptyTitle: "今天想推进什么?",
|
|
21
|
-
emptyBody: "会话已就绪。输入框上方可切换工作区或工作流 / Pi。点选建议快速开场,或直接描述目标。业务写入仍经受治理 DAG。",
|
|
22
|
-
},
|
|
23
|
-
pi: {
|
|
24
|
-
id: "pi",
|
|
25
|
-
label: "Pi",
|
|
26
|
-
shortLabel: "Pi",
|
|
27
|
-
description: "保留压缩与 Explore,直接协作编码",
|
|
28
|
-
createLabel: "新建 Pi 会话",
|
|
29
|
-
bannerTitle: "Pi",
|
|
30
|
-
bannerBody: "保留 Console 压缩、Explore 与持续目标,直接协作编码。不注入工作流业务指令与业务工具。",
|
|
31
|
-
landingTitle: "开始一段 Pi 协作",
|
|
32
|
-
landingBody: "Pi 模式保留压缩、Explore、持续目标、认证与 SDD 接入,取消工作流业务注入。适合有人监督的直接编码与调试。",
|
|
33
|
-
emptyTitle: "直接协作编码",
|
|
34
|
-
emptyBody: "会话已就绪。输入框上方可切换工作区或模式。使用 Pi 工具读写仓库;Explore 负责只读深挖。本会话不加载任务 / DAG 工作流工具。",
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
/** Slash actions that only exist in workflow sessions. Browser-safe: pure
|
|
38
|
-
* string constants, no runtime/tool policy imports. `/goal` was moved to
|
|
39
|
-
* shared on 2026-09-10 (exec-plan 2026-09-10-console-chat-goal-for-pi-mode);
|
|
40
|
-
* interview/panel navigation remain workflow-only. */
|
|
41
|
-
export const WORKFLOW_EXCLUSIVE_SLASH_ACTIONS = Object.freeze([
|
|
42
|
-
"focus-panel:task",
|
|
43
|
-
"focus-panel:contract",
|
|
44
|
-
"focus-panel:dag",
|
|
45
|
-
"focus:interview",
|
|
46
|
-
]);
|
|
47
|
-
export function isWorkflowExclusiveSlashAction(action) {
|
|
48
|
-
return WORKFLOW_EXCLUSIVE_SLASH_ACTIONS.includes(action);
|
|
49
|
-
}
|