@trim21/personal-pi-extensions 0.1.20261012003405 → 0.1.20261012023047
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 -9
- package/package.json +1 -1
- package/src/codemode/declarations.ts +1 -0
- package/src/codemode/prelude.ts +27 -2
- package/src/codemode/protocol.ts +3 -37
- package/src/codemode/tool.ts +4 -12
- package/src/codemode/worker.js +85 -682
- package/src/codemode/worker.ts +61 -13
- package/src/gh/base.ts +38 -357
- package/src/gh/diff-stats.ts +100 -0
- package/src/gh/index.ts +1 -1
- package/src/gh/schemas.ts +4 -11
- package/src/gh/tools/download-release-assets.ts +43 -64
- package/src/gh/tools/get-workflow-jobs.ts +17 -31
- package/src/gh/tools/list-issues.ts +18 -31
- package/src/gh/tools/list-prs.ts +17 -30
- package/src/gh/tools/list-releases.ts +18 -36
- package/src/gh/tools/list-workflow-runs.ts +22 -34
- package/src/gh/tools/read-ci-logs.ts +24 -50
- package/src/gh/tools/read-issue-comments.ts +17 -25
- package/src/gh/tools/read-issue.ts +19 -25
- package/src/gh/tools/read-pr-comments.ts +34 -43
- package/src/gh/tools/read-pr-diff.ts +19 -32
- package/src/gh/tools/read-pr-status.ts +22 -33
- package/src/gh/tools/read-pr.ts +19 -21
- package/src/gh/tools/read-release.ts +19 -33
- package/src/gh/tools/read-repo.ts +16 -30
- package/src/gh/tools/wait-commit-checks.ts +33 -56
- package/src/gh/tools/wait-pr-checks.ts +27 -51
- package/src/gh/tools/watch-run.ts +33 -65
- package/src/lib/github.ts +0 -15
- package/src/lib/tool-bus.ts +163 -9
- package/src/skills/github-ci-logs/SKILL.md +38 -21
- package/src/gh/render.ts +0 -259
package/README.md
CHANGED
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
|
|
32
32
|
`codemode` 让模型写一段 JavaScript(作为 async 函数体,`await` 与 `return` 都可用),在
|
|
33
33
|
进程内的 worker 线程里用 QuickJS wasm VM 执行。VM 里没有 node、文件系统、网络、timer
|
|
34
|
-
或模块加载,脚本的出口只有 `call(name, args)
|
|
34
|
+
或模块加载,脚本的出口只有 `call(name, args)`、`fs.read` / `fs.write` 两个文件原语与内建的
|
|
35
|
+
`sleep`。
|
|
35
36
|
|
|
36
37
|
- **嵌套调用经工具总线**:`call("Bash", { command })` 最终执行的是 `Bash` 工具自己的
|
|
37
38
|
`execute`,所以工具的审批照常生效(Bash 沙箱外执行会弹自己的提权确认)。codemode
|
|
@@ -53,16 +54,18 @@
|
|
|
53
54
|
直接改,不进脚本)。
|
|
54
55
|
- **只有脚本输出进上下文**:`text(value)` / `console.log(...)` 与 `return` 值进入工具结果,
|
|
55
56
|
中间的工具调用与它们的返回内容不会(也不在会话记录里留下工具调用条目)。
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
- **返回值**:工具的结果就是结构化载荷,`call()` 直接把它解包成对象给脚本——gh-readonly
|
|
58
|
+
的工具给 REST 原物或从响应解析出的结构,`Bash` / `bash` 给 `{ exitCode, output }`
|
|
59
|
+
(`exitCode` 为 `null` 表示命令被超时/中止杀掉;命令非零退出是正常结果,直接读
|
|
60
|
+
`exitCode` 分支即可)。`{ ok: false, error }` 会 reject 成 `CallFailedError`
|
|
60
61
|
(脚本可按 `instanceof CallFailedError` 区分调用失败与自身运行期错误)。没有声明输出
|
|
61
|
-
|
|
62
|
+
结构的工具不进集合,`call()` 会报「not available in codemode」。
|
|
62
63
|
- **脚本接口**:`call` / `CallFailedError` / `ALL_TOOLS` / `fs.read` / `fs.write` /
|
|
63
|
-
`text` / `image` / `exit` / `console.*` / `store.set` / `store.get` / `store.list`
|
|
64
|
+
`text` / `image` / `exit` / `sleep` / `console.*` / `store.set` / `store.get` / `store.list`
|
|
64
65
|
(会话内持久的键值表);首行可选 `// @options: {"max_output_tokens": 10000}`。
|
|
65
66
|
脚本没有超时:死循环由调用方中止(Esc)结束,等嵌套调用返回(含用户审批弹窗)多久都不算超时。
|
|
67
|
+
`await sleep(seconds)`(秒,可为小数)是脚本唯一的等待手段,由 worker 线程自己的定时器驱动
|
|
68
|
+
(不经主线程、不产生进度帧),也没有上限——要等多久由脚本决定,不想等就中止。
|
|
66
69
|
- **工具描述**里给出每个可调用工具的 `declare function call(name, args): Promise<T>` 重载,
|
|
67
70
|
参数与返回类型都取自工具自己的 schema,所以模型在写脚本前就知道返回值形状。
|
|
68
71
|
- **store** 记在每次成功调用工具结果的 `details.store` 上(与 `src/lib/file-reads.ts` 的
|
|
@@ -94,7 +97,7 @@
|
|
|
94
97
|
"disabledTools": ["talk-*", { "tools": ["web_*"], "models": ["gpt-*"] }],
|
|
95
98
|
"enabledTools": [{ "tools": ["web_search"], "models": ["glm-*"] }],
|
|
96
99
|
// 只进 codemode、不交给模型的工具(须声明结构化输出才会生效)
|
|
97
|
-
"codemodeOnlyTools": [{ "tools": ["
|
|
100
|
+
"codemodeOnlyTools": [{ "tools": ["aft_*"], "models": ["deepseek-*"] }],
|
|
98
101
|
},
|
|
99
102
|
}
|
|
100
103
|
```
|
|
@@ -109,6 +112,9 @@
|
|
|
109
112
|
的工具进不了 codemode,因此会被忽略、保持直接可用,并在会话启动时给出一条 warning——
|
|
110
113
|
想真正移除工具用 `disabledTools`。
|
|
111
114
|
|
|
115
|
+
有些工具由代码固定为 codemode-only(例如 gh-readonly 那一组,见
|
|
116
|
+
[GitHub 只读工具](#github-只读工具)),不需要这条配置;`disabledTools` 对它们同样生效。
|
|
117
|
+
|
|
112
118
|
**判定时机是每个会话启动**:pi 在启动 / `/new` / `resume` / `/fork` / `/reload` 时重建
|
|
113
119
|
扩展,注册就发生在 `session_start` 里(模型在扩展加载期读不到),因此新会话按自己的
|
|
114
120
|
模型重新判定;**同一会话内 `/model` 切换不会改变工具集合**,换模型请开新会话。
|
|
@@ -471,11 +477,13 @@ pi -e ./src/aft/index.ts
|
|
|
471
477
|
|
|
472
478
|
GitHub 只读工具集。取数全部走 octokit REST(`src/lib/github-reads.ts` 的读取层 + `src/lib/github.ts` 的 accessor);只有「当前仓库」解析(`gh repo view`)与 token 获取(`gh auth token`)还用系统 [`gh`](https://cli.github.com/),因此 `gh` 不在 PATH 时整组不注册并在 session_start 报错;Windows 禁用。一个会话只 spawn 一次 `gh auth token`:三个子客户端(search / checks / reads)共用同一个 accessor。
|
|
473
479
|
|
|
480
|
+
**这一组工具只经 `codemode` 曝光、模型不能直接调用**:它们注册在工具总线上但不交给 pi,只能在脚本里 `call("read-github-issue", …)`。工具的返回值就是结构化载荷(REST 原物,或从响应解析出的统计 / 判定),没有面向模型的文本输出。想在某些环境或模型下彻底关掉它们,用 `disabledTools`(可带 `models`)。
|
|
481
|
+
|
|
474
482
|
- **Issue / PR**:`read-github-issue`、`list-github-issues`、`read-github-issue-comments`、`read-github-pr`、`list-github-prs`、`read-github-pr-diff`、`read-github-pr-status`、`read-github-pr-comments`
|
|
475
483
|
- **CI**:`read-github-ci-logs`、`list-github-workflow-runs`、`get-github-workflow-jobs`、`wait-github-pr-checks`、`wait-github-commit-checks`、`watch-github-run`
|
|
476
484
|
- **仓库 / 发布**:`read-github-repo`、`list-github-releases`、`read-github-release`、`download-github-release-assets`
|
|
477
485
|
|
|
478
|
-
`list-github-issues` / `list-github-prs` 带 `keywords` 时走 GitHub search API,不带 `keywords` 时走 REST 列表端点(state 缺省只搜 open,`state: "all"` 不加 state qualifier 覆盖 open + closed
|
|
486
|
+
`list-github-issues` / `list-github-prs` 带 `keywords` 时走 GitHub search API,不带 `keywords` 时走 REST 列表端点(state 缺省只搜 open,`state: "all"` 不加 state qualifier 覆盖 open + closed),载荷给全部命中。`wait-github-pr-checks` / `wait-github-commit-checks` 30 秒轮询、600 秒截止:任一 fail 即返回,全部 pass / skipped 才算通过,超时返回快照不抛错;检查快照还包含「已结束但没有 check run」的 check suite——fork PR 上等待维护者批准的 workflow 只存在于这一层,遇到它立即返回 `awaiting_approval`(不会自行开始,轮询无意义),条目名字取自对应的 workflow run。`read-github-ci-logs` 把日志落盘并只返回 step 索引,日志正文用脚本的 `fs.read(log_file)` 读。
|
|
479
487
|
|
|
480
488
|
出网代理:`~/.pi/agent/proxy.json`(`{ "proxy": "http://127.0.0.1:7890", "noProxy": "localhost" }`),缺省字段回退 `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` / `NO_PROXY` 环境变量。
|
|
481
489
|
|
package/package.json
CHANGED
|
@@ -152,6 +152,7 @@ export function renderDeclarations(tools: readonly ToolLike[]): string {
|
|
|
152
152
|
"declare function text(value: unknown): void;",
|
|
153
153
|
"declare function image(value: unknown): void;",
|
|
154
154
|
"declare function exit(): void;",
|
|
155
|
+
"declare function sleep(seconds: number): Promise<void>;",
|
|
155
156
|
"declare const store: { set(key: string, value: unknown): void; get(key: string): unknown; list(): string[] };",
|
|
156
157
|
].join("\n");
|
|
157
158
|
}
|
package/src/codemode/prelude.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* codemode 脚本侧的 prelude:在 QuickJS VM 里先于脚本求值,构建脚本能看到的全部
|
|
3
3
|
* 能力(`call` / `CallFailedError` / `ALL_TOOLS` / `fs` / `text` / `image` / `exit` /
|
|
4
|
-
* `console` / `store`),并把宿主桥接封在闭包里——脚本拿不到 `bridge` 本身。
|
|
4
|
+
* `sleep` / `console` / `store`),并把宿主桥接封在闭包里——脚本拿不到 `bridge` 本身。
|
|
5
5
|
*
|
|
6
6
|
* 值与参数过桥时都是 JSON 文本,本侧负责 parse/stringify;异步调用用一个 pending
|
|
7
7
|
* 表把 id 映射到 promise,由宿主在结果到达时 settle。每次调用失败(工具或 fs 原语)
|
|
8
8
|
* 都由宿主以 `ok: false` 回报,本侧统一 reject 成 `CallFailedError`(脚本可以按
|
|
9
9
|
* instanceof 区分「调用失败」与自己的运行期错误)。
|
|
10
10
|
*
|
|
11
|
-
* 求值结果是一个函数 `(bridge, toolsJson, storeJson) => { settle, run, stalled }`。
|
|
11
|
+
* 求值结果是一个函数 `(bridge, toolsJson, storeJson) => { settle, timerDone, run, stalled }`。
|
|
12
12
|
* `bridge(kind, a, b, c)`:
|
|
13
13
|
* - `"call"`(id, name, argsJson):工具,以及名字为 `fs.read` / `fs.write` 的 fs 原语
|
|
14
|
+
* - `"sleep"`(id, ms):等待,由 worker 线程自己的定时器在 ms 后调 `timerDone(id)`
|
|
14
15
|
* - `"output"`("text", text)/("image", data, mimeType)
|
|
15
16
|
* - `"done"`(ok, valueJsonOrErrorJson, writesJson)
|
|
16
17
|
*/
|
|
@@ -259,6 +260,22 @@ export const PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson
|
|
|
259
260
|
throw EXIT;
|
|
260
261
|
}
|
|
261
262
|
|
|
263
|
+
// 脚本唯一的等待手段:worker 线程在 ms 后调 timerDone(id) 解开它。沙箱里没有别的
|
|
264
|
+
// timer,也没有上限——睡多久由脚本决定,结束只能靠调用方中止。
|
|
265
|
+
function sleep(seconds) {
|
|
266
|
+
if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds < 0) {
|
|
267
|
+
throw new TypeErrorCtor(
|
|
268
|
+
"sleep(seconds) expects a non-negative number of seconds, got " + format(seconds),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return new Promise((resolve) => {
|
|
272
|
+
const id = nextId++;
|
|
273
|
+
// sleep 只会被 timerDone 成功解开,reject 用不到
|
|
274
|
+
pending.set(id, { resolve, reject: () => {} });
|
|
275
|
+
bridge("sleep", id, Math.round(seconds * 1000));
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
262
279
|
const console = {};
|
|
263
280
|
for (const level of ["log", "info", "warn", "error", "debug"]) {
|
|
264
281
|
console[level] = (...args) => {
|
|
@@ -275,6 +292,7 @@ export const PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson
|
|
|
275
292
|
Object.defineProperty(globalThis, "text", { value: text, enumerable: true });
|
|
276
293
|
Object.defineProperty(globalThis, "image", { value: image, enumerable: true });
|
|
277
294
|
Object.defineProperty(globalThis, "exit", { value: exit, enumerable: true });
|
|
295
|
+
Object.defineProperty(globalThis, "sleep", { value: sleep, enumerable: true });
|
|
278
296
|
|
|
279
297
|
return {
|
|
280
298
|
settle(id, ok, payload) {
|
|
@@ -294,6 +312,13 @@ export const PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson
|
|
|
294
312
|
}
|
|
295
313
|
entry.resolve(value);
|
|
296
314
|
},
|
|
315
|
+
// worker 线程的定时器到点后解开一次 sleep(成功路径,不带值)
|
|
316
|
+
timerDone(id) {
|
|
317
|
+
const entry = pending.get(id);
|
|
318
|
+
if (!entry) return;
|
|
319
|
+
pending.delete(id);
|
|
320
|
+
entry.resolve();
|
|
321
|
+
},
|
|
297
322
|
run(fn) {
|
|
298
323
|
let promise;
|
|
299
324
|
try {
|
package/src/codemode/protocol.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* 主线程执行工具后把结果发回去。脚本没有超时,只有调用方的中止:中止时主线程直接
|
|
7
7
|
* `worker.terminate()`,因此不需要取消消息。
|
|
8
8
|
*
|
|
9
|
-
* worker
|
|
10
|
-
*
|
|
9
|
+
* 校验只做一个方向:worker 里跑的是模型写的代码,它回传的消息由主线程用 typebox
|
|
10
|
+
* 当外部输入复核;反向的消息是主线程自己构造的,形状由 `HostMessage` 类型约束,
|
|
11
|
+
* worker 只按 `t` 分派。
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import { type TSchema, Type } from "typebox";
|
|
@@ -32,35 +33,6 @@ const outputItemSchema = Type.Union([
|
|
|
32
33
|
Type.Object({ type: Type.Literal("image"), data: Type.String(), mimeType: Type.String() }),
|
|
33
34
|
]);
|
|
34
35
|
|
|
35
|
-
const toolDeclSchema = Type.Object({
|
|
36
|
-
name: Type.String(),
|
|
37
|
-
description: Type.Optional(Type.String()),
|
|
38
|
-
/** 工具的 structuredSchema(TypeBox schema):脚本侧据此渲染 call() 的返回类型。 */
|
|
39
|
-
structuredSchema: Type.Optional(Type.Unknown()),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const startSchema = Type.Object({
|
|
43
|
-
t: Type.Literal("start"),
|
|
44
|
-
code: Type.String(),
|
|
45
|
-
tools: Type.Array(toolDeclSchema),
|
|
46
|
-
store: Type.Record(Type.String(), Type.Unknown()),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const resultSchema = Type.Union([
|
|
50
|
-
Type.Object({
|
|
51
|
-
t: Type.Literal("result"),
|
|
52
|
-
id: Type.Number(),
|
|
53
|
-
ok: Type.Literal(true),
|
|
54
|
-
value: Type.Unknown(),
|
|
55
|
-
}),
|
|
56
|
-
Type.Object({
|
|
57
|
-
t: Type.Literal("result"),
|
|
58
|
-
id: Type.Number(),
|
|
59
|
-
ok: Type.Literal(false),
|
|
60
|
-
error: Type.String(),
|
|
61
|
-
}),
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
36
|
const callSchema = Type.Object({
|
|
65
37
|
t: Type.Literal("call"),
|
|
66
38
|
id: Type.Number(),
|
|
@@ -100,7 +72,6 @@ const doneSchema = Type.Union([
|
|
|
100
72
|
}),
|
|
101
73
|
]);
|
|
102
74
|
|
|
103
|
-
const hostMessageSchema = Type.Union([startSchema, resultSchema]);
|
|
104
75
|
const workerMessageSchema = Type.Union([callSchema, outputFrameSchema, doneSchema]);
|
|
105
76
|
|
|
106
77
|
/** worker 启动数据:注册时编译好的 wasm 模块(结构化克隆可以带它跨线程)。 */
|
|
@@ -155,11 +126,6 @@ function decode<T>(schema: TSchema, value: unknown): DecodeResult<T> {
|
|
|
155
126
|
return { ok: true, frame: value as T };
|
|
156
127
|
}
|
|
157
128
|
|
|
158
|
-
/** 校验主线程发给 worker 的消息。 */
|
|
159
|
-
export function decodeHostMessage(value: unknown): DecodeResult<HostMessage> {
|
|
160
|
-
return decode<HostMessage>(hostMessageSchema, value);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
129
|
/** 校验 worker 发回主线程的消息。 */
|
|
164
130
|
export function decodeWorkerMessage(value: unknown): DecodeResult<WorkerMessage> {
|
|
165
131
|
return decode<WorkerMessage>(workerMessageSchema, value);
|
package/src/codemode/tool.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { Type } from "typebox";
|
|
|
24
24
|
import type { FileSnapshot, ReadsState } from "../lib/file-reads.js";
|
|
25
25
|
import type { ToolPendant } from "../lib/pendant.js";
|
|
26
26
|
import type { RequestPolicy } from "../lib/request-policy.js";
|
|
27
|
-
import { type ToolBus
|
|
27
|
+
import { type ToolBus } from "../lib/tool-bus.js";
|
|
28
28
|
import { renderDeclarations, toScriptTools } from "./declarations.js";
|
|
29
29
|
import { createCodemodeFs } from "./fs.js";
|
|
30
30
|
import type { CodemodeOutputItem, ScriptError, StoreWrites } from "./protocol.js";
|
|
@@ -113,6 +113,8 @@ function buildDescription(tools: readonly CallableTool[]): string {
|
|
|
113
113
|
" fields you need into `text(value)` / `return` — e.g. `text(result.items.map((item) => item.title))`.",
|
|
114
114
|
"- Do not stringify whole results (`JSON.stringify(result)`) or print every field the tool happened to",
|
|
115
115
|
" return: urls, ids and metadata you will not use cost context and buy nothing.",
|
|
116
|
+
"- `await sleep(seconds)` waits that many seconds (fractions allowed). It is the only way to",
|
|
117
|
+
' wait: the sandbox has no timers, and it beats `call("Bash", { command: "sleep 1" })`.',
|
|
116
118
|
"- `store.set(key, value)`, `store.get(key)` and `store.list()` are a small key/value store that",
|
|
117
119
|
" persists across codemode calls in this session; `store.set(key, undefined)` removes a key.",
|
|
118
120
|
"- The sandbox has no file system, network, timers or Node APIs: calling tools is its only",
|
|
@@ -380,17 +382,7 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
|
|
|
380
382
|
if (!callable.has(name)) {
|
|
381
383
|
return { ok: false, error: `Tool "${name}" is not available in codemode.` };
|
|
382
384
|
}
|
|
383
|
-
|
|
384
|
-
if (result.structuredResult) {
|
|
385
|
-
return result.structuredResult.ok
|
|
386
|
-
? { ok: true, value: result.structuredResult.value }
|
|
387
|
-
: { ok: false, error: result.structuredResult.error };
|
|
388
|
-
}
|
|
389
|
-
const text = toolResultText(result);
|
|
390
|
-
if (result.isError) {
|
|
391
|
-
return { ok: false, error: text };
|
|
392
|
-
}
|
|
393
|
-
return { ok: true, value: text };
|
|
385
|
+
return bus.executeCodemodeTool(name, args, { ctx, signal });
|
|
394
386
|
},
|
|
395
387
|
});
|
|
396
388
|
|