@trim21/personal-pi-extensions 0.1.680 → 0.1.683
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 +17 -10
- package/package.json +2 -2
- package/src/claude-code/shell.ts +167 -96
- package/src/codemode/tool.ts +8 -1
- package/src/codemode/worker.js +8521 -52
- package/src/opencode/bash.ts +158 -105
package/src/opencode/bash.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
|
|
4
5
|
import type { ExtensionAPI, TruncationResult } from "@earendil-works/pi-coding-agent";
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
} from "../bwrap/runtime.js";
|
|
14
15
|
import { resolveWorkdir } from "../lib/path.js";
|
|
15
16
|
import { createRequestPolicy } from "../lib/request-policy.js";
|
|
16
|
-
import type
|
|
17
|
+
import { defineStructuredTool, type ToolBus } from "../lib/tool-bus.js";
|
|
17
18
|
import { registerToolsOnSessionStart } from "../lib/tool-registration.js";
|
|
18
19
|
|
|
19
20
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
@@ -25,6 +26,39 @@ const CAPTURE_TRUNCATED_NOTICE = "[output capture truncated at the in-memory saf
|
|
|
25
26
|
/** Bash tool guidance, kept in markdown so it reads like documentation. */
|
|
26
27
|
const BASH_PROMPT = readFileSync(fileURLToPath(new URL("bash.md", import.meta.url)), "utf8").trim();
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* `bash` 的结构化结果:只有退出码与完整输出——命令非零退出不是失败,所以载荷里没有成败
|
|
31
|
+
* 标志,脚本直接读 `exitCode` 分支;超时/中止时 `exitCode` 为 `null`(原因在模型侧文本里)。
|
|
32
|
+
*/
|
|
33
|
+
const bashStructuredSchema = Type.Object({
|
|
34
|
+
exitCode: Type.Union([Type.Number(), Type.Null()], {
|
|
35
|
+
description: "Exit code of the command; null when it was killed (timeout or abort)",
|
|
36
|
+
}),
|
|
37
|
+
output: Type.String({
|
|
38
|
+
description:
|
|
39
|
+
"Complete output of the command (stdout and stderr merged). Never truncated, never mixed with tool-added notices.",
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 载荷里的输出:文本被截断时读回落盘的完整输出(读不到就退回那份截断文本),
|
|
45
|
+
* 因此脚本拿到的永远是命令真正输出的内容。
|
|
46
|
+
*/
|
|
47
|
+
async function fullOutput(
|
|
48
|
+
output: string,
|
|
49
|
+
truncation: TruncationResult,
|
|
50
|
+
spillPath: string | undefined,
|
|
51
|
+
): Promise<string> {
|
|
52
|
+
if (spillPath === undefined || !truncation.truncated) {
|
|
53
|
+
return output;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return await readFile(spillPath, "utf8");
|
|
57
|
+
} catch {
|
|
58
|
+
return output;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
28
62
|
/** opencode 风格截断提示(成功、超时、中断路径共用)。 */
|
|
29
63
|
function appendTruncationNotice(
|
|
30
64
|
text: string,
|
|
@@ -54,113 +88,132 @@ export default function opencodeBash(
|
|
|
54
88
|
}
|
|
55
89
|
|
|
56
90
|
export function registerBashTool(bus: ToolBus, pi: ExtensionAPI, runtime: BwrapRuntime): void {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
91
|
+
bus.register(
|
|
92
|
+
defineStructuredTool({
|
|
93
|
+
name: "bash",
|
|
94
|
+
label: "bash",
|
|
95
|
+
description: [
|
|
96
|
+
"Executes a given bash command synchronously and returns its output.",
|
|
97
|
+
"The default working directory is the current directory; use workdir to run elsewhere.",
|
|
98
|
+
"timeout is in milliseconds, defaults to 120000, and may not exceed 7200000.",
|
|
99
|
+
"Every command runs in the foreground. Background command execution is not supported; shell jobs are waited for before the tool returns.",
|
|
100
|
+
].join("\n"),
|
|
101
|
+
promptSnippet: "execute bash command",
|
|
102
|
+
promptGuidelines: [BASH_PROMPT],
|
|
103
|
+
parameters: Type.Object(
|
|
104
|
+
{
|
|
105
|
+
command: Type.String({ description: "The command to execute" }),
|
|
106
|
+
description: Type.Optional(
|
|
107
|
+
Type.String({ description: "Clear, concise description of the command" }),
|
|
108
|
+
),
|
|
109
|
+
workdir: Type.Optional(
|
|
110
|
+
Type.String({
|
|
111
|
+
description:
|
|
112
|
+
"Working directory to execute the command in. Defaults to the current directory; relative paths resolve from there. prefer use this argument over `cd ...`",
|
|
113
|
+
}),
|
|
114
|
+
),
|
|
115
|
+
timeout: Type.Optional(
|
|
116
|
+
Type.Number({
|
|
117
|
+
description: "Optional timeout in milliseconds (max 7200000)",
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
dangerouslyDisableSandbox: Type.Optional(
|
|
121
|
+
Type.Boolean({
|
|
122
|
+
description:
|
|
123
|
+
"Request one-time unsandboxed execution. The user must approve this request.",
|
|
124
|
+
}),
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
{ additionalProperties: false },
|
|
128
|
+
),
|
|
129
|
+
async execute(id, params, signal, onUpdate, ctx) {
|
|
130
|
+
const timeout = params.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
131
|
+
if (!Number.isFinite(timeout) || timeout <= 0 || timeout > MAX_TIMEOUT_MS) {
|
|
132
|
+
throw new Error(`timeout must be between 1 and ${MAX_TIMEOUT_MS} milliseconds`);
|
|
133
|
+
}
|
|
101
134
|
|
|
102
|
-
|
|
135
|
+
const cwd = params.workdir ? await resolveWorkdir(params.workdir, ctx.cwd) : ctx.cwd;
|
|
103
136
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
137
|
+
let result: Awaited<ReturnType<BwrapRuntime["execute"]>>;
|
|
138
|
+
try {
|
|
139
|
+
result = await runtime.execute({
|
|
140
|
+
ctx,
|
|
141
|
+
cwd,
|
|
142
|
+
toolCallId: id,
|
|
143
|
+
command: params.command,
|
|
144
|
+
timeout: timeout / 1000,
|
|
145
|
+
requestFullAccess: params.dangerouslyDisableSandbox,
|
|
146
|
+
description: params.description,
|
|
147
|
+
signal,
|
|
148
|
+
onUpdate,
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (!(error instanceof Error)) {
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
if (error instanceof BashInterruptedError) {
|
|
155
|
+
const text = appendTruncationNotice(
|
|
156
|
+
error.partial.output || "",
|
|
157
|
+
error.partial.truncation,
|
|
158
|
+
error.partial.fullOutputPath,
|
|
159
|
+
);
|
|
160
|
+
const status =
|
|
161
|
+
error.kind === "timeout"
|
|
162
|
+
? `Command exceeded timeout of ${timeout} ms. Retry with a larger timeout if the command is expected to take longer.`
|
|
163
|
+
: `Command aborted by user after ${formatElapsedSeconds(error.elapsedMs)}`;
|
|
164
|
+
const full = text ? `${text}\n\n${status}` : status;
|
|
165
|
+
// 对齐上游 opencode:超时与中断都不抛错,输出与状态文本一起返回;
|
|
166
|
+
// 超时可能是沙箱的网络限制导致的,附加沙箱状态(用户中断与沙箱无关)
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{ type: "text" as const, text: full },
|
|
170
|
+
...sandboxHintBlock(error.kind === "timeout" ? error.sandboxHint : undefined),
|
|
171
|
+
...sandboxHintBlock(error.sandboxReminder),
|
|
172
|
+
],
|
|
173
|
+
details: error.kind === "timeout" ? { timeout: true } : {},
|
|
174
|
+
structuredResult: {
|
|
175
|
+
ok: true as const,
|
|
176
|
+
value: {
|
|
177
|
+
exitCode: null,
|
|
178
|
+
output: await fullOutput(
|
|
179
|
+
error.partial.output,
|
|
180
|
+
error.partial.truncation,
|
|
181
|
+
error.partial.fullOutputPath,
|
|
182
|
+
),
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
119
187
|
throw error;
|
|
120
188
|
}
|
|
121
|
-
if (error instanceof BashInterruptedError) {
|
|
122
|
-
const text = appendTruncationNotice(
|
|
123
|
-
error.partial.output || "",
|
|
124
|
-
error.partial.truncation,
|
|
125
|
-
error.partial.fullOutputPath,
|
|
126
|
-
);
|
|
127
|
-
const status =
|
|
128
|
-
error.kind === "timeout"
|
|
129
|
-
? `Command exceeded timeout of ${timeout} ms. Retry with a larger timeout if the command is expected to take longer.`
|
|
130
|
-
: `Command aborted by user after ${formatElapsedSeconds(error.elapsedMs)}`;
|
|
131
|
-
const full = text ? `${text}\n\n${status}` : status;
|
|
132
|
-
// 对齐上游 opencode:超时与中断都不抛错,输出与状态文本一起返回;
|
|
133
|
-
// 超时可能是沙箱的网络限制导致的,附加沙箱状态(用户中断与沙箱无关)
|
|
134
|
-
return {
|
|
135
|
-
content: [
|
|
136
|
-
{ type: "text" as const, text: full },
|
|
137
|
-
...sandboxHintBlock(error.kind === "timeout" ? error.sandboxHint : undefined),
|
|
138
|
-
...sandboxHintBlock(error.sandboxReminder),
|
|
139
|
-
],
|
|
140
|
-
details: error.kind === "timeout" ? { timeout: true } : {},
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
145
189
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
190
|
+
// 命令失败(非 0 退出码)不抛错:输出与状态文本一起返回
|
|
191
|
+
let text = result.output || "(no output)";
|
|
192
|
+
text = appendTruncationNotice(text, result.truncation, result.fullOutputPath);
|
|
193
|
+
const failed = result.exitCode !== 0 && result.exitCode !== null;
|
|
194
|
+
return {
|
|
195
|
+
content: [
|
|
196
|
+
{ type: "text" as const, text },
|
|
197
|
+
{ type: "text" as const, text: `Command exited with code ${result.exitCode}.` },
|
|
198
|
+
// 失败可能是被沙箱的写边界或网络限制挡住的,附一块沙箱状态
|
|
199
|
+
...sandboxHintBlock(failed ? result.sandboxHint : undefined),
|
|
200
|
+
...sandboxHintBlock(result.sandboxReminder),
|
|
201
|
+
],
|
|
202
|
+
details: {
|
|
203
|
+
exitCode: result.exitCode,
|
|
204
|
+
truncated: result.truncation.truncated,
|
|
205
|
+
...(result.fullOutputPath && { fullOutputPath: result.fullOutputPath }),
|
|
206
|
+
},
|
|
207
|
+
structuredResult: {
|
|
208
|
+
ok: true as const,
|
|
209
|
+
value: {
|
|
210
|
+
exitCode: result.exitCode,
|
|
211
|
+
output: await fullOutput(result.output, result.truncation, result.fullOutputPath),
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
structuredSchema: bashStructuredSchema,
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
166
219
|
}
|