@trim21/personal-pi-extensions 0.1.655 → 0.1.659

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 CHANGED
@@ -43,8 +43,8 @@
43
43
  - **只有脚本输出进上下文**:`text(value)` / `console.log(...)` 与 `return` 值进入工具结果,
44
44
  中间的工具调用与它们的返回内容不会(也不在会话记录里留下工具调用条目)。
45
45
  - **脚本接口**:`tools` / `ALL_TOOLS` / `text` / `image` / `exit` / `console.*` /
46
- `store(key, value)` / `load(key)`;首行可选 `// @options: {"timeout_ms": 30000,
47
- "max_output_tokens": 10000}`。默认没有超时,靠调用方中止或 `timeout_ms` 结束。
46
+ `store(key, value)` / `load(key)`;首行可选 `// @options: {"max_output_tokens": 10000}`。
47
+ 脚本没有超时:死循环由调用方中止(Esc)结束,等嵌套调用返回(含用户审批弹窗)多久都不算超时。
48
48
  - **store** 写在会话的自定义 entry 上(跟随分支),输出超过 `max_output_tokens` 时头尾
49
49
  截断并把全文落到 `$TMPDIR/pi-codemode-*.txt`。
50
50
  - **构建**:worker 入口是 esbuild 产物 `src/codemode/worker.js`(随仓库提交,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.655",
3
+ "version": "0.1.659",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * 每次执行起一个新的 worker:主线程把注册时编好的 wasm 模块连同脚本、可调用工具、
5
5
  * 初始 store 一起发过去,worker 里建 QuickJS VM 跑脚本,脚本的嵌套调用与输出回传,
6
- * 主线程执行工具后把结果发回去。超时/中止由主线程 `worker.terminate()` 负责,因此
7
- * 不需要取消消息。
6
+ * 主线程执行工具后把结果发回去。脚本没有超时,只有调用方的中止:中止时主线程直接
7
+ * `worker.terminate()`,因此不需要取消消息。
8
8
  *
9
9
  * worker 边界两侧都做 typebox 校验:worker 里跑的是模型写的代码,回传的消息同样
10
10
  * 当外部输入对待。
@@ -60,12 +60,7 @@ const outputSchema = Type.Object({
60
60
  });
61
61
 
62
62
  const scriptErrorSchema = Type.Object({
63
- kind: Type.Union([
64
- Type.Literal("script"),
65
- Type.Literal("timeout"),
66
- Type.Literal("aborted"),
67
- Type.Literal("sandbox"),
68
- ]),
63
+ kind: Type.Union([Type.Literal("script"), Type.Literal("aborted"), Type.Literal("sandbox")]),
69
64
  name: Type.Optional(Type.String()),
70
65
  message: Type.String(),
71
66
  stack: Type.Optional(Type.String()),
@@ -104,7 +99,7 @@ export interface WorkerBootstrap {
104
99
  export type CodemodeOutputItem =
105
100
  { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
106
101
 
107
- export type ScriptErrorKind = "script" | "timeout" | "aborted" | "sandbox";
102
+ export type ScriptErrorKind = "script" | "aborted" | "sandbox";
108
103
 
109
104
  export interface ScriptError {
110
105
  kind: ScriptErrorKind;
@@ -42,7 +42,7 @@ export interface SandboxRunOptions {
42
42
  code: string;
43
43
  tools: readonly ScriptTool[];
44
44
  store: Record<string, unknown>;
45
- timeoutMs: number;
45
+ /** 调用方中止本次执行;脚本没有自己的超时,死循环只能靠它结束。 */
46
46
  signal?: AbortSignal;
47
47
  /** 每次嵌套调用:由调用方执行工具并把结果回给脚本。 */
48
48
  onCall(request: { id: number; name: string; args: unknown }): Promise<SandboxCallOutcome>;
@@ -84,7 +84,6 @@ function runInWorker(wasm: object, options: SandboxRunOptions): Promise<SandboxO
84
84
  const calls: ScriptCall[] = [];
85
85
  const started = new Map<number, { name: string; at: number }>();
86
86
  let settled = false;
87
- let deadline: NodeJS.Timeout | undefined;
88
87
 
89
88
  const finish = (
90
89
  body:
@@ -95,9 +94,6 @@ function runInWorker(wasm: object, options: SandboxRunOptions): Promise<SandboxO
95
94
  return;
96
95
  }
97
96
  settled = true;
98
- if (deadline) {
99
- clearTimeout(deadline);
100
- }
101
97
  void worker.terminate();
102
98
  resolve({ ...body, output, calls });
103
99
  };
@@ -106,14 +102,6 @@ function runInWorker(wasm: object, options: SandboxRunOptions): Promise<SandboxO
106
102
  finish({ ok: false, error: { kind, message }, writes: NO_WRITES });
107
103
  };
108
104
 
109
- // 默认没有超时(Infinity):setTimeout 会把 Infinity 当成 1ms,必须显式判断
110
- if (Number.isFinite(options.timeoutMs)) {
111
- deadline = setTimeout(() => {
112
- fail(`the script exceeded its ${options.timeoutMs}ms deadline`, "timeout");
113
- }, options.timeoutMs);
114
- deadline.unref();
115
- }
116
-
117
105
  if (options.signal) {
118
106
  if (options.signal.aborted) {
119
107
  fail("codemode execution was aborted", "aborted");
@@ -1,19 +1,14 @@
1
1
  /**
2
- * codemode 脚本源码的解析:首行可选的 `// @options: {"max_output_tokens":…, "timeout_ms":…}`。
2
+ * codemode 脚本源码的解析:首行可选的 `// @options: {"max_output_tokens":…}`。
3
3
  *
4
4
  * 选项行与脚本首行共用一行,解析后原样保留为空行,脚本里报错的行号因此与用户写的
5
5
  * 一致。解析失败(空输入、JSON 非法、未知字段、只有选项行没有代码)直接报错。
6
6
  */
7
7
 
8
8
  const OPTIONS_PREFIX = "// @options:";
9
- const SUPPORTED_FIELDS = ["max_output_tokens", "timeout_ms"] as const;
9
+ const SUPPORTED_FIELDS = ["max_output_tokens"] as const;
10
10
 
11
11
  export const DEFAULT_OUTPUT_TOKENS = 10_000;
12
- /**
13
- * 脚本默认没有超时:编排一批工具调用本来就可能跑几分钟(例如批量读大文件)。
14
- * 调用方中止与 `// @options: {"timeout_ms": …}` 都能提前结束。
15
- */
16
- export const DEFAULT_TIMEOUT_MS = Infinity;
17
12
 
18
13
  export class CodemodeSourceError extends Error {
19
14
  constructor(message: string) {
@@ -24,7 +19,6 @@ export class CodemodeSourceError extends Error {
24
19
 
25
20
  export interface CodemodeSourceOptions {
26
21
  maxOutputTokens: number;
27
- timeoutMs: number;
28
22
  }
29
23
 
30
24
  export interface ParsedCodemodeSource {
@@ -54,11 +48,7 @@ function parseOptions(json: string): Partial<CodemodeSourceOptions> {
54
48
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
55
49
  throw new CodemodeSourceError(`${OPTIONS_PREFIX} ${key} must be a positive number`);
56
50
  }
57
- if (key === "max_output_tokens") {
58
- options.maxOutputTokens = value;
59
- } else {
60
- options.timeoutMs = value;
61
- }
51
+ options.maxOutputTokens = value;
62
52
  }
63
53
  return options;
64
54
  }
@@ -22,12 +22,7 @@ import { type ToolBus, toolResultText } from "../lib/tool-bus.js";
22
22
  import { renderDeclarations, toScriptTools } from "./declarations.js";
23
23
  import type { CodemodeOutputItem, ScriptError, StoreWrites } from "./protocol.js";
24
24
  import { type CodemodeSandbox, createCodemodeSandbox, type ScriptCall } from "./sandbox.js";
25
- import {
26
- CODEMODE_SOURCE_GRAMMAR,
27
- DEFAULT_OUTPUT_TOKENS,
28
- DEFAULT_TIMEOUT_MS,
29
- parseCodemodeSource,
30
- } from "./source.js";
25
+ import { CODEMODE_SOURCE_GRAMMAR, DEFAULT_OUTPUT_TOKENS, parseCodemodeSource } from "./source.js";
31
26
 
32
27
  export const CODEMODE_TOOL_NAME = "codemode";
33
28
 
@@ -230,7 +225,7 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
230
225
  parameters: Type.Object({
231
226
  code: Type.String({
232
227
  description:
233
- 'Raw JavaScript source. Top-level await and return work. May start with a `// @options: {"timeout_ms": 30000}` line.',
228
+ 'Raw JavaScript source. Top-level await and return work. May start with a `// @options: {"max_output_tokens": 10000}` line.',
234
229
  }),
235
230
  }),
236
231
  constrainedSampling: {
@@ -241,12 +236,10 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
241
236
 
242
237
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
243
238
  let code: string;
244
- let timeoutMs = DEFAULT_TIMEOUT_MS;
245
239
  let maxOutputTokens = DEFAULT_OUTPUT_TOKENS;
246
240
  try {
247
241
  const parsed = parseCodemodeSource(params.code);
248
242
  code = parsed.code;
249
- timeoutMs = parsed.options.timeoutMs ?? timeoutMs;
250
243
  maxOutputTokens = parsed.options.maxOutputTokens ?? maxOutputTokens;
251
244
  } catch (error) {
252
245
  const message = error instanceof Error ? error.message : String(error);
@@ -264,7 +257,6 @@ export function createCodemodeTools(pi: ExtensionAPI): CodemodeTools {
264
257
  code,
265
258
  tools: scriptTools,
266
259
  store: readStore(ctx),
267
- timeoutMs,
268
260
  signal,
269
261
  onOutput: (items) => {
270
262
  const text = textOf(items);
@@ -5,8 +5,7 @@ import { JSException, MAX_STACK_SIZE, QuickJS } from "quickjs-wasi";
5
5
  // src/codemode/prelude.ts
6
6
  var MAX_STORE_VALUE_CHARS = 256 * 1024;
7
7
  var MAX_STORE_TOTAL_CHARS = 1024 * 1024;
8
- var IMAGE_HELPER_EXPECTS =
9
- "image expects a non-empty image URL string, an object with image_url, or a raw MCP image block";
8
+ var IMAGE_HELPER_EXPECTS = "image expects a non-empty image URL string, an object with image_url, or a raw MCP image block";
10
9
  var PRELUDE_SOURCE = String.raw`(function (bridge, toolsJson, storeJson) {
11
10
  "use strict";
12
11
  const stringify = JSON.stringify;
@@ -288,70 +287,65 @@ import { Type } from "typebox";
288
287
  import { Value } from "typebox/value";
289
288
  var outputItemSchema = Type.Union([
290
289
  Type.Object({ type: Type.Literal("text"), text: Type.String() }),
291
- Type.Object({ type: Type.Literal("image"), data: Type.String(), mimeType: Type.String() }),
290
+ Type.Object({ type: Type.Literal("image"), data: Type.String(), mimeType: Type.String() })
292
291
  ]);
293
292
  var toolDeclSchema = Type.Object({
294
293
  name: Type.String(),
295
- description: Type.Optional(Type.String()),
294
+ description: Type.Optional(Type.String())
296
295
  });
297
296
  var startSchema = Type.Object({
298
297
  t: Type.Literal("start"),
299
298
  code: Type.String(),
300
299
  tools: Type.Array(toolDeclSchema),
301
- store: Type.Record(Type.String(), Type.Unknown()),
300
+ store: Type.Record(Type.String(), Type.Unknown())
302
301
  });
303
302
  var resultSchema = Type.Union([
304
303
  Type.Object({
305
304
  t: Type.Literal("result"),
306
305
  id: Type.Number(),
307
306
  ok: Type.Literal(true),
308
- value: Type.Unknown(),
307
+ value: Type.Unknown()
309
308
  }),
310
309
  Type.Object({
311
310
  t: Type.Literal("result"),
312
311
  id: Type.Number(),
313
312
  ok: Type.Literal(false),
314
- error: Type.String(),
315
- }),
313
+ error: Type.String()
314
+ })
316
315
  ]);
317
316
  var callSchema = Type.Object({
318
317
  t: Type.Literal("call"),
319
318
  id: Type.Number(),
320
319
  name: Type.String(),
321
- args: Type.Unknown(),
320
+ args: Type.Unknown()
322
321
  });
323
322
  var outputSchema = Type.Object({
324
323
  t: Type.Literal("output"),
325
- items: Type.Array(outputItemSchema),
324
+ items: Type.Array(outputItemSchema)
326
325
  });
327
326
  var scriptErrorSchema = Type.Object({
328
- kind: Type.Union([
329
- Type.Literal("script"),
330
- Type.Literal("timeout"),
331
- Type.Literal("aborted"),
332
- Type.Literal("sandbox"),
333
- ]),
327
+ kind: Type.Union([Type.Literal("script"), Type.Literal("aborted"), Type.Literal("sandbox")]),
334
328
  name: Type.Optional(Type.String()),
335
329
  message: Type.String(),
336
- stack: Type.Optional(Type.String()),
330
+ stack: Type.Optional(Type.String())
337
331
  });
338
332
  var storeWritesSchema = Type.Object({
339
333
  set: Type.Record(Type.String(), Type.Unknown()),
340
- delete: Type.Array(Type.String()),
334
+ delete: Type.Array(Type.String())
341
335
  });
342
336
  var doneSchema = Type.Union([
343
337
  Type.Object({
344
338
  t: Type.Literal("done"),
345
339
  ok: Type.Literal(true),
346
340
  value: Type.Unknown(),
347
- writes: storeWritesSchema,
341
+ writes: storeWritesSchema
348
342
  }),
349
343
  Type.Object({
350
344
  t: Type.Literal("done"),
351
345
  ok: Type.Literal(false),
352
346
  error: scriptErrorSchema,
353
- writes: storeWritesSchema,
354
- }),
347
+ writes: storeWritesSchema
348
+ })
355
349
  ]);
356
350
  var hostMessageSchema = Type.Union([startSchema, resultSchema]);
357
351
  var workerMessageSchema = Type.Union([callSchema, outputSchema, doneSchema]);
@@ -369,7 +363,7 @@ function decodeHostMessage(value) {
369
363
  // src/codemode/worker.ts
370
364
  var MEMORY_LIMIT_BYTES = 512 * 1024 * 1024;
371
365
  function toScriptIdentifier(name) {
372
- return name.replaceAll(/[^A-Za-z0-9_$]/g, "_").replaceAll(/^(\d)/, "_$1");
366
+ return name.replaceAll(/[^A-Za-z0-9_$]/g, "_").replaceAll(/^\d/g, "_");
373
367
  }
374
368
  function discardOutput(memory) {
375
369
  return {
@@ -381,7 +375,7 @@ function discardOutput(memory) {
381
375
  }
382
376
  view.setUint32(nwrittenPtr, written, true);
383
377
  return 0;
384
- },
378
+ }
385
379
  };
386
380
  }
387
381
  function post(message) {
@@ -427,61 +421,60 @@ async function runScript(wasm, start) {
427
421
  memoryLimit: MEMORY_LIMIT_BYTES,
428
422
  // 没有这个上限时深递归会打穿 wasm 栈变成 trap,而不是脚本里可捕获的 RangeError
429
423
  maxStackSize: MAX_STACK_SIZE,
430
- wasi: discardOutput,
424
+ wasi: discardOutput
431
425
  });
432
- const bridge = vm.newFunction("bridge", (kind, a, b, c) => {
433
- const stringOr = (value, fallback) =>
434
- value === void 0 || value.isUndefined ? fallback : value.toString();
435
- switch (kind.toString()) {
436
- case "call": {
437
- post({
438
- t: "call",
439
- id: a.toNumber(),
440
- name: stringOr(b, ""),
441
- args: c === void 0 || c.isUndefined ? void 0 : JSON.parse(c.toString()),
442
- });
443
- break;
444
- }
445
- case "output": {
446
- post({
447
- t: "output",
448
- items:
449
- a.toString() === "image"
450
- ? [
451
- {
452
- type: "image",
453
- data: stringOr(b, ""),
454
- mimeType: stringOr(c, "application/octet-stream"),
455
- },
456
- ]
457
- : [{ type: "text", text: stringOr(b, "") }],
458
- });
459
- break;
460
- }
461
- case "done": {
462
- const writes = c === void 0 || c.isUndefined ? emptyWrites() : parseWrites(c.toString());
463
- if (a.toBoolean()) {
426
+ const bridge = vm.newFunction(
427
+ "bridge",
428
+ (kind, a, b, c) => {
429
+ const stringOr = (value, fallback) => value === void 0 || value.isUndefined ? fallback : value.toString();
430
+ switch (kind.toString()) {
431
+ case "call": {
464
432
  post({
465
- t: "done",
466
- ok: true,
467
- value: b === void 0 || b.isUndefined ? void 0 : JSON.parse(b.toString()),
468
- writes,
433
+ t: "call",
434
+ id: a.toNumber(),
435
+ name: stringOr(b, ""),
436
+ args: c === void 0 || c.isUndefined ? void 0 : JSON.parse(c.toString())
469
437
  });
470
- } else {
438
+ break;
439
+ }
440
+ case "output": {
471
441
  post({
472
- t: "done",
473
- ok: false,
474
- error: scriptError(b === void 0 ? void 0 : b.toString(), "script"),
475
- writes,
442
+ t: "output",
443
+ items: a.toString() === "image" ? [
444
+ {
445
+ type: "image",
446
+ data: stringOr(b, ""),
447
+ mimeType: stringOr(c, "application/octet-stream")
448
+ }
449
+ ] : [{ type: "text", text: stringOr(b, "") }]
476
450
  });
451
+ break;
452
+ }
453
+ case "done": {
454
+ const writes = c === void 0 || c.isUndefined ? emptyWrites() : parseWrites(c.toString());
455
+ if (a.toBoolean()) {
456
+ post({
457
+ t: "done",
458
+ ok: true,
459
+ value: b === void 0 || b.isUndefined ? void 0 : JSON.parse(b.toString()),
460
+ writes
461
+ });
462
+ } else {
463
+ post({
464
+ t: "done",
465
+ ok: false,
466
+ error: scriptError(b === void 0 ? void 0 : b.toString(), "script"),
467
+ writes
468
+ });
469
+ }
470
+ break;
477
471
  }
478
- break;
479
472
  }
473
+ return vm.undefined;
480
474
  }
481
- return vm.undefined;
482
- });
483
- const api = vm.withScope((scope) =>
484
- scope.escape(
475
+ );
476
+ const api = vm.withScope(
477
+ (scope) => scope.escape(
485
478
  vm.callFunction(
486
479
  vm.evalCode(PRELUDE_SOURCE, "codemode-prelude.js"),
487
480
  vm.undefined,
@@ -491,13 +484,13 @@ async function runScript(wasm, start) {
491
484
  start.tools.map((tool) => ({
492
485
  name: tool.name,
493
486
  jsName: toScriptIdentifier(tool.name),
494
- description: tool.description,
495
- })),
496
- ),
487
+ description: tool.description
488
+ }))
489
+ )
497
490
  ),
498
- vm.newString(JSON.stringify(start.store)),
499
- ),
500
- ),
491
+ vm.newString(JSON.stringify(start.store))
492
+ )
493
+ )
501
494
  );
502
495
  const settle = api.getProp("settle");
503
496
  const run = api.getProp("run");
@@ -519,21 +512,18 @@ async function runScript(wasm, start) {
519
512
  api,
520
513
  vm.newNumber(result.id),
521
514
  result.ok ? vm.true : vm.false,
522
- result.ok
523
- ? result.value === void 0
524
- ? vm.undefined
525
- : vm.newString(JSON.stringify(result.value))
526
- : vm.newString(result.error),
515
+ result.ok ? result.value === void 0 ? vm.undefined : vm.newString(JSON.stringify(result.value)) : vm.newString(result.error)
527
516
  );
528
517
  });
529
- } catch {}
518
+ } catch {
519
+ }
530
520
  drain();
531
521
  });
532
522
  try {
533
523
  const fn = vm.evalCode(
534
524
  `(async (tools, console) => {${start.code}
535
525
  })`,
536
- "codemode.js",
526
+ "codemode.js"
537
527
  );
538
528
  vm.callFunction(run, api, fn).dispose();
539
529
  fn.dispose();
@@ -545,16 +535,16 @@ async function runScript(wasm, start) {
545
535
  ok: false,
546
536
  error: scriptError(
547
537
  JSON.stringify({ name: error.name, message: error.message, stack: error.stack }),
548
- "script",
538
+ "script"
549
539
  ),
550
- writes: emptyWrites(),
540
+ writes: emptyWrites()
551
541
  });
552
542
  } else {
553
543
  post({
554
544
  t: "done",
555
545
  ok: false,
556
546
  error: { kind: "sandbox", message: error instanceof Error ? error.message : String(error) },
557
- writes: emptyWrites(),
547
+ writes: emptyWrites()
558
548
  });
559
549
  }
560
550
  }
@@ -572,7 +562,7 @@ function main() {
572
562
  t: "done",
573
563
  ok: false,
574
564
  error: { kind: "sandbox", message: "codemode worker: expected a start message first" },
575
- writes: emptyWrites(),
565
+ writes: emptyWrites()
576
566
  });
577
567
  return;
578
568
  }
@@ -580,4 +570,6 @@ function main() {
580
570
  });
581
571
  }
582
572
  main();
583
- export { toScriptIdentifier };
573
+ export {
574
+ toScriptIdentifier
575
+ };
@@ -28,7 +28,7 @@ const MEMORY_LIMIT_BYTES = 512 * 1024 * 1024;
28
28
 
29
29
  /** 工具名到脚本标识符的归一化:非法字符换成 `_`,数字开头补 `_`。 */
30
30
  export function toScriptIdentifier(name: string): string {
31
- return name.replaceAll(/[^A-Za-z0-9_$]/g, "_").replaceAll(/^(\d)/, "_$1");
31
+ return name.replaceAll(/[^A-Za-z0-9_$]/g, "_").replaceAll(/^\d/g, "_");
32
32
  }
33
33
 
34
34
  /**