@sukeai/pi-logfwd 0.2.0 → 0.2.2

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
@@ -44,6 +44,14 @@ pi -e npm:@sukeai/pi-logfwd
44
44
 
45
45
  装完在 pi 里 `/reload`,内置 `bash` 即被替换——所有 shell 命令自动走实时转发(参数 `command` / `timeout` / `cwd` / `logFile` / `noPty` 可用)。想还原内置 bash:`pi remove npm:@sukeai/pi-logfwd` 后 `/reload`。
46
46
 
47
+ ## 工具结果标记
48
+
49
+ 每次调用的结果末尾会附加一行状态标记,命令失败**不会静默**:
50
+
51
+ - 退出码非零 → `(exit code: N)`(真退出码取自 Go 端 JSONL `exit` 事件,非 pi-logfwd 进程自身退出码)
52
+ - `timeout` 杀进程 → `(timed out after Ns, exit code 124)`(判据是 exit 事件的 `message: "killed by --timeout"`,命令自己 `exit 124` 不会被误判为超时)
53
+ - 正常退出(0)→ 无标记
54
+
47
55
  ## 平台支持
48
56
 
49
57
  二进制分发模型:npm **platform companion 包**(esbuild 同款机制)。主包把全部平台包列为 `optionalDependencies`,npm 只安装与当前 `os`/`cpu` 匹配的那一个,其余静默跳过;扩展在运行时按平台定位二进制。
