@zhushanwen/pi-ext-guards 0.4.3 → 0.5.0
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 +1 -1
- package/src/__tests__/predicates.test.ts +87 -2
- package/src/index.ts +58 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-ext-guards",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Shared runtime-environment guards for Pi extensions — oncePerProcess(key, fn) dedupes cross-session side-effect operations against pi factory re-invocation and handler accumulation (shared library, not a Pi extension, zero dependencies).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
// src/__tests__/predicates.test.ts
|
|
2
2
|
//
|
|
3
|
-
// isRecord / isEnoentError / isSubagentProcess
|
|
3
|
+
// isRecord / isEnoentError / isErrnoWithCode / isSubagentProcess / isTaijiHost /
|
|
4
|
+
// isTaijiGuiHost 单测
|
|
5
|
+
//(ext-simplify-17 D3/D2/D4 新导出 + isErrnoWithCode errno 守卫上收 +
|
|
6
|
+
// dmg-r1-7 宿主谓词跨包收敛):
|
|
4
7
|
// - isRecord 排数组严版:数组必须 false(钉值,防回退到允数组宽版)
|
|
5
8
|
// - isEnoentError scheduler 严版:仅 Error 子类 + code === "ENOENT" 才 true,
|
|
6
9
|
// 非 Error 对象带 code 是宽版才接受的形态,严版必须 false
|
|
10
|
+
// - isErrnoWithCode:isEnoentError 的 code 参数化形态,收窄纪律同源
|
|
7
11
|
// - isSubagentProcess 三态 + env 注入隔离 + 旧键宁缺勿污钉值(D4 重锚定)
|
|
12
|
+
// - isTaijiHost / isTaijiGuiHost:env 严格等值 + mode 收紧 rpc 真值表
|
|
13
|
+
// (自 plan state.ts / canvas host.ts 双实现收敛,dmg-r1-7)
|
|
8
14
|
|
|
9
15
|
import { describe, expect, it } from "vitest";
|
|
10
16
|
|
|
11
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
isEnoentError,
|
|
19
|
+
isErrnoWithCode,
|
|
20
|
+
isRecord,
|
|
21
|
+
isSubagentProcess,
|
|
22
|
+
isTaijiGuiHost,
|
|
23
|
+
isTaijiHost,
|
|
24
|
+
TAIJI_GUI_HOST_ENV,
|
|
25
|
+
} from "../index.ts";
|
|
12
26
|
|
|
13
27
|
describe("isRecord", () => {
|
|
14
28
|
it("对象 true", () => {
|
|
@@ -58,6 +72,32 @@ describe("isEnoentError", () => {
|
|
|
58
72
|
});
|
|
59
73
|
});
|
|
60
74
|
|
|
75
|
+
describe("isErrnoWithCode", () => {
|
|
76
|
+
it("Error 带 code=期望值 true(Node fs 错误的构造形态)", () => {
|
|
77
|
+
expect(isErrnoWithCode(Object.assign(new Error("exists"), { code: "EEXIST" }), "EEXIST")).toBe(
|
|
78
|
+
true,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("code 为其他值 false", () => {
|
|
83
|
+
expect(isErrnoWithCode(Object.assign(new Error("exists"), { code: "EEXIST" }), "EACCES")).toBe(
|
|
84
|
+
false,
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("普通 Error false(无 code 属性)", () => {
|
|
89
|
+
expect(isErrnoWithCode(new Error("x"), "EEXIST")).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("非 Error 对象带同名 code false(与 isEnoentError 同一严版口径)", () => {
|
|
93
|
+
expect(isErrnoWithCode({ code: "EEXIST" }, "EEXIST")).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("undefined false", () => {
|
|
97
|
+
expect(isErrnoWithCode(undefined, "EEXIST")).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
61
101
|
describe("isSubagentProcess", () => {
|
|
62
102
|
it("TAIJI_AGENT_SUBAGENT=1 命中(引擎 spawn 链恒注入的现行标记)", () => {
|
|
63
103
|
expect(isSubagentProcess({ TAIJI_AGENT_SUBAGENT: "1" })).toBe(true);
|
|
@@ -107,3 +147,48 @@ describe("isSubagentProcess", () => {
|
|
|
107
147
|
);
|
|
108
148
|
});
|
|
109
149
|
});
|
|
150
|
+
|
|
151
|
+
describe("isTaijiHost / isTaijiGuiHost", () => {
|
|
152
|
+
// 谓词读活 env(每次调用时取 process.env,不缓存),用 saved/finally 操纵恢复;
|
|
153
|
+
// 键经 TAIJI_GUI_HOST_ENV 常量取值(常量改值时测试自动跟随,防键名漂移)
|
|
154
|
+
function withEnv(value: string | undefined, fn: () => void): void {
|
|
155
|
+
const key = TAIJI_GUI_HOST_ENV;
|
|
156
|
+
const saved = process.env[key];
|
|
157
|
+
try {
|
|
158
|
+
if (value === undefined) delete process.env[key];
|
|
159
|
+
else process.env[key] = value;
|
|
160
|
+
fn();
|
|
161
|
+
} finally {
|
|
162
|
+
if (saved === undefined) delete process.env[key];
|
|
163
|
+
else process.env[key] = saved;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
it("env 信号缺失 = 非宿主(mode 无关)", () => {
|
|
168
|
+
withEnv(undefined, () => {
|
|
169
|
+
expect(isTaijiHost()).toBe(false);
|
|
170
|
+
expect(isTaijiGuiHost({ mode: "rpc" })).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("env=1 但 mode 非 rpc(TUI / json / print)= 非 GUI 宿主(env 泄漏形态,禁入 select)", () => {
|
|
175
|
+
withEnv("1", () => {
|
|
176
|
+
expect(isTaijiHost()).toBe(true);
|
|
177
|
+
expect(isTaijiGuiHost({ mode: "tui" })).toBe(false);
|
|
178
|
+
expect(isTaijiGuiHost({ mode: "json" })).toBe(false);
|
|
179
|
+
expect(isTaijiGuiHost({ mode: "print" })).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("env=1 + rpc = taiji GUI 宿主", () => {
|
|
184
|
+
withEnv("1", () => {
|
|
185
|
+
expect(isTaijiGuiHost({ mode: "rpc" })).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("env 非 \"1\" 值 = 非宿主(严格等值判定)", () => {
|
|
190
|
+
withEnv("0", () => {
|
|
191
|
+
expect(isTaijiHost()).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -116,6 +116,18 @@ export function isEnoentError(err: unknown): boolean {
|
|
|
116
116
|
return err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* 判断 error 是否为带指定 errno code 的 Node 系统调用错误(`Error` 实例且
|
|
121
|
+
* `code === 期望值`)。
|
|
122
|
+
*
|
|
123
|
+
* 与 isEnoentError 同一收窄纪律(`"code" in err` 零断言——taste/no-unsafe-cast),
|
|
124
|
+
* code 参数化:isEnoentError = code 钉死 "ENOENT" 的特化形态。非 Error 对象即使带
|
|
125
|
+
* 同名 code 也 false(与 isEnoentError 的严版口径一致——Node fs 抛错恒为 Error 实例)。
|
|
126
|
+
*/
|
|
127
|
+
export function isErrnoWithCode(error: unknown, code: string): boolean {
|
|
128
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
129
|
+
}
|
|
130
|
+
|
|
119
131
|
// ── subagent 进程判据(isSubagentProcess,ext-simplify-17 D4)────────────
|
|
120
132
|
//
|
|
121
133
|
// 背景:extension 需要知道「当前 pi 进程是否引擎链上的 subagent 子进程」来决定降级
|
|
@@ -148,6 +160,52 @@ export function isSubagentProcess(
|
|
|
148
160
|
return env[SUBAGENT_MARKER_ENV] === "1";
|
|
149
161
|
}
|
|
150
162
|
|
|
163
|
+
// ── taiji GUI 宿主判据(isTaijiGuiHost,dmg-r1-7 跨包收敛)──────────────
|
|
164
|
+
//
|
|
165
|
+
// 背景:taiji runtime 对托管 pi 恒注入 TAIJI_AGENT_EXT_LOG=1(恒注入点
|
|
166
|
+
// packages/pi-rpc/src/env.ts 的 outboundExtras;名义语义是扩展日志开关,
|
|
167
|
+
// extension-logger 见之落盘 INFO),独立 pi 无此信号。env 只证明 taiji runtime
|
|
168
|
+
// 在上游,宿主没有 marker 路由的形态(TUI / json / print)select 会落终端 UI——
|
|
169
|
+
// 完整谓词必须再收紧 ctx.mode === "rpc";单查 env 会在 env 泄漏到独立 pi 时误入
|
|
170
|
+
// select 挂起(禁超时探测,对齐时间平抑红线:不做「挂起后超时回退」类时间窗兜底)。
|
|
171
|
+
//
|
|
172
|
+
// [双语义耦合登记,SSOT 随谓词收敛单点化于此] 本 env 名义语义是扩展日志开关,
|
|
173
|
+
// 本谓词是第二消费方(宿主分流)——若日志开关走向可配置(值不再是恒 '1'),全部
|
|
174
|
+
// 宿主分流同帧静默失效,届时必须拆专用宿主信号 env 并纳入恒注入,不得沿用本名;
|
|
175
|
+
// 拆分时消费方(plan state.ts / canvas host.ts 的 re-export 调用点)一并随迁移。
|
|
176
|
+
//
|
|
177
|
+
// 收敛历史:plan 包内三处 `isTaijiHost() && ctx.mode === "rpc"` 复写先收敛为
|
|
178
|
+
// plan state.ts 单点(D-B1-7),canvas host.ts 此前持独立实现、一致性靠两侧注释
|
|
179
|
+
// 互指维持(dmg-r1-7 判其为无机器检查的双实现)——现整体收敛于本包,两包改
|
|
180
|
+
// re-export,一致性由构造保证。
|
|
181
|
+
|
|
182
|
+
/** taiji GUI 宿主判定 env 键(taiji runtime 恒注入 "1";名义语义 = 扩展日志开关,见分节双语义耦合登记)。 */
|
|
183
|
+
export const TAIJI_GUI_HOST_ENV = "TAIJI_AGENT_EXT_LOG";
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* taiji runtime 宿主 env 信号(isTaijiGuiHost 的 env 腿):`TAIJI_GUI_HOST_ENV`
|
|
187
|
+
* 严格等值 "1" 判定。每次调用时读(不可模块加载时缓存——测试与运行中 env 都可能
|
|
188
|
+
* 变化)。
|
|
189
|
+
*/
|
|
190
|
+
export function isTaijiHost(): boolean {
|
|
191
|
+
return process.env[TAIJI_GUI_HOST_ENV] === "1";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** 宿主形态判别的最小结构面(pi ExtensionContext 的 mode 字段结构化投影——零 pi SDK 依赖)。 */
|
|
195
|
+
// oe-exempt:20261016:framework:宿主形态判别的数据形状契约(pi ctx 的结构化投影,非抽象接口单实现)
|
|
196
|
+
export interface GuiHostContextLike {
|
|
197
|
+
mode: string;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* taiji GUI 宿主完整谓词:env 信号 + rpc 模式(marker select 只在 rpc 宿主有路由)。
|
|
202
|
+
* 消费方 = plan 的 submit-review marker select / complete 执行方式 form / E3 恢复
|
|
203
|
+
* 分流(state.ts re-export)与 canvas 六工具的 select 前置门(host.ts re-export)。
|
|
204
|
+
*/
|
|
205
|
+
export function isTaijiGuiHost(ctx: GuiHostContextLike): boolean {
|
|
206
|
+
return isTaijiHost() && ctx.mode === "rpc";
|
|
207
|
+
}
|
|
208
|
+
|
|
151
209
|
// ── stale ctx 守卫(guardStaleCtx,崩溃韧性 D1)────────────────────────
|
|
152
210
|
//
|
|
153
211
|
// 背景:pi 的 extension API
|