@trim21/personal-pi-extensions 0.1.451 → 0.1.452

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.451",
3
+ "version": "0.1.452",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
package/src/aft/bridge.ts CHANGED
@@ -17,11 +17,13 @@ import {
17
17
  readConfigTiers,
18
18
  resolveCortexKitConfigPaths,
19
19
  resolveCortexKitStorageRoot,
20
+ setActiveLogger,
20
21
  timeoutForCommand,
21
22
  } from "@cortexkit/aft-bridge";
22
23
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
23
24
 
24
25
  import type { SemanticRemote } from "./config.js";
26
+ import { bridgeLogger } from "./logger.js";
25
27
 
26
28
  /** Pi 会话 ID:Rust 侧用它做 session 作用域(undo/checkpoint),感知工具可留空。 */
27
29
  export function resolveSessionId(extCtx: ExtensionContext): string | undefined {
@@ -78,6 +80,9 @@ export const SEMANTIC_API_KEY_ENV = "AFT_SEMANTIC_API_KEY";
78
80
  * 两者都缺省时不注入任何凭据(无鉴权端点直接可用)。
79
81
  */
80
82
  export async function createAftPool(cwd: string, semantic?: SemanticRemote): Promise<AftPool> {
83
+ // 必须在任何 bridge 代码运行前注册:不设 logger 时 aft-bridge 会把 child
84
+ // stderr / 生命周期日志 fallback 到 console.error,raw 输出打进 pi 的 stderr 破坏 TUI。
85
+ setActiveLogger(bridgeLogger);
81
86
  const binaryPath = await resolveAftBinary();
82
87
  const paths = resolveCortexKitConfigPaths(cwd);
83
88
  const childEnv: Record<string, string> = {
@@ -109,7 +114,7 @@ export async function createAftPool(cwd: string, semantic?: SemanticRemote): Pro
109
114
  const pool = await createAftTransportPool({
110
115
  harness: "pi",
111
116
  binaryPath,
112
- poolOptions: { childEnv },
117
+ poolOptions: { childEnv, logger: bridgeLogger },
113
118
  configOverrides: {
114
119
  storage_dir: resolveCortexKitStorageRoot(),
115
120
  cortexkit_user_config_path: paths.userConfigPath,
@@ -0,0 +1,117 @@
1
+ /**
2
+ * AFT 扩展日志:落盘到 agent dir 的 tmp/(<agentDir>/tmp/aft-plugin.log)。
3
+ *
4
+ * aft-bridge 会把 aft 子进程的 stderr 逐行转发给 logger;不设置 logger 时它
5
+ * fallback 到 console.error,raw 文本直接打进 pi 进程的 stderr,破坏 TUI。
6
+ * 这里用 RotatingLogSink(带轮转的异步追加写)承接,日志只进文件不进终端。
7
+ */
8
+
9
+ import { join } from "node:path";
10
+
11
+ import { RotatingLogSink } from "@cortexkit/aft-bridge";
12
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
13
+
14
+ const TAG = "[aft-pi]";
15
+ const FLUSH_INTERVAL_MS = 500;
16
+ const BUFFER_SIZE_LIMIT = 50;
17
+
18
+ function logFilePath(): string {
19
+ return join(getAgentDir(), "tmp", "aft-plugin.log");
20
+ }
21
+
22
+ function createLogger() {
23
+ let sink: RotatingLogSink | null = null;
24
+ let buffer: string[] = [];
25
+ let flushTimer: ReturnType<typeof setTimeout> | null = null;
26
+
27
+ // sink 懒创建:getAgentDir() 读 PI_CODING_AGENT_DIR,测试里先改 env 再触发写入。
28
+ function getSink(): RotatingLogSink {
29
+ sink ??= new RotatingLogSink(logFilePath());
30
+ return sink;
31
+ }
32
+
33
+ function flush(): void {
34
+ if (buffer.length === 0) return;
35
+ const data = buffer.join("");
36
+ buffer = [];
37
+ try {
38
+ getSink().append(data);
39
+ } catch {
40
+ // 日志绝不抛错
41
+ }
42
+ }
43
+
44
+ function scheduleFlush(): void {
45
+ if (flushTimer) return;
46
+ flushTimer = setTimeout(() => {
47
+ flushTimer = null;
48
+ flush();
49
+ }, FLUSH_INTERVAL_MS);
50
+ if (typeof flushTimer === "object" && "unref" in flushTimer) {
51
+ flushTimer.unref();
52
+ }
53
+ }
54
+
55
+ function write(level: string, message: string, sessionId?: string): void {
56
+ try {
57
+ const timestamp = new Date().toISOString();
58
+ const sessionPrefix = sessionId ? ` [${sessionId}]` : "";
59
+ buffer.push(`[${timestamp}] ${level} ${TAG}${sessionPrefix} ${message}\n`);
60
+ if (buffer.length >= BUFFER_SIZE_LIMIT) {
61
+ flush();
62
+ } else {
63
+ scheduleFlush();
64
+ }
65
+ } catch {
66
+ // 日志绝不抛错
67
+ }
68
+ }
69
+
70
+ return {
71
+ log: (message: string, sessionId?: string): void => write("INFO", message, sessionId),
72
+ warn: (message: string, sessionId?: string): void => write("WARN", message, sessionId),
73
+ error: (message: string, sessionId?: string): void => write("ERROR", message, sessionId),
74
+ getLogFilePath: logFilePath,
75
+ drain: async (): Promise<void> => {
76
+ flush();
77
+ if (sink) await sink.drain();
78
+ },
79
+ };
80
+ }
81
+
82
+ const logger = createLogger();
83
+
84
+ export function log(message: string): void {
85
+ logger.log(message);
86
+ }
87
+
88
+ export function warn(message: string): void {
89
+ logger.warn(message);
90
+ }
91
+
92
+ export function error(message: string): void {
93
+ logger.error(message);
94
+ }
95
+
96
+ export function getLogFilePath(): string {
97
+ return logger.getLogFilePath();
98
+ }
99
+
100
+ /** aft-bridge 的 Logger 适配:child stderr / bridge 生命周期日志都走这里。 */
101
+ export const bridgeLogger = {
102
+ log(message: string, meta?: { sessionId?: string }): void {
103
+ logger.log(message, meta?.sessionId);
104
+ },
105
+ warn(message: string, meta?: { sessionId?: string }): void {
106
+ logger.warn(message, meta?.sessionId);
107
+ },
108
+ error(message: string, meta?: { sessionId?: string }): void {
109
+ logger.error(message, meta?.sessionId);
110
+ },
111
+ getLogFilePath: (): string => logger.getLogFilePath(),
112
+ };
113
+
114
+ /** 立即落盘,供测试与关闭路径使用。 */
115
+ export async function drainLogger(): Promise<void> {
116
+ await logger.drain();
117
+ }