@tea-agent/loop-agent 0.43.0-next.4 → 0.43.0-next.5
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 +80 -9
- package/dist/application/task-lifecycle/advance.js +17 -4
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +244 -33
- package/dist/executors/pi-executor.js +2 -0
- package/dist/executors/pi-sdk-executor.js +75 -39
- package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/pi-runtime.js +113 -95
- package/dist/worker/console/chat/routes.js +164 -54
- package/dist/worker/console/chat/scm-routes.js +193 -0
- package/dist/worker/console/chat/scm-service.js +253 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/session-catalog.js +1 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +22 -6
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/tools.js +1 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CCj36aaC.js → abnfDiagram-N423BO3Z-BuyrlONK.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Cl3MIVFh.js → arc-BvCl_wpi.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C_n4kMJS.js → architectureDiagram-T3A2C74G-BkBpBaRq.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DlWYJ7cK.js → blockDiagram-VBNYF7ZC-yve4Mcb_.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ud5pS7vS.js → c4Diagram-5PPSVZJV-9yLGboBx.js} +1 -1
- package/dist/worker/console/static/assets/channel-BRahIhjw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CQPwOv_j.js → chunk-2GRJ4B5K-BKP69b8r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CMzjxrWI.js → chunk-2Q5K7J3B-NWFRnXju.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-pcnXnD6t.js → chunk-5RXB4S5H-BiweqLEc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CEB3AvU2.js → chunk-5VM5RSS4-CP9GHmRY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DSKnbXbp.js → chunk-6Q2QTUOP-BiGDkBtj.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CS6-PjCZ.js → chunk-GF5L2VYU-DkJ4ZjQS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-f6q8Tnzz.js → chunk-JWPE2WC7-B5jjn7zq.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DkB0QX3q.js → chunk-KBJHAD2P-C6KoK9O0.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BdB4jAv6.js → chunk-RYQCIY6F-BvNIfyn-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVm1bnX6.js → chunk-XXDRQBXY-BZ33iX4W.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GOC80mhq.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GOC80mhq.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-FV_P-IEp.js → cose-bilkent-JH36ORCC-CP8eIuZd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BM7RtkA2.js → cynefin-VYW2F7L2-DZECjpOe.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C1BZwTG5.js → cynefinDiagram-MW4NZA55-CvVJoFqf.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C6McNKmY.js → dagre-VZM6K2ZE-D_JTqdAn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-C0aWRUaE.js → diagram-7IWD3JNH-Db7AQ0z0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bfk_SKE8.js → diagram-B4RE2ZJO-AZPVo5CZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-prx1T4qf.js → diagram-LBJQPF4R-BgXqI3E6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DBvty0eU.js → diagram-Q27KOJAE-BYjFn3Ba.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CF0em8e9.js → diagram-UB23O5K3-B4rJgGGO.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CF90b4Ws.js → ebnfDiagram-BXEA7PRR-C6XWZhpw.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-hfunxzof.js → erDiagram-JOGREHBK-1YdQRIQW.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D-QSy4Nl.js → flowDiagram-UKHOOZJN-l3a0AwN0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DaBGNcvT.js → ganttDiagram-PKOTCBZU-CQL96F2Z.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BChcubGL.js → gitGraphDiagram-DS77QQ5N-BxCvafRb.js} +1 -1
- package/dist/worker/console/static/assets/index-4wCGsXuU.js +465 -0
- package/dist/worker/console/static/assets/index-CiF9WBoR.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BkC67fIa.js → infoDiagram-6WML65LV-BUipIeDO.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-ibhLczCD.js → ishikawaDiagram-WSZJBQD7-CRfomVMX.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DCRMitay.js → journeyDiagram-NVQOT4AX-_UFvyviX.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DJ_hKaYI.js → kanban-definition-27J2QSJJ-CKIRoqKv.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CIlLsYsA.js → linear-CjttJDCM.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-C-5IQMP_.js → mermaid.core-DPvHX155.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-TCzV5TQT.js → mindmap-definition-FAOFIHXS-Qmj-qwBe.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-bfhd2BQH.js → pegDiagram-VL7TDLO6-Auw6DRgq.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BxFRAm5N.js → pieDiagram-7S7Q4E2Y-CqMrfD4c.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C-m12lxv.js → quadrantDiagram-CIZ2JOQS-BAddeggH.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CwmLiGgx.js → railroadDiagram-AXF67PYL-DG1JuOu2.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-oc0Q1jiD.js → requirementDiagram-LRYGKXZP-DLYGMKlC.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-9Ght6Yur.js → sankeyDiagram-W5VNT64P-g68SbLM9.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-ndm3lyz9.js → sequenceDiagram-SI44F4Z6-CmszLKv8.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7sPlKVcw.js → sizeCapture-X5ZJPWSS-D2tEKUCj.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Izl21-oS.js → stateDiagram-OKZ733FA-Syc5E_Jf.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CwWwuPI-.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CtO3D65o.js → swimlanes-SLNWSIFB-Ibn0IRCa.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-A7fqemr6.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cyg5cY7z.js → timeline-definition-Z64GVDOM-2ZW9W3iS.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CwtE_TFK.js → vennDiagram-T6HMQDX7-v50HsKUh.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Dtt9v8OO.js → wardleyDiagram-T6FBY63Y-DqCXEoDH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B2Y3ryb6.js → xychartDiagram-ELKLHX3M-vIP8Vo9d.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +20 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/workspace-context.js +6 -0
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-design-policy.js +2 -1
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/package.json +1 -1
- package/skills/frontend-plan/SKILL.md +7 -0
- package/dist/worker/console/static/assets/channel-CEMRpNm-.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C9lhaZkR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C9lhaZkR.js +0 -1
- package/dist/worker/console/static/assets/index-CJkCY1XR.js +0 -464
- package/dist/worker/console/static/assets/index-DUBCbiqv.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CmElLFpP.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DKRbGG8V.js +0 -8
|
@@ -0,0 +1,253 @@
|
|
|
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
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** `git rev-parse --is-inside-work-tree` — cheap repo probe. */
|
|
60
|
+
async isRepo() {
|
|
61
|
+
const r = await this.git(["rev-parse", "--is-inside-work-tree"], { timeoutMs: 5_000 });
|
|
62
|
+
return r.ok && r.stdout.trim() === "true";
|
|
63
|
+
}
|
|
64
|
+
async status() {
|
|
65
|
+
// -uall: expand untracked directories to individual files (VS Code style);
|
|
66
|
+
// renames surface as R rows (with fromPath) only when git's rename
|
|
67
|
+
// detection is active, which is the default for status since git 2.9.
|
|
68
|
+
const porcelain = await this.git([
|
|
69
|
+
"status", "--porcelain=v1", "-z", "--branch", "-uall",
|
|
70
|
+
]);
|
|
71
|
+
if (!porcelain.ok)
|
|
72
|
+
return porcelain;
|
|
73
|
+
const changes = [];
|
|
74
|
+
// porcelain v1 -z: NUL-separated fields. Header is
|
|
75
|
+
// "## branch...upstream [ahead N, behind M]"; status rows "XY path";
|
|
76
|
+
// rename/copy rows "XY newpath" followed by a bare "oldpath" field.
|
|
77
|
+
const fields = porcelain.stdout.split("\0").filter((s) => s !== "");
|
|
78
|
+
let branch = "";
|
|
79
|
+
let upstream;
|
|
80
|
+
let ahead;
|
|
81
|
+
let behind;
|
|
82
|
+
for (const field of fields) {
|
|
83
|
+
if (field.startsWith("## ")) {
|
|
84
|
+
const header = field.slice(3);
|
|
85
|
+
const track = header.split(/\s+/)[0] ?? "";
|
|
86
|
+
const dot = track.indexOf("...");
|
|
87
|
+
if (dot >= 0) {
|
|
88
|
+
branch = track.slice(0, dot);
|
|
89
|
+
upstream = track.slice(dot + 3);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
branch = track;
|
|
93
|
+
}
|
|
94
|
+
const aheadM = /\[ahead (\d+)/.exec(header);
|
|
95
|
+
const behindM = /behind (\d+)\]/.exec(header);
|
|
96
|
+
if (aheadM)
|
|
97
|
+
ahead = Number(aheadM[1]);
|
|
98
|
+
if (behindM)
|
|
99
|
+
behind = Number(behindM[1]);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (field.startsWith("#"))
|
|
103
|
+
continue;
|
|
104
|
+
// Bare path field: from-path of the previous rename/copy row.
|
|
105
|
+
const prev = changes[changes.length - 1];
|
|
106
|
+
if (prev && prev.xy.match(/[RC]/) && !prev.fromPath && !/^[ MADRCU?!]{1,2} /.test(field)) {
|
|
107
|
+
prev.fromPath = field;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const xy = field.slice(0, 2);
|
|
111
|
+
const path = field.slice(3);
|
|
112
|
+
changes.push({
|
|
113
|
+
path,
|
|
114
|
+
xy,
|
|
115
|
+
staged: xy[0] !== " " && xy[0] !== "?" && xy[0] !== "!",
|
|
116
|
+
unstaged: xy[1] !== " " && xy[1] !== "?",
|
|
117
|
+
untracked: xy === "??",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const head = await this.git(["rev-parse", "--short", "HEAD"], { timeoutMs: 5_000 });
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
repoRoot: this.repoRoot,
|
|
124
|
+
branch,
|
|
125
|
+
head: head.ok ? head.stdout.trim() : "",
|
|
126
|
+
upstream,
|
|
127
|
+
ahead,
|
|
128
|
+
behind,
|
|
129
|
+
changes,
|
|
130
|
+
stagedCount: changes.filter((c) => c.staged).length,
|
|
131
|
+
unstagedCount: changes.filter((c) => c.unstaged).length,
|
|
132
|
+
untrackedCount: changes.filter((c) => c.untracked).length,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** Unified diff for one path (or whole tree when path omitted). */
|
|
136
|
+
async diff(options) {
|
|
137
|
+
const args = ["diff"];
|
|
138
|
+
if (options.staged)
|
|
139
|
+
args.push("--cached");
|
|
140
|
+
args.push(`-U${options.contextLines ?? 3}`);
|
|
141
|
+
if (options.path) {
|
|
142
|
+
args.push("--", options.path);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
args.push("--stat");
|
|
146
|
+
}
|
|
147
|
+
const r = await this.git(args);
|
|
148
|
+
if (!r.ok)
|
|
149
|
+
return r;
|
|
150
|
+
return { ok: true, diff: r.stdout };
|
|
151
|
+
}
|
|
152
|
+
/** Stage paths (`git add --` / `git add -A --` for "."). */
|
|
153
|
+
async stage(paths, all = false) {
|
|
154
|
+
if (!all && paths.length === 0) {
|
|
155
|
+
return { ok: false, code: "NO_PATHS", message: "stage requires paths or all=true" };
|
|
156
|
+
}
|
|
157
|
+
const args = ["add"];
|
|
158
|
+
if (all) {
|
|
159
|
+
args.push("-A");
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
args.push("--");
|
|
163
|
+
args.push(...paths);
|
|
164
|
+
}
|
|
165
|
+
return this.git(args);
|
|
166
|
+
}
|
|
167
|
+
/** Unstage paths (`git reset HEAD --`). */
|
|
168
|
+
async unstage(paths) {
|
|
169
|
+
if (paths.length === 0) {
|
|
170
|
+
return { ok: false, code: "NO_PATHS", message: "unstage requires paths" };
|
|
171
|
+
}
|
|
172
|
+
return this.git(["reset", "HEAD", "--", ...paths]);
|
|
173
|
+
}
|
|
174
|
+
/** Discard worktree changes for paths (`git checkout --`). DANGEROUS. */
|
|
175
|
+
async discard(paths) {
|
|
176
|
+
if (paths.length === 0) {
|
|
177
|
+
return { ok: false, code: "NO_PATHS", message: "discard requires paths" };
|
|
178
|
+
}
|
|
179
|
+
return this.git(["checkout", "--", ...paths]);
|
|
180
|
+
}
|
|
181
|
+
async commit(message) {
|
|
182
|
+
const trimmed = message.trim();
|
|
183
|
+
if (!trimmed) {
|
|
184
|
+
return { ok: false, code: "EMPTY_MESSAGE", message: "commit message required" };
|
|
185
|
+
}
|
|
186
|
+
const r = await this.git(["commit", "-m", trimmed]);
|
|
187
|
+
if (!r.ok)
|
|
188
|
+
return r;
|
|
189
|
+
const sha = await this.git(["rev-parse", "--short", "HEAD"], { timeoutMs: 5_000 });
|
|
190
|
+
return { ok: true, commit: sha.ok ? sha.stdout.trim() : "" };
|
|
191
|
+
}
|
|
192
|
+
async log(limit = 30) {
|
|
193
|
+
const r = await this.git([
|
|
194
|
+
"log",
|
|
195
|
+
`--max-count=${limit}`,
|
|
196
|
+
"--pretty=format:%H\u0001%h\u0001%s\u0001%an\u0001%aI\u0001%D",
|
|
197
|
+
]);
|
|
198
|
+
if (!r.ok)
|
|
199
|
+
return r;
|
|
200
|
+
const commits = [];
|
|
201
|
+
for (const line of r.stdout.split("\n")) {
|
|
202
|
+
if (!line.trim())
|
|
203
|
+
continue;
|
|
204
|
+
const [sha, short, subject, author, date, refs] = line.split("\u0001");
|
|
205
|
+
if (!sha)
|
|
206
|
+
continue;
|
|
207
|
+
commits.push({
|
|
208
|
+
sha: short ?? sha,
|
|
209
|
+
subject: subject ?? "",
|
|
210
|
+
author: author ?? "",
|
|
211
|
+
date: date ?? "",
|
|
212
|
+
refs: refs?.trim() || undefined,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return { ok: true, commits };
|
|
216
|
+
}
|
|
217
|
+
async branchList() {
|
|
218
|
+
const r = await this.git(["branch", "--list", "--format=%(HEAD)%02%(refname:short)"]);
|
|
219
|
+
if (!r.ok)
|
|
220
|
+
return r;
|
|
221
|
+
const branches = [];
|
|
222
|
+
for (const line of r.stdout.split("\n")) {
|
|
223
|
+
if (!line.trim())
|
|
224
|
+
continue;
|
|
225
|
+
const [headMark, name] = line.split("\x02");
|
|
226
|
+
if (!name)
|
|
227
|
+
continue;
|
|
228
|
+
branches.push({ name: name.trim(), current: headMark?.trim() === "*" });
|
|
229
|
+
}
|
|
230
|
+
const current = branches.find((b) => b.current)?.name ?? "";
|
|
231
|
+
return { ok: true, current, branches };
|
|
232
|
+
}
|
|
233
|
+
async branchCreate(name) {
|
|
234
|
+
if (!/^[\w.\-/]+$/.test(name)) {
|
|
235
|
+
return { ok: false, code: "INVALID_NAME", message: `invalid branch name: ${name}` };
|
|
236
|
+
}
|
|
237
|
+
return this.git(["checkout", "-b", name]);
|
|
238
|
+
}
|
|
239
|
+
async branchSwitch(name) {
|
|
240
|
+
if (!/^[\w.\-/]+$/.test(name)) {
|
|
241
|
+
return { ok: false, code: "INVALID_NAME", message: `invalid branch name: ${name}` };
|
|
242
|
+
}
|
|
243
|
+
return this.git(["checkout", name]);
|
|
244
|
+
}
|
|
245
|
+
async push() {
|
|
246
|
+
const r = await this.git(["push"], { timeoutMs: 120_000 });
|
|
247
|
+
return r.ok ? { ok: true, output: r.stdout } : r;
|
|
248
|
+
}
|
|
249
|
+
async pull() {
|
|
250
|
+
const r = await this.git(["pull", "--no-edit"], { timeoutMs: 120_000 });
|
|
251
|
+
return r.ok ? { ok: true, output: r.stdout } : r;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
}
|
|
@@ -36,6 +36,7 @@ export class ChatSessionCatalog {
|
|
|
36
36
|
deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
|
|
37
37
|
isBlank: isBlankChatSession(record),
|
|
38
38
|
...(workspaceId ? { workspaceId } : {}),
|
|
39
|
+
sessionMode: record.sessionMode,
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
remove(sessionId) {
|
|
@@ -0,0 +1,48 @@
|
|
|
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: "会话已就绪。点选建议快速开场,或在下方输入框直接描述目标。业务写入仍经受治理 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,直接协作编码。不注入工作流业务指令、业务工具或 Goal 驱动。",
|
|
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. */
|
|
39
|
+
export const WORKFLOW_EXCLUSIVE_SLASH_ACTIONS = Object.freeze([
|
|
40
|
+
"focus-panel:task",
|
|
41
|
+
"focus-panel:contract",
|
|
42
|
+
"focus-panel:dag",
|
|
43
|
+
"focus:interview",
|
|
44
|
+
"goal",
|
|
45
|
+
]);
|
|
46
|
+
export function isWorkflowExclusiveSlashAction(action) {
|
|
47
|
+
return WORKFLOW_EXCLUSIVE_SLASH_ACTIONS.includes(action);
|
|
48
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console Chat session modes (workflow | pi).
|
|
3
|
+
*
|
|
4
|
+
* sessionMode is a durable session identity, independent of per-turn
|
|
5
|
+
* operator | requirement-interview and of UI layout mode. Record is the
|
|
6
|
+
* source of truth; runtime, catalog, list/detail and the browser cache
|
|
7
|
+
* all project this field.
|
|
8
|
+
*/
|
|
9
|
+
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS, OPERATOR_CHAT_PI_BUILTIN_TOOLS, authorizeOperatorChatTool, isOperatorChatToolDenied, } from "./tools.js";
|
|
10
|
+
import { OPERATOR_CHAT_AGENT_TOOL_NAME } from "./subagents/types.js";
|
|
11
|
+
import { CONSOLE_CHAT_SESSION_MODES, DEFAULT_CONSOLE_CHAT_SESSION_MODE, } from "./session-mode-view.js";
|
|
12
|
+
export { CONSOLE_CHAT_SESSION_MODES, DEFAULT_CONSOLE_CHAT_SESSION_MODE, SESSION_MODE_COPY, } from "./session-mode-view.js";
|
|
13
|
+
export const SESSION_MODE_INVALID_CODE = "INVALID_SESSION_MODE";
|
|
14
|
+
export const SESSION_MODE_UNKNOWN_CODE = "UNKNOWN_SESSION_MODE";
|
|
15
|
+
export const SESSION_MODE_ACTION_DENIED_CODE = "SESSION_MODE_ACTION_DENIED";
|
|
16
|
+
export const SESSION_MODE_IMMUTABLE_CODE = "SESSION_MODE_IMMUTABLE";
|
|
17
|
+
export class InvalidChatSessionModeError extends Error {
|
|
18
|
+
value;
|
|
19
|
+
code = SESSION_MODE_INVALID_CODE;
|
|
20
|
+
constructor(value) {
|
|
21
|
+
super(`sessionMode must be "workflow" or "pi"${value === undefined || value === null || value === ""
|
|
22
|
+
? ""
|
|
23
|
+
: `; got ${String(value)}`}`);
|
|
24
|
+
this.value = value;
|
|
25
|
+
this.name = "InvalidChatSessionModeError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class UnknownChatSessionModeError extends Error {
|
|
29
|
+
value;
|
|
30
|
+
code = SESSION_MODE_UNKNOWN_CODE;
|
|
31
|
+
constructor(value) {
|
|
32
|
+
super(`chat session record has unknown sessionMode ${JSON.stringify(value)}; refusing to run as workflow`);
|
|
33
|
+
this.value = value;
|
|
34
|
+
this.name = "UnknownChatSessionModeError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const MODE_SET = new Set(CONSOLE_CHAT_SESSION_MODES);
|
|
38
|
+
function isSessionMode(value) {
|
|
39
|
+
return typeof value === "string" && MODE_SET.has(value);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create-path parser. Missing / null / "" → workflow. Explicit illegal
|
|
43
|
+
* values fail closed and must not silently degrade.
|
|
44
|
+
*/
|
|
45
|
+
export function parseCreateSessionMode(value) {
|
|
46
|
+
if (value === undefined || value === null || value === "") {
|
|
47
|
+
return DEFAULT_CONSOLE_CHAT_SESSION_MODE;
|
|
48
|
+
}
|
|
49
|
+
if (isSessionMode(value))
|
|
50
|
+
return value;
|
|
51
|
+
throw new InvalidChatSessionModeError(value);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Restore-path parser. Missing field → workflow (stock-data rule, not a
|
|
55
|
+
* compatibility shim). Present but unknown → fail closed.
|
|
56
|
+
*/
|
|
57
|
+
export function readStoredSessionMode(value) {
|
|
58
|
+
if (value === undefined || value === null || value === "") {
|
|
59
|
+
return DEFAULT_CONSOLE_CHAT_SESSION_MODE;
|
|
60
|
+
}
|
|
61
|
+
if (isSessionMode(value))
|
|
62
|
+
return value;
|
|
63
|
+
throw new UnknownChatSessionModeError(value);
|
|
64
|
+
}
|
|
65
|
+
export function isWorkflowSessionMode(mode) {
|
|
66
|
+
return mode === "workflow";
|
|
67
|
+
}
|
|
68
|
+
export function isPiSessionMode(mode) {
|
|
69
|
+
return mode === "pi";
|
|
70
|
+
}
|
|
71
|
+
/** Shared Console foundations that both modes keep. Not a native-only prompt. */
|
|
72
|
+
export const PI_CHAT_SYSTEM_PROMPT_BASE = [
|
|
73
|
+
"You are Console Chat in Pi mode: a coding assistant with Pi tools plus Console shared foundations.",
|
|
74
|
+
"Use read/write/edit/bash/grep/find/ls to inspect and change the workspace. apply_patch, full-tools, shell and coding-chat are denied.",
|
|
75
|
+
"Agent (Explore) is a built-in read-only subagent for path-uncertain codebase search. Do not use it for a known single-file read. Prefer safe-read/safe-grep for secret-safe probes.",
|
|
76
|
+
"ask_user_question pauses for a real user answer. todo_write tracks the current work and is restored after compression; it is not a workflow auto-continue signal. The persistent terminal tool is available.",
|
|
77
|
+
"This session keeps Console compression (L0/L1/L2, sift, strict checkpoint) and existing Aimax / SDD access. Do not create tasks, run DAGs, drive Goals, or start requirement interviews from this session.",
|
|
78
|
+
"Obey repository AGENTS.md, skills and extensions when they apply. Choosing Pi mode is not extra write authorization and does not bypass Console access control or output scrubbing.",
|
|
79
|
+
].join("\n");
|
|
80
|
+
export const CHAT_SAFE_EXPLORE_TOOL_IDS = Object.freeze([
|
|
81
|
+
"safe-read",
|
|
82
|
+
"safe-grep",
|
|
83
|
+
"git-status",
|
|
84
|
+
"git-diff",
|
|
85
|
+
]);
|
|
86
|
+
export const PI_CHAT_GENERIC_HELPER_TOOLS = Object.freeze([
|
|
87
|
+
"ask_user_question",
|
|
88
|
+
"todo_write",
|
|
89
|
+
]);
|
|
90
|
+
export const PI_CHAT_WORKFLOW_TOOL_IDS = Object.freeze([
|
|
91
|
+
"get_goal",
|
|
92
|
+
"create_goal",
|
|
93
|
+
"update_goal",
|
|
94
|
+
]);
|
|
95
|
+
const CHAT_EXTENSION_DENIED_TOOL_IDS = new Set([
|
|
96
|
+
"apply_patch",
|
|
97
|
+
"apply-patch",
|
|
98
|
+
"full-tools",
|
|
99
|
+
"full_tools",
|
|
100
|
+
"coding-chat",
|
|
101
|
+
"coding_chat",
|
|
102
|
+
"shell",
|
|
103
|
+
].map((name) => name.toLowerCase()));
|
|
104
|
+
const WORKFLOW_OPERATOR_TOOL_IDS = new Set(OPERATOR_CHAT_ALLOWED_TOOLS.map((name) => name.toLowerCase()));
|
|
105
|
+
const PI_GOAL_TOOL_IDS = new Set(PI_CHAT_WORKFLOW_TOOL_IDS.map((name) => name.toLowerCase()));
|
|
106
|
+
export function workflowActiveToolBaseline() {
|
|
107
|
+
return [
|
|
108
|
+
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
109
|
+
...OPERATOR_CHAT_PI_BUILTIN_TOOLS,
|
|
110
|
+
...CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
111
|
+
...OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS,
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
export function piActiveToolBaseline() {
|
|
115
|
+
return [
|
|
116
|
+
...OPERATOR_CHAT_PI_BUILTIN_TOOLS,
|
|
117
|
+
...CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
118
|
+
...OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS,
|
|
119
|
+
...PI_CHAT_GENERIC_HELPER_TOOLS,
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
export function chatActiveToolBaseline(sessionMode) {
|
|
123
|
+
return sessionMode === "pi"
|
|
124
|
+
? piActiveToolBaseline()
|
|
125
|
+
: workflowActiveToolBaseline();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Gate 2 pin: mode baseline plus user/project extension tools. Pi mode must
|
|
129
|
+
* not re-add operator actions or Goal tools via the extras pass.
|
|
130
|
+
*/
|
|
131
|
+
export function computeChatActiveToolNames(session, sessionMode) {
|
|
132
|
+
const baseline = chatActiveToolBaseline(sessionMode);
|
|
133
|
+
if (!session?.getAllTools)
|
|
134
|
+
return baseline;
|
|
135
|
+
const baselineLower = new Set(baseline.map((name) => name.toLowerCase()));
|
|
136
|
+
const extras = [];
|
|
137
|
+
for (const tool of session.getAllTools()) {
|
|
138
|
+
const name = tool?.name;
|
|
139
|
+
if (!name || typeof name !== "string")
|
|
140
|
+
continue;
|
|
141
|
+
const lower = name.toLowerCase();
|
|
142
|
+
if (baselineLower.has(lower))
|
|
143
|
+
continue;
|
|
144
|
+
if (CHAT_EXTENSION_DENIED_TOOL_IDS.has(lower))
|
|
145
|
+
continue;
|
|
146
|
+
if (sessionMode === "pi" && WORKFLOW_OPERATOR_TOOL_IDS.has(lower))
|
|
147
|
+
continue;
|
|
148
|
+
if (sessionMode === "pi" && PI_GOAL_TOOL_IDS.has(lower))
|
|
149
|
+
continue;
|
|
150
|
+
extras.push(name);
|
|
151
|
+
}
|
|
152
|
+
return extras.length > 0 ? [...baseline, ...extras] : baseline;
|
|
153
|
+
}
|
|
154
|
+
export function authorizeChatSessionTool(toolId, sessionMode) {
|
|
155
|
+
if (sessionMode === "workflow")
|
|
156
|
+
return authorizeOperatorChatTool(toolId);
|
|
157
|
+
const id = toolId.trim();
|
|
158
|
+
if (!id) {
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
code: "tool-unknown",
|
|
162
|
+
toolId: id,
|
|
163
|
+
message: "empty tool id is not allowed in Pi mode",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (isOperatorChatToolDenied(id)) {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
code: "tool-denied",
|
|
170
|
+
toolId: id,
|
|
171
|
+
message: `tool "${id}" is denied in Pi mode (no apply_patch/full-tools/shell/coding-chat)`,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const lower = id.toLowerCase();
|
|
175
|
+
if (WORKFLOW_OPERATOR_TOOL_IDS.has(lower) || PI_GOAL_TOOL_IDS.has(lower)) {
|
|
176
|
+
return {
|
|
177
|
+
ok: false,
|
|
178
|
+
code: "tool-denied",
|
|
179
|
+
toolId: id,
|
|
180
|
+
message: `tool "${id}" is a workflow action and is not available in Pi mode`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const authorized = new Set(piActiveToolBaseline().map((name) => name.toLowerCase()));
|
|
184
|
+
if (authorized.has(lower))
|
|
185
|
+
return { ok: true, toolId: id };
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
code: "tool-unknown",
|
|
189
|
+
toolId: id,
|
|
190
|
+
message: `tool "${id}" is not registered for Pi mode`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function composeChatSystemPromptParts(input) {
|
|
194
|
+
const base = input.sessionMode === "pi"
|
|
195
|
+
? PI_CHAT_SYSTEM_PROMPT_BASE
|
|
196
|
+
: input.workflowPrompt;
|
|
197
|
+
const suffix = input.sessionMode === "workflow" ? input.systemPromptSuffix?.trim() : "";
|
|
198
|
+
return suffix ? [base, suffix] : [base];
|
|
199
|
+
}
|
|
200
|
+
export function composeChatSystemPrompt(input) {
|
|
201
|
+
return composeChatSystemPromptParts(input).join("\n\n");
|
|
202
|
+
}
|
|
203
|
+
export const PI_TODO_WRITE_DESCRIPTION = "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (no partial updates, no per-item edits). Before starting multi-step work, add one todo per concrete step. Keep at least one todo `in_progress` while work remains. Mark a todo `completed` immediately when it is done (do not batch completions) and resend the full list as progress advances. Skip the list for simple Q&A, read-only lookups, and single-step tasks. Statuses: `pending` / `in_progress` / `completed`.";
|
|
204
|
+
// Slash gating lives in the browser-safe view module (shortcuts.ts is in the
|
|
205
|
+
// UI bundle); re-export here for Node-side consumers.
|
|
206
|
+
export { WORKFLOW_EXCLUSIVE_SLASH_ACTIONS, isWorkflowExclusiveSlashAction, } from "./session-mode-view.js";
|
|
207
|
+
export function shouldLoadWorkflowBusinessTools(sessionMode) {
|
|
208
|
+
return sessionMode === "workflow";
|
|
209
|
+
}
|
|
210
|
+
export function shouldInjectGoalContinuation(sessionMode) {
|
|
211
|
+
return sessionMode === "workflow";
|
|
212
|
+
}
|
|
213
|
+
export function shouldAcceptOperatorSnapshot(sessionMode) {
|
|
214
|
+
return sessionMode === "workflow";
|
|
215
|
+
}
|
|
216
|
+
export const SESSION_MODE_ACTION_DENIED_MESSAGE = "this action is only available in workflow sessions";
|
|
217
|
+
/** Stable tool name used by tests and catalogs for the Explore subagent. */
|
|
218
|
+
export const PI_CHAT_AGENT_TOOL_NAME = OPERATOR_CHAT_AGENT_TOOL_NAME;
|