@@ -137,11 +137,13 @@ export default function (pi: ExtensionAPI) {
137
137
  "Run a bash command in the current working directory through pi-logfwd (PTY, real-time " +
138
138
  "log forwarding). Output is streamed back in real time instead of being buffered until " +
139
139
  "the process exits; the conversation keeps the last 60000 chars and longer output is " +
140
- "mirrored to a temp file whose path is reported at the end. This tool replaces pi's " +
141
- "built-in buffered bash. NOT supported: interactive secret prompts (sudo/ssh password) " +
142
- "and GUI authorization dialogs - PTY can render a prompt but nothing can answer it, so " +
143
- "tell the user to run those manually. Optionally provide a timeout (seconds), cwd, " +
144
- "logFile to append to, or noPty.",
140
+ "mirrored to a temp file whose path is reported at the end. The real exit status is never " +
141
+ "silent: a non-zero exit appends `(exit code: N)` and a tool-timeout kill appends " +
142
+ "`(timed out after Ns, exit code 124)` at the end of the result. This tool replaces " +
143
+ "pi's built-in buffered bash. NOT supported: interactive secret prompts (sudo/ssh " +
144
+ "password) and GUI authorization dialogs - PTY can render a prompt but nothing can " +
145
+ "answer it, so tell the user to run those manually. Optionally provide a timeout " +
146
+ "(seconds), cwd, logFile to append to, or noPty.",
145
147
  promptSnippet: "Execute bash commands (ls, grep, find, etc.) with real-time streamed output (PTY)",
146
148
  promptGuidelines: [
147
149
  "You can inspect PI_* environment variables for current model and session details.",
@@ -209,17 +211,32 @@ export default function (pi: ExtensionAPI) {
209
211
  }
210
212
  };
211
213
 
214
+ // 消费 JSONL:output 事件进流式文本;exit 事件携带「真正」的命令退出码
215
+ // (超时被杀为 124 + message "killed by --timeout")。child(Go 进程) 的 close
216
+ // code 虽经 os.Exit 透传命令退出码,但 Go 自身报错/被杀时即偏离、且丢失
217
+ // timeout 的 message 与 durationMs;故一律以 exit 事件为准,close code 仅在
218
+ // 事件缺失时兜底。
219
+ type ExitInfo = { code: number | null; durationMs: number; message: string };
220
+ let exitEv: ExitInfo | null = null;
212
221
  const rl = createInterface({ input: child.stdout });
213
222
  rl.on("line", (line) => {
214
223
  try {
215
224
  const ev = JSON.parse(line);
216
- if (ev.event === "output") push(ev.data);
225
+ if (ev.event === "output") {
226
+ push(ev.data);
227
+ } else if (ev.event === "exit") {
228
+ exitEv = {
229
+ code: typeof ev.code === "number" ? ev.code : null,
230
+ durationMs: typeof ev.durationMs === "number" ? ev.durationMs : 0,
231
+ message: typeof ev.message === "string" ? ev.message : "",
232
+ };
233
+ }
217
234
  } catch {
218
235
  /* ignore malformed lines */
219
236
  }
220
237
  });
221
238
 
222
- let exitCode: number | null = null;
239
+ let goExitCode: number | null = null; // child(Go 进程) 自身退出码,仅作 exit 事件缺失时的兜底
223
240
  let errText = "";
224
241
  child.stderr.on("data", (d: Buffer) => (errText += d.toString()));
225
242
  child.on("error", (e: Error) => {
@@ -229,7 +246,7 @@ export default function (pi: ExtensionAPI) {
229
246
  errText += `pi-logfwd: ${e.message}\n`;
230
247
  }
231
248
  });
232
- child.on("close", (c) => (exitCode = c));
249
+ child.on("close", (c) => (goExitCode = c));
233
250
  signal?.addEventListener("abort", () => child.kill("SIGTERM"), { once: true });
234
251
 
235
252
  await new Promise<void>((resolve) => {
@@ -256,12 +273,36 @@ export default function (pi: ExtensionAPI) {
256
273
  }
257
274
  }
258
275
 
276
+ // 真退出码优先(exit 事件);只有 Go 进程异常退出(未发 exit 事件)时才退回其进程退出码。
277
+ const exitCode = exitEv ? exitEv.code : goExitCode;
278
+ // 超时判据只看 exit 事件 message:Go 端仅在 timeout 杀死时写 "killed by --timeout";
279
+ // 命令自己 exit 124(如 gnu timeout 惯例)时 message 为空,不应误判为超时。
280
+ const timedOut = /timeout/i.test(exitEv?.message ?? "");
281
+
259
282
  const text = tail || "(no output)";
283
+ const notes: string[] = [];
284
+ if (timedOut) {
285
+ const t =
286
+ typeof params.timeout === "number"
287
+ ? `${params.timeout}s`
288
+ : `${((exitEv?.durationMs ?? 0) / 1000).toFixed(1)}s`;
289
+ notes.push(`(timed out after ${t}, exit code 124)`);
290
+ } else if (exitCode !== null && exitCode !== 0) {
291
+ notes.push(`(exit code: ${exitCode})`);
292
+ }
293
+
260
294
  let resultText = errText ? `${text}\n${errText.trimEnd()}` : text;
295
+ if (notes.length) resultText += `\n${notes.join("\n")}`;
261
296
  if (logPath) resultText += `\n(输出超长,完整日志已存 ${logPath})`;
262
297
  return {
263
298
  content: [{ type: "text", text: resultText }],
264
- details: { exitCode, forwarded: true, logFile: params.logFile ?? null },
299
+ details: {
300
+ exitCode,
301
+ forwarded: true,
302
+ timedOut,
303
+ durationMs: exitEv?.durationMs ?? null,
304
+ logFile: params.logFile ?? null,
305
+ },
265
306
  };
266
307
  },
267
308
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sukeai/pi-logfwd",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "pi package: replaces the built-in bash tool with real-time log forwarding via the pi-logfwd Go binary (PTY, JSONL events, optional log file) / pi 实时命令日志转发扩展(bash 覆盖内置)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,10 +26,10 @@
26
26
  "typebox": "*"
27
27
  },
28
28
  "optionalDependencies": {
29
- "@sukeai/pi-logfwd-darwin-arm64": "0.2.0",
30
- "@sukeai/pi-logfwd-darwin-amd64": "0.2.0",
31
- "@sukeai/pi-logfwd-linux-arm64": "0.2.0",
32
- "@sukeai/pi-logfwd-linux-amd64": "0.2.0"
29
+ "@sukeai/pi-logfwd-darwin-arm64": "0.2.2",
30
+ "@sukeai/pi-logfwd-darwin-amd64": "0.2.2",
31
+ "@sukeai/pi-logfwd-linux-arm64": "0.2.2",
32
+ "@sukeai/pi-logfwd-linux-amd64": "0.2.2"
33
33
  },
34
34
  "pi": {
35
35
  "extensions": [