@cydm/pie 1.0.4 → 1.0.6
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/README.md +11 -0
- package/dist/builtin/extensions/ask-user/index.js +11 -2911
- package/dist/builtin/extensions/changelog/index.js +3 -8
- package/dist/builtin/extensions/deploy/index.js +1 -1
- package/dist/builtin/extensions/document-attachments/index.js +1 -0
- package/dist/builtin/extensions/files/index.js +1 -1
- package/dist/builtin/extensions/init/index.js +1 -3
- package/dist/builtin/extensions/kimi-attachments/index.js +1 -0
- package/dist/builtin/extensions/plan-mode/index.js +55 -95
- package/dist/builtin/extensions/subagent/index.js +26 -10994
- package/dist/builtin/extensions/todo/index.js +33 -152
- package/dist/chunks/capabilities-FENCOHVA.js +9 -0
- package/dist/chunks/chunk-JYBXCEJJ.js +315 -0
- package/dist/chunks/chunk-MWFBYJOI.js +7334 -0
- package/dist/{builtin/extensions/questionnaire/index.js → chunks/chunk-RID3574D.js} +1453 -1488
- package/dist/chunks/chunk-TBJ25UWB.js +3657 -0
- package/dist/chunks/chunk-TG2EQLX2.js +43 -0
- package/dist/chunks/chunk-XZXLO7YB.js +322 -0
- package/dist/chunks/file-logger-AL5VVZHH.js +22 -0
- package/dist/chunks/src-EGWRDMLB.js +13 -0
- package/dist/chunks/src-WRUACRN2.js +132 -0
- package/dist/cli.js +41336 -55921
- package/package.json +4 -5
|
@@ -1,161 +1,42 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
ManageTodoListParamsSchema,
|
|
4
|
+
executeManageTodoList,
|
|
5
|
+
restoreTodosFromMessages
|
|
6
|
+
} from "../../../chunks/chunk-TBJ25UWB.js";
|
|
7
|
+
import "../../../chunks/chunk-MWFBYJOI.js";
|
|
8
|
+
import "../../../chunks/chunk-RID3574D.js";
|
|
9
|
+
import "../../../chunks/chunk-TG2EQLX2.js";
|
|
2
10
|
|
|
3
11
|
// builtin/extensions/todo/index.ts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
var TOOL_DESCRIPTION = `Manage a structured todo list to track progress and plan tasks throughout your coding session.
|
|
13
|
+
|
|
14
|
+
Use this tool for complex multi-step work, especially when:
|
|
15
|
+
- The task needs planning or progress tracking
|
|
16
|
+
- The user provided multiple requests
|
|
17
|
+
- You need to break a larger change into actionable steps
|
|
18
|
+
- You are about to start or finish a tracked step
|
|
19
|
+
|
|
20
|
+
Workflow:
|
|
21
|
+
1. Write the full todo list with clear action items
|
|
22
|
+
2. Mark the current item as in-progress before starting it
|
|
23
|
+
3. Complete the work for that item
|
|
24
|
+
4. Mark it completed immediately
|
|
25
|
+
5. Continue until all items are done
|
|
26
|
+
|
|
27
|
+
Always send the full list on write. Partial updates are not supported.`;
|
|
16
28
|
function todoExtension(ctx) {
|
|
17
|
-
ctx.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
description:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const pieDir = path.join(ctx2.cwd, ".pie");
|
|
26
|
-
if (!fs.existsSync(pieDir)) {
|
|
27
|
-
fs.mkdirSync(pieDir, { recursive: true });
|
|
28
|
-
}
|
|
29
|
-
const command = args?.trim();
|
|
30
|
-
const existingTodos = await readTodos(ctx2.cwd);
|
|
31
|
-
if (command?.match(/^done\s+\d+/i)) {
|
|
32
|
-
const match = command.match(/done\s+(\d+)/i);
|
|
33
|
-
const id = match ? parseInt(match[1], 10) : 0;
|
|
34
|
-
if (!existingTodos) return "No todos.md file found.";
|
|
35
|
-
return `\u8BF7\u5C06 .pie/todos.md \u4E2D\u7B2C ${id} \u4E2A\u672A\u5B8C\u6210\u4EFB\u52A1\u6807\u8BB0\u4E3A\u5DF2\u5B8C\u6210\uFF1A
|
|
36
|
-
|
|
37
|
-
\u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1A
|
|
38
|
-
\`\`\`
|
|
39
|
-
${existingTodos}
|
|
40
|
-
\`\`\`
|
|
41
|
-
|
|
42
|
-
\u8981\u6C42\uFF1A
|
|
43
|
-
1. \u627E\u5230\u7B2C ${id} \u4E2A\u4EE5 "- [ ]" \u5F00\u5934\u7684\u4EFB\u52A1
|
|
44
|
-
2. \u5C06\u5176\u6539\u4E3A "- [x]"\uFF08\u4FDD\u7559\u539F\u6709\u5185\u5BB9\u548C\u4F18\u5148\u7EA7\u6807\u8BB0\uFF09
|
|
45
|
-
3. \u5728\u672B\u5C3E\u6DFB\u52A0\u5B8C\u6210\u65F6\u95F4\u6CE8\u91CA\uFF1A<!-- completed: YYYY-MM-DD -->
|
|
46
|
-
4. \u4F7F\u7528 write \u5DE5\u5177\u66F4\u65B0\u6587\u4EF6
|
|
47
|
-
5. \u62A5\u544A\u5B8C\u6210\u4E86\u54EA\u4E2A\u4EFB\u52A1`;
|
|
48
|
-
}
|
|
49
|
-
if (command?.toLowerCase() === "clear") {
|
|
50
|
-
if (!existingTodos) return "No todos.md file found.";
|
|
51
|
-
return `\u8BF7\u6E05\u7406 .pie/todos.md \u4E2D\u7684\u5DF2\u5B8C\u6210\u4EFB\u52A1\uFF1A
|
|
52
|
-
|
|
53
|
-
\u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1A
|
|
54
|
-
\`\`\`
|
|
55
|
-
${existingTodos}
|
|
56
|
-
\`\`\`
|
|
57
|
-
|
|
58
|
-
\u8981\u6C42\uFF1A
|
|
59
|
-
1. \u79FB\u9664\u6240\u6709\u4EE5 "- [x]" \u5F00\u5934\u7684\u5DF2\u5B8C\u6210\u4EFB\u52A1
|
|
60
|
-
2. \u4FDD\u7559\u6240\u6709 "- [ ]" \u672A\u5B8C\u6210\u4EFB\u52A1
|
|
61
|
-
3. \u4FDD\u7559\u6587\u4EF6\u6807\u9898\u548C\u5176\u4ED6\u7ED3\u6784
|
|
62
|
-
4. \u4F7F\u7528 write \u5DE5\u5177\u66F4\u65B0\u6587\u4EF6
|
|
63
|
-
5. \u62A5\u544A\u6E05\u7406\u4E86\u591A\u5C11\u4E2A\u5DF2\u5B8C\u6210\u4EFB\u52A1`;
|
|
64
|
-
}
|
|
65
|
-
if (command?.toLowerCase().startsWith("add ")) {
|
|
66
|
-
const description = command.slice(4).trim();
|
|
67
|
-
ctx2.ui.notify("\u{1F4DD} \u6B63\u5728\u5206\u6790\u4EFB\u52A1...", "info");
|
|
68
|
-
return `\u8BF7\u5C06\u4EE5\u4E0B\u63CF\u8FF0\u8F6C\u6362\u4E3A\u7ED3\u6784\u5316\u7684 todo \u6761\u76EE\u5E76\u6DFB\u52A0\u5230 .pie/todos.md\uFF1A
|
|
69
|
-
|
|
70
|
-
\u63CF\u8FF0\uFF1A"${description}"
|
|
71
|
-
|
|
72
|
-
${existingTodos ? `
|
|
73
|
-
\u5F53\u524D todos.md \u5185\u5BB9\uFF1A
|
|
74
|
-
\`\`\`
|
|
75
|
-
${existingTodos}
|
|
76
|
-
\`\`\`` : ""}
|
|
77
|
-
|
|
78
|
-
\u4EFB\u52A1\uFF1A
|
|
79
|
-
1. \u5206\u6790\u63CF\u8FF0\uFF0C\u786E\u5B9A\uFF1A
|
|
80
|
-
- \u4EFB\u52A1\u7C7B\u578B\uFF1Afeature / bug / refactor / docs / test / other
|
|
81
|
-
- \u4F18\u5148\u7EA7\uFF1Ahigh / medium / low\uFF08\u6839\u636E\u7D27\u6025\u7A0B\u5EA6\u548C\u5F71\u54CD\u5224\u65AD\uFF09
|
|
82
|
-
- \u6E05\u6670\u7B80\u6D01\u7684\u4EFB\u52A1\u63CF\u8FF0
|
|
83
|
-
|
|
84
|
-
2. \u751F\u6210\u683C\u5F0F\uFF1A
|
|
85
|
-
- [ ] ![{priority}] [{type}] {description}
|
|
86
|
-
|
|
87
|
-
\u793A\u4F8B\uFF1A
|
|
88
|
-
- [ ] ![high] [bug] \u4FEE\u590D\u767B\u5F55\u9875\u9762\u7684\u5185\u5B58\u6CC4\u6F0F
|
|
89
|
-
- [ ] ![medium] [feature] \u6DFB\u52A0\u7528\u6237\u5BFC\u51FA\u529F\u80FD
|
|
90
|
-
- [ ] ![low] [docs] \u66F4\u65B0 API \u6587\u6863
|
|
91
|
-
|
|
92
|
-
3. \u4F7F\u7528 write \u5DE5\u5177\uFF1A
|
|
93
|
-
- \u5982\u679C\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u521B\u5EFA\u6807\u51C6\u683C\u5F0F
|
|
94
|
-
- \u5C06\u65B0\u4EFB\u52A1\u6309\u4F18\u5148\u7EA7\u63D2\u5165\u5230 "## Active" \u90E8\u5206\u7684\u5408\u9002\u4F4D\u7F6E\uFF08high\u5728\u524D\uFF0Clow\u5728\u540E\uFF09
|
|
95
|
-
- \u4FDD\u6301\u73B0\u6709\u4EFB\u52A1\u4E0D\u53D8
|
|
96
|
-
|
|
97
|
-
4. \u62A5\u544A\u6DFB\u52A0\u4E86\u4EC0\u4E48\u4EFB\u52A1`;
|
|
98
|
-
}
|
|
99
|
-
if (command?.toLowerCase() === "priority") {
|
|
100
|
-
if (!existingTodos) return "No todos.md file found.";
|
|
101
|
-
ctx2.ui.notify("\u{1F504} \u6B63\u5728\u6309\u4F18\u5148\u7EA7\u6392\u5E8F...", "info");
|
|
102
|
-
return `\u8BF7\u91CD\u65B0\u6574\u7406 .pie/todos.md \u4E2D\u7684\u4EFB\u52A1\uFF0C\u6309\u4F18\u5148\u7EA7\u6392\u5E8F\uFF1A
|
|
103
|
-
|
|
104
|
-
\u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1A
|
|
105
|
-
\`\`\`
|
|
106
|
-
${existingTodos}
|
|
107
|
-
\`\`\`
|
|
108
|
-
|
|
109
|
-
\u6392\u5E8F\u89C4\u5219\uFF1A
|
|
110
|
-
1. \u672A\u5B8C\u6210\u4EFB\u52A1\u6309\u4F18\u5148\u7EA7\u6392\u5E8F\uFF1Ahigh \u2192 medium \u2192 low
|
|
111
|
-
2. \u540C\u7EA7\u4F18\u5148\u7EA7\u4FDD\u6301\u539F\u6709\u987A\u5E8F
|
|
112
|
-
3. \u5DF2\u5B8C\u6210\u4EFB\u52A1\u653E\u5728\u6700\u540E
|
|
113
|
-
4. \u4FDD\u6301\u6587\u4EF6\u5176\u4ED6\u7ED3\u6784\u4E0D\u53D8
|
|
114
|
-
|
|
115
|
-
\u683C\u5F0F\u4FDD\u6301\uFF1A
|
|
116
|
-
- [ ] ![high] [type] \u63CF\u8FF0
|
|
117
|
-
- [ ] ![medium] [type] \u63CF\u8FF0
|
|
118
|
-
- [ ] ![low] [type] \u63CF\u8FF0
|
|
119
|
-
|
|
120
|
-
\u4F7F\u7528 write \u5DE5\u5177\u66F4\u65B0\u6587\u4EF6\u3002`;
|
|
121
|
-
}
|
|
122
|
-
ctx2.ui.notify("\u{1F50D} \u6B63\u5728\u5206\u6790\u5BF9\u8BDD\u63D0\u53D6\u4EFB\u52A1...", "info");
|
|
123
|
-
return `\u{1F50D} \u667A\u80FD\u4EFB\u52A1\u63D0\u53D6
|
|
124
|
-
|
|
125
|
-
\u8BF7\u5206\u6790\u6211\u4EEC\u7684\u5BF9\u8BDD\u5386\u53F2\uFF0C\u63D0\u53D6\u6240\u6709\u9700\u8981\u540E\u7EED\u5904\u7406\u7684\u4EFB\u52A1\u3002
|
|
126
|
-
|
|
127
|
-
${existingTodos ? `\u5F53\u524D\u5DF2\u6709\u4EFB\u52A1\uFF08\u907F\u514D\u91CD\u590D\uFF09\uFF1A
|
|
128
|
-
\`\`\`
|
|
129
|
-
${existingTodos}
|
|
130
|
-
\`\`\`
|
|
131
|
-
` : ""}
|
|
132
|
-
|
|
133
|
-
\u63D0\u53D6\u6807\u51C6\uFF1A
|
|
134
|
-
1. **\u660E\u786E\u7684\u5F85\u529E** - "\u6211\u4EEC\u9700\u8981..."\u3001"\u8FD8\u8981..."\u3001"TODO..."
|
|
135
|
-
2. **\u53D1\u73B0\u7684\u95EE\u9898** - "\u8FD9\u91CC\u6709 bug"\u3001"\u9700\u8981\u4FEE\u590D..."
|
|
136
|
-
3. **\u6539\u8FDB\u5EFA\u8BAE** - "\u5E94\u8BE5\u4F18\u5316..."\u3001"\u53EF\u4EE5\u91CD\u6784..."
|
|
137
|
-
4. **\u540E\u7EED\u5DE5\u4F5C** - "\u63A5\u4E0B\u6765..."\u3001"\u4E4B\u540E\u8981..."
|
|
138
|
-
5. **\u672A\u5B8C\u6210\u4E8B\u9879** - \u5BF9\u8BDD\u4E2D\u63D0\u53CA\u4F46\u672A\u5B8C\u6210\u7684\u52A8\u4F5C
|
|
139
|
-
|
|
140
|
-
\u5FFD\u7565\uFF1A
|
|
141
|
-
- \u5DF2\u7ECF\u660E\u786E\u8BF4"\u5B8C\u6210"\u7684\u4E8B\u9879
|
|
142
|
-
- \u592A\u6A21\u7CCA\u65E0\u6CD5\u6267\u884C\u7684\u60F3\u6CD5
|
|
143
|
-
- \u5F53\u524D\u5BF9\u8BDD\u4E2D\u5DF2\u7ECF\u89E3\u51B3\u7684\u95EE\u9898
|
|
144
|
-
|
|
145
|
-
\u8F93\u51FA\u683C\u5F0F\uFF08\u5982\u679C\u63D0\u53D6\u5230\u4EFB\u52A1\uFF09\uFF1A
|
|
146
|
-
|
|
147
|
-
## \u53D1\u73B0\u7684\u4EFB\u52A1
|
|
148
|
-
|
|
149
|
-
| # | \u4F18\u5148\u7EA7 | \u7C7B\u578B | \u4EFB\u52A1\u63CF\u8FF0 |
|
|
150
|
-
|---|--------|------|----------|
|
|
151
|
-
| 1 | \u{1F534} high | bug | \u4FEE\u590D xxx \u95EE\u9898 |
|
|
152
|
-
| 2 | \u{1F7E1} medium | feature | \u6DFB\u52A0 xxx \u529F\u80FD |
|
|
153
|
-
|
|
154
|
-
\u8BF7\u4F7F\u7528 write \u5DE5\u5177\u5C06\u63D0\u53D6\u5230\u7684\u4EFB\u52A1\u6DFB\u52A0\u5230 .pie/todos.md\u3002
|
|
155
|
-
\u5982\u679C\u6CA1\u6709\u53D1\u73B0\u65B0\u4EFB\u52A1\uFF0C\u76F4\u63A5\u62A5\u544A\u5373\u53EF\u3002`;
|
|
29
|
+
ctx.registerTool({
|
|
30
|
+
name: "manage_todo_list",
|
|
31
|
+
label: "manage_todo_list",
|
|
32
|
+
description: TOOL_DESCRIPTION,
|
|
33
|
+
parameters: ManageTodoListParamsSchema,
|
|
34
|
+
async execute(args) {
|
|
35
|
+
const currentTodos = restoreTodosFromMessages(ctx.getMessages());
|
|
36
|
+
return executeManageTodoList(args, currentTodos).result;
|
|
156
37
|
}
|
|
157
38
|
});
|
|
158
|
-
ctx.log("
|
|
39
|
+
ctx.log("Builtin todo extension ready");
|
|
159
40
|
}
|
|
160
41
|
export {
|
|
161
42
|
todoExtension as default
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
createCliHostCapabilities
|
|
4
|
+
} from "./chunk-JYBXCEJJ.js";
|
|
5
|
+
import "./chunk-RID3574D.js";
|
|
6
|
+
import "./chunk-TG2EQLX2.js";
|
|
7
|
+
export {
|
|
8
|
+
createCliHostCapabilities
|
|
9
|
+
};
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
Type
|
|
4
|
+
} from "./chunk-RID3574D.js";
|
|
5
|
+
|
|
6
|
+
// src/capabilities/bash/index.ts
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { createWriteStream } from "fs";
|
|
9
|
+
import { tmpdir } from "os";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
import { randomBytes } from "crypto";
|
|
12
|
+
|
|
13
|
+
// src/capabilities/bash/truncate.ts
|
|
14
|
+
var DEFAULT_MAX_LINES = 2e3;
|
|
15
|
+
var DEFAULT_MAX_BYTES = 50 * 1024;
|
|
16
|
+
function formatSize(bytes) {
|
|
17
|
+
if (bytes < 1024) {
|
|
18
|
+
return `${bytes}B`;
|
|
19
|
+
} else if (bytes < 1024 * 1024) {
|
|
20
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
21
|
+
} else {
|
|
22
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function truncateTail(content, options = {}) {
|
|
26
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
27
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
28
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
29
|
+
const lines = content.split("\n");
|
|
30
|
+
const totalLines = lines.length;
|
|
31
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
32
|
+
return {
|
|
33
|
+
content,
|
|
34
|
+
truncated: false,
|
|
35
|
+
truncatedBy: null,
|
|
36
|
+
totalLines,
|
|
37
|
+
totalBytes,
|
|
38
|
+
outputLines: totalLines,
|
|
39
|
+
outputBytes: totalBytes,
|
|
40
|
+
lastLinePartial: false,
|
|
41
|
+
maxLines,
|
|
42
|
+
maxBytes
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const outputLinesArr = [];
|
|
46
|
+
let outputBytesCount = 0;
|
|
47
|
+
let truncatedBy = "lines";
|
|
48
|
+
let lastLinePartial = false;
|
|
49
|
+
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
50
|
+
const line = lines[i];
|
|
51
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0);
|
|
52
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
53
|
+
truncatedBy = "bytes";
|
|
54
|
+
if (outputLinesArr.length === 0) {
|
|
55
|
+
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
56
|
+
outputLinesArr.unshift(truncatedLine);
|
|
57
|
+
outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
|
|
58
|
+
lastLinePartial = true;
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
outputLinesArr.unshift(line);
|
|
63
|
+
outputBytesCount += lineBytes;
|
|
64
|
+
}
|
|
65
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
66
|
+
truncatedBy = "lines";
|
|
67
|
+
}
|
|
68
|
+
const outputContent = outputLinesArr.join("\n");
|
|
69
|
+
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
70
|
+
return {
|
|
71
|
+
content: outputContent,
|
|
72
|
+
truncated: true,
|
|
73
|
+
truncatedBy,
|
|
74
|
+
totalLines,
|
|
75
|
+
totalBytes,
|
|
76
|
+
outputLines: outputLinesArr.length,
|
|
77
|
+
outputBytes: finalOutputBytes,
|
|
78
|
+
lastLinePartial,
|
|
79
|
+
maxLines,
|
|
80
|
+
maxBytes
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
84
|
+
const buf = Buffer.from(str, "utf-8");
|
|
85
|
+
if (buf.length <= maxBytes) {
|
|
86
|
+
return str;
|
|
87
|
+
}
|
|
88
|
+
let start = buf.length - maxBytes;
|
|
89
|
+
while (start < buf.length && (buf[start] & 192) === 128) {
|
|
90
|
+
start++;
|
|
91
|
+
}
|
|
92
|
+
return buf.slice(start).toString("utf-8");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/capabilities/bash/index.ts
|
|
96
|
+
var bashSchema = Type.Object({
|
|
97
|
+
command: Type.String({ description: "The bash command to execute" }),
|
|
98
|
+
timeout: Type.Optional(
|
|
99
|
+
Type.Number({
|
|
100
|
+
description: "Timeout in seconds. Default: 120s (2 minutes). For longer tasks, explicitly set a higher value: { timeout: 600 } for npm install, { timeout: 600 } for cargo build, etc."
|
|
101
|
+
})
|
|
102
|
+
),
|
|
103
|
+
cwd: Type.Optional(Type.String({ description: "Working directory for the command" }))
|
|
104
|
+
});
|
|
105
|
+
var DEFAULT_TIMEOUT_SECONDS = 120;
|
|
106
|
+
function getTempFilePath() {
|
|
107
|
+
const id = randomBytes(8).toString("hex");
|
|
108
|
+
return join(tmpdir(), `pie-bash-${id}.log`);
|
|
109
|
+
}
|
|
110
|
+
function killProcessTree(pid) {
|
|
111
|
+
if (process.platform === "win32") {
|
|
112
|
+
try {
|
|
113
|
+
spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
|
|
114
|
+
stdio: "ignore",
|
|
115
|
+
detached: true
|
|
116
|
+
});
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
try {
|
|
121
|
+
process.kill(-pid, "SIGKILL");
|
|
122
|
+
} catch {
|
|
123
|
+
try {
|
|
124
|
+
process.kill(pid, "SIGKILL");
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function createBashTool(defaultCwd) {
|
|
131
|
+
return {
|
|
132
|
+
name: "bash",
|
|
133
|
+
label: "bash",
|
|
134
|
+
description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Default timeout: 120s. Use timeout parameter for longer tasks (e.g., npm install -> 600).`,
|
|
135
|
+
parameters: bashSchema,
|
|
136
|
+
execute: async (_toolCallId, { command, timeout, cwd }, signal, onUpdate) => {
|
|
137
|
+
const workingDir = cwd ?? defaultCwd ?? process.cwd();
|
|
138
|
+
const timeoutMs = (timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1e3;
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
if (signal?.aborted) {
|
|
141
|
+
reject(new Error("Operation aborted"));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
let tempFilePath;
|
|
145
|
+
let tempFileStream;
|
|
146
|
+
let totalBytes = 0;
|
|
147
|
+
const chunks = [];
|
|
148
|
+
let chunksBytes = 0;
|
|
149
|
+
const maxChunksBytes = DEFAULT_MAX_BYTES * 2;
|
|
150
|
+
let timedOut = false;
|
|
151
|
+
const child = spawn(command, [], {
|
|
152
|
+
shell: true,
|
|
153
|
+
cwd: workingDir,
|
|
154
|
+
env: process.env,
|
|
155
|
+
detached: true
|
|
156
|
+
// Allows killing entire process tree
|
|
157
|
+
});
|
|
158
|
+
const timeoutId = setTimeout(() => {
|
|
159
|
+
timedOut = true;
|
|
160
|
+
if (child.pid) {
|
|
161
|
+
killProcessTree(child.pid);
|
|
162
|
+
}
|
|
163
|
+
}, timeoutMs);
|
|
164
|
+
const onAbort = () => {
|
|
165
|
+
clearTimeout(timeoutId);
|
|
166
|
+
if (child.pid) {
|
|
167
|
+
killProcessTree(child.pid);
|
|
168
|
+
}
|
|
169
|
+
reject(new Error("Operation aborted"));
|
|
170
|
+
};
|
|
171
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
172
|
+
child.stdout?.on("data", (data) => {
|
|
173
|
+
totalBytes += data.length;
|
|
174
|
+
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
|
175
|
+
tempFilePath = getTempFilePath();
|
|
176
|
+
tempFileStream = createWriteStream(tempFilePath);
|
|
177
|
+
for (const chunk of chunks) {
|
|
178
|
+
tempFileStream.write(chunk);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (tempFileStream) {
|
|
182
|
+
tempFileStream.write(data);
|
|
183
|
+
}
|
|
184
|
+
chunks.push(data);
|
|
185
|
+
chunksBytes += data.length;
|
|
186
|
+
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
187
|
+
const removed = chunks.shift();
|
|
188
|
+
chunksBytes -= removed.length;
|
|
189
|
+
}
|
|
190
|
+
if (onUpdate) {
|
|
191
|
+
const fullBuffer = Buffer.concat(chunks);
|
|
192
|
+
const fullText = fullBuffer.toString("utf-8");
|
|
193
|
+
const truncation = truncateTail(fullText);
|
|
194
|
+
onUpdate({
|
|
195
|
+
content: [{ type: "text", text: truncation.content || "" }],
|
|
196
|
+
details: {
|
|
197
|
+
exitCode: null,
|
|
198
|
+
signal: null,
|
|
199
|
+
timedOut: false,
|
|
200
|
+
truncation: truncation.truncated ? {
|
|
201
|
+
truncated: true,
|
|
202
|
+
truncatedBy: truncation.truncatedBy,
|
|
203
|
+
totalLines: truncation.totalLines,
|
|
204
|
+
outputLines: truncation.outputLines
|
|
205
|
+
} : void 0,
|
|
206
|
+
fullOutputPath: tempFilePath
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
child.stderr?.on("data", (data) => {
|
|
212
|
+
totalBytes += data.length;
|
|
213
|
+
if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
|
|
214
|
+
tempFilePath = getTempFilePath();
|
|
215
|
+
tempFileStream = createWriteStream(tempFilePath);
|
|
216
|
+
for (const chunk of chunks) {
|
|
217
|
+
tempFileStream.write(chunk);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (tempFileStream) {
|
|
221
|
+
tempFileStream.write(data);
|
|
222
|
+
}
|
|
223
|
+
chunks.push(data);
|
|
224
|
+
chunksBytes += data.length;
|
|
225
|
+
while (chunksBytes > maxChunksBytes && chunks.length > 1) {
|
|
226
|
+
const removed = chunks.shift();
|
|
227
|
+
chunksBytes -= removed.length;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
child.on("close", (code, sig) => {
|
|
231
|
+
clearTimeout(timeoutId);
|
|
232
|
+
signal?.removeEventListener("abort", onAbort);
|
|
233
|
+
if (tempFileStream) {
|
|
234
|
+
tempFileStream.end();
|
|
235
|
+
}
|
|
236
|
+
const fullBuffer = Buffer.concat(chunks);
|
|
237
|
+
let fullOutput = fullBuffer.toString("utf-8");
|
|
238
|
+
const truncation = truncateTail(fullOutput);
|
|
239
|
+
let outputText = truncation.content || "(no output)";
|
|
240
|
+
const details = {
|
|
241
|
+
exitCode: code,
|
|
242
|
+
signal: sig?.toString() ?? null,
|
|
243
|
+
timedOut
|
|
244
|
+
};
|
|
245
|
+
if (truncation.truncated) {
|
|
246
|
+
details.truncation = {
|
|
247
|
+
truncated: true,
|
|
248
|
+
truncatedBy: truncation.truncatedBy,
|
|
249
|
+
totalLines: truncation.totalLines,
|
|
250
|
+
outputLines: truncation.outputLines
|
|
251
|
+
};
|
|
252
|
+
details.fullOutputPath = tempFilePath;
|
|
253
|
+
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
254
|
+
const endLine = truncation.totalLines;
|
|
255
|
+
if (truncation.lastLinePartial) {
|
|
256
|
+
const lastLineSize = formatSize(
|
|
257
|
+
Buffer.byteLength(fullOutput.split("\n").pop() || "", "utf-8")
|
|
258
|
+
);
|
|
259
|
+
outputText += `
|
|
260
|
+
|
|
261
|
+
[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;
|
|
262
|
+
} else if (truncation.truncatedBy === "lines") {
|
|
263
|
+
outputText += `
|
|
264
|
+
|
|
265
|
+
[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;
|
|
266
|
+
} else {
|
|
267
|
+
outputText += `
|
|
268
|
+
|
|
269
|
+
[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (timedOut) {
|
|
273
|
+
outputText += `
|
|
274
|
+
|
|
275
|
+
[Command timed out after ${timeout ?? DEFAULT_TIMEOUT_SECONDS} seconds]`;
|
|
276
|
+
}
|
|
277
|
+
const content = [{ type: "text", text: outputText }];
|
|
278
|
+
if (code !== 0 && code !== null) {
|
|
279
|
+
reject(new Error(outputText));
|
|
280
|
+
} else {
|
|
281
|
+
resolve({ content, details });
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
child.on("error", (err) => {
|
|
285
|
+
clearTimeout(timeoutId);
|
|
286
|
+
signal?.removeEventListener("abort", onAbort);
|
|
287
|
+
if (tempFileStream) {
|
|
288
|
+
tempFileStream.end();
|
|
289
|
+
}
|
|
290
|
+
reject(err);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/capabilities/index.ts
|
|
298
|
+
function createCliHostCapabilities(cwd) {
|
|
299
|
+
const bashTool = createBashTool(cwd);
|
|
300
|
+
const capabilities = [
|
|
301
|
+
{
|
|
302
|
+
id: "bash",
|
|
303
|
+
description: "Node/TUI host capability for executing shell commands in the local workspace.",
|
|
304
|
+
tools: [bashTool]
|
|
305
|
+
}
|
|
306
|
+
];
|
|
307
|
+
return {
|
|
308
|
+
capabilities,
|
|
309
|
+
tools: capabilities.flatMap((capability) => capability.tools)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export {
|
|
314
|
+
createCliHostCapabilities
|
|
315
|
+
};
|