@zhushanwen/pi-ext-guards 0.4.0 → 0.4.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-ext-guards",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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,11 +1,12 @@
|
|
|
1
1
|
// src/__tests__/ext-guards.test.ts
|
|
2
2
|
//
|
|
3
|
-
// oncePerProcess 单测——验收条款逐条对应(u-guards-pkg
|
|
3
|
+
// oncePerProcess 单测——验收条款逐条对应(u-guards-pkg ①;失败语义按加固审查裁决
|
|
4
|
+
// 修订为「失败不缓存、下次调用可重试」):
|
|
4
5
|
// a. key 隔离去重:不同 key 互不影响,各自首次调用执行
|
|
5
6
|
// b. 同 key 双调 fn 仅执行一次
|
|
6
|
-
// c. fn
|
|
7
|
+
// c. fn 抛错不吞不包装,且不缓存——同 key 再调重新执行(可重试)
|
|
7
8
|
// d. 结果缓存形态:值原样重放(严格同一引用)、Promise 实例重放
|
|
8
|
-
//
|
|
9
|
+
// e. rejected Promise:落定后释放 key,下次调用重新执行
|
|
9
10
|
//
|
|
10
11
|
// 模块级 Map 是被测状态:测试间共享,每个用例用全文件唯一的 key 隔离。
|
|
11
12
|
|
|
@@ -37,21 +38,68 @@ describe("oncePerProcess", () => {
|
|
|
37
38
|
expect(fnB).toHaveBeenCalledTimes(1);
|
|
38
39
|
});
|
|
39
40
|
|
|
40
|
-
it("fn
|
|
41
|
+
it("fn 同步抛错:错误原样上抛(不吞不包装),不写缓存——同 key 再调重新执行(可重试)", () => {
|
|
41
42
|
const boom = new Error("reap failed");
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
let attempts = 0;
|
|
44
|
+
const fn = vi.fn(() => {
|
|
45
|
+
attempts += 1;
|
|
46
|
+
if (attempts === 1) throw boom;
|
|
47
|
+
return "recovered";
|
|
44
48
|
});
|
|
45
49
|
|
|
46
|
-
expect(() => oncePerProcess("dedupe:throws",
|
|
50
|
+
expect(() => oncePerProcess("dedupe:throws-retry", fn)).toThrow(boom);
|
|
51
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
52
|
+
|
|
53
|
+
// 失败不缓存:下次同 key 调用重新执行 fn,成功结果照常缓存
|
|
54
|
+
expect(oncePerProcess("dedupe:throws-retry", fn)).toBe("recovered");
|
|
55
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
56
|
+
// 成功后去重恢复:不再执行
|
|
57
|
+
expect(oncePerProcess("dedupe:throws-retry", fn)).toBe("recovered");
|
|
58
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("async fn 抛错:rejected Promise 落定后释放 key——下次调用重新执行(可重试)", async () => {
|
|
62
|
+
const boom = new Error("async reap failed");
|
|
63
|
+
let attempts = 0;
|
|
64
|
+
const fn = vi.fn(async () => {
|
|
65
|
+
attempts += 1;
|
|
66
|
+
if (attempts === 1) throw boom;
|
|
67
|
+
return "recovered";
|
|
68
|
+
});
|
|
47
69
|
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
expect(
|
|
70
|
+
const first = oncePerProcess("dedupe:async-rejected-retry", fn);
|
|
71
|
+
// 先 attach 断言再消费,避免 unhandledRejection
|
|
72
|
+
await expect(first).rejects.toThrow(boom);
|
|
73
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
// rejection 已落定、key 已释放:下次调用重新执行 fn
|
|
76
|
+
const second = oncePerProcess("dedupe:async-rejected-retry", fn);
|
|
77
|
+
expect(second).not.toBe(first);
|
|
78
|
+
await expect(second).resolves.toBe("recovered");
|
|
79
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("async fn:rejection 落定前的同 key 双调仍重放同一实例(落定后释放)", async () => {
|
|
83
|
+
const boom = new Error("async reap failed");
|
|
84
|
+
const fn = vi.fn(async () => {
|
|
85
|
+
throw boom;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const first = oncePerProcess("dedupe:async-inflight", fn);
|
|
89
|
+
// 落定前第二次调用:同一 Promise 实例(在飞去重不双跑)
|
|
90
|
+
const second = oncePerProcess("dedupe:async-inflight", fn);
|
|
91
|
+
expect(second).toBe(first);
|
|
92
|
+
// 先 attach 断言再消费,避免 unhandledRejection
|
|
93
|
+
const firstRejection = expect(first).rejects.toThrow(boom);
|
|
94
|
+
await firstRejection;
|
|
95
|
+
await Promise.resolve(); // 让释放 handler 的微任务走完
|
|
96
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
97
|
+
|
|
98
|
+
// 落定后第三次调用:key 已释放,fn 重新执行
|
|
99
|
+
const third = oncePerProcess("dedupe:async-inflight", fn);
|
|
100
|
+
expect(third).not.toBe(first);
|
|
101
|
+
await expect(third).rejects.toThrow(boom);
|
|
102
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
55
103
|
});
|
|
56
104
|
|
|
57
105
|
it("结果缓存形态:对象返回值严格同一引用(重放非重新求值)", () => {
|
|
@@ -78,20 +126,14 @@ describe("oncePerProcess", () => {
|
|
|
78
126
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
79
127
|
});
|
|
80
128
|
|
|
81
|
-
it("
|
|
82
|
-
const
|
|
83
|
-
const fn = vi.fn(async () => {
|
|
84
|
-
throw boom;
|
|
85
|
-
});
|
|
129
|
+
it("fn 返回 undefined:值仍被缓存(不因 undefined 误判未执行)", () => {
|
|
130
|
+
const fn = vi.fn(() => undefined);
|
|
86
131
|
|
|
87
|
-
const first = oncePerProcess("dedupe:
|
|
88
|
-
|
|
89
|
-
const firstRejection = expect(first).rejects.toThrow(boom);
|
|
90
|
-
const second = oncePerProcess("dedupe:async-rejected", fn);
|
|
132
|
+
const first = oncePerProcess("dedupe:undefined-value", fn);
|
|
133
|
+
const second = oncePerProcess("dedupe:undefined-value", fn);
|
|
91
134
|
|
|
92
|
-
expect(
|
|
93
|
-
|
|
94
|
-
await expect(second).rejects.toThrow(boom);
|
|
135
|
+
expect(first).toBeUndefined();
|
|
136
|
+
expect(second).toBeUndefined();
|
|
95
137
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
96
138
|
});
|
|
97
139
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// guardStaleCtx 单测——验收条款逐条对应(crash-resilience u1-ext-guard):
|
|
4
4
|
// a. 前置代际检查:isCtxStale=true → fn 不执行、stale 静默降级(返回 undefined)
|
|
5
5
|
// b. 非 stale 错误原样上抛(守卫不吞真实 bug,同步 throw 与 async rejection 双面)
|
|
6
|
-
// c. onStale 回调:前置命中无参调用;文案分诊命中携带错误调用;缺省走
|
|
6
|
+
// c. onStale 回调:前置命中无参调用;文案分诊命中携带错误调用;缺省走 TAIJI_AGENT_DEBUG=1 stderr
|
|
7
7
|
// d. 文案子串匹配:STALE_CTX_MARKER 命中即静默(isCtxStale=false 时文案兜底仍生效)
|
|
8
8
|
// e. async fn:stale rejection → resolve undefined;非 stale rejection → 原样 reject
|
|
9
9
|
// f. 正常路径:返回值/resolve 值原样透传(守卫不改变正常路径,A2 负面验证的单测面)
|
|
@@ -140,8 +140,8 @@ describe("guardStaleCtx async fn(F2 形态异步面,scheduler tick 迁移形
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
describe("guardStaleCtx onStale 缺省(默认 debug 日志)", () => {
|
|
143
|
-
it("
|
|
144
|
-
vi.stubEnv("
|
|
143
|
+
it("TAIJI_AGENT_DEBUG=1:stderr 写入守卫降级行(含 label 与错误文案)", () => {
|
|
144
|
+
vi.stubEnv("TAIJI_AGENT_DEBUG", "1");
|
|
145
145
|
const stderr = vi.spyOn(process.stderr, "write").mockReturnValue(true);
|
|
146
146
|
|
|
147
147
|
guardStaleCtx(() => {
|
|
@@ -154,8 +154,8 @@ describe("guardStaleCtx onStale 缺省(默认 debug 日志)", () => {
|
|
|
154
154
|
expect(line).toContain(STALE_CTX_MARKER);
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
it("
|
|
158
|
-
vi.stubEnv("
|
|
157
|
+
it("TAIJI_AGENT_DEBUG 未设:静默(不写 stderr)", () => {
|
|
158
|
+
vi.stubEnv("TAIJI_AGENT_DEBUG", "");
|
|
159
159
|
const stderr = vi.spyOn(process.stderr, "write").mockReturnValue(true);
|
|
160
160
|
|
|
161
161
|
guardStaleCtx(() => {
|
|
@@ -59,14 +59,14 @@ describe("isEnoentError", () => {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
describe("isSubagentProcess", () => {
|
|
62
|
-
it("
|
|
63
|
-
expect(isSubagentProcess({
|
|
62
|
+
it("TAIJI_AGENT_SUBAGENT=1 命中(引擎 spawn 链恒注入的现行标记)", () => {
|
|
63
|
+
expect(isSubagentProcess({ TAIJI_AGENT_SUBAGENT: "1" })).toBe(true);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it("值非 \"1\" 不命中(标记必须是引擎注入的字面 \"1\")", () => {
|
|
67
|
-
expect(isSubagentProcess({
|
|
68
|
-
expect(isSubagentProcess({
|
|
69
|
-
expect(isSubagentProcess({
|
|
67
|
+
expect(isSubagentProcess({ TAIJI_AGENT_SUBAGENT: "0" })).toBe(false);
|
|
68
|
+
expect(isSubagentProcess({ TAIJI_AGENT_SUBAGENT: "" })).toBe(false);
|
|
69
|
+
expect(isSubagentProcess({ TAIJI_AGENT_SUBAGENT: "true" })).toBe(false);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it("缺失不命中(顶层会话 → 消费方不降级)", () => {
|
|
@@ -74,28 +74,28 @@ describe("isSubagentProcess", () => {
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
it("env 参数注入隔离:传入的 env 与宿主 process.env 无关(宿主即使带标记,空 env 也不命中)", () => {
|
|
77
|
-
const saved = process.env.
|
|
77
|
+
const saved = process.env.TAIJI_AGENT_SUBAGENT;
|
|
78
78
|
try {
|
|
79
|
-
process.env.
|
|
79
|
+
process.env.TAIJI_AGENT_SUBAGENT = "1";
|
|
80
80
|
// 显式传 env:宿主的标记不渗透,判据只看参数
|
|
81
81
|
expect(isSubagentProcess({})).toBe(false);
|
|
82
|
-
expect(isSubagentProcess({
|
|
82
|
+
expect(isSubagentProcess({ TAIJI_AGENT_SUBAGENT: "1" })).toBe(true);
|
|
83
83
|
} finally {
|
|
84
|
-
if (saved === undefined) delete process.env.
|
|
85
|
-
else process.env.
|
|
84
|
+
if (saved === undefined) delete process.env.TAIJI_AGENT_SUBAGENT;
|
|
85
|
+
else process.env.TAIJI_AGENT_SUBAGENT = saved;
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
it("缺省参数读 process.env(不传 env 时以宿主环境为判据)", () => {
|
|
90
|
-
const saved = process.env.
|
|
90
|
+
const saved = process.env.TAIJI_AGENT_SUBAGENT;
|
|
91
91
|
try {
|
|
92
|
-
process.env.
|
|
92
|
+
process.env.TAIJI_AGENT_SUBAGENT = "1";
|
|
93
93
|
expect(isSubagentProcess()).toBe(true);
|
|
94
|
-
delete process.env.
|
|
94
|
+
delete process.env.TAIJI_AGENT_SUBAGENT;
|
|
95
95
|
expect(isSubagentProcess()).toBe(false);
|
|
96
96
|
} finally {
|
|
97
|
-
if (saved === undefined) delete process.env.
|
|
98
|
-
else process.env.
|
|
97
|
+
if (saved === undefined) delete process.env.TAIJI_AGENT_SUBAGENT;
|
|
98
|
+
else process.env.TAIJI_AGENT_SUBAGENT = saved;
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
|
package/src/index.ts
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
// reaper 双跑即此形态)。本包把这类「pi 运行环境隐式坑」的守卫集中一处,业务
|
|
12
12
|
// extension 引入即用,防线不再散落各包内联。
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
/** 一次执行的结果记录:只缓存成功结果;失败(同步抛错 / Promise rejection)不缓存,下次调用可重试。 */
|
|
15
|
+
interface ExecutionRecord {
|
|
16
|
+
readonly value: unknown;
|
|
17
|
+
}
|
|
18
18
|
|
|
19
19
|
// 「按进程去重」的物理载体 = 模块级 Map。有效前提:同一进程内模块级状态跨 factory
|
|
20
20
|
// 二调持久——pi extension 缓存按 cwd 失效时 factory 重跑,但 jiti/Node 模块缓存同图
|
|
@@ -23,22 +23,24 @@ type ExecutionRecord =
|
|
|
23
23
|
const executions = new Map<string, ExecutionRecord>();
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* 进程内按 key 去重地执行 fn:同一 key
|
|
26
|
+
* 进程内按 key 去重地执行 fn:同一 key 至多执行一次**成功**,后续调用重放首次成功结果。
|
|
27
27
|
*
|
|
28
|
-
* 语义细节(设计 §3.2 D3 守卫粒度段 + §3.3 D3
|
|
28
|
+
* 语义细节(设计 §3.2 D3 守卫粒度段 + §3.3 D3 守卫语义;失败语义按加固审查裁决修订
|
|
29
|
+
* ——「恢复幂等,下次启动重试」承诺优先,见下方失败段):
|
|
29
30
|
*
|
|
30
31
|
* - **结果缓存形态(非跳过)**:首次调用执行 fn 并缓存结果;后续同 key 调用不执行
|
|
31
32
|
* fn,返回首次的返回值——原样重放:对象返回严格同一引用(toBe 级,非重新求值);
|
|
32
|
-
* fn 返回 Promise
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
33
|
+
* fn 返回 Promise 时重放同一实例。
|
|
34
|
+
* - **失败不缓存、可重试**:fn 同步抛错原样上抛(守卫不捕获、不包装)且**不写缓存**;
|
|
35
|
+
* fn 返回的 Promise rejection 落定后释放 key(仅释放本条失败记录,期间同 key 重试
|
|
36
|
+
* 成功的新记录不受影响)——下次同 key 调用重新执行 fn。守卫不自动重试,重试节奏
|
|
37
|
+
* 由调用方决定(宿主的其他触发面承接,如下一个 session_start)。代价(有意接受):
|
|
38
|
+
* 首次失败到重试成功之间,factory 二调的双跑窗口重新打开——重试的是**失败过**的
|
|
39
|
+
* 操作,成功即缓存、窗口随即关闭;对幂等的进程级维护(recoverCrashedRuns「恢复
|
|
40
|
+
* 幂等,下次启动重试」、配置迁移、索引重建等)这正是期望行为。验收条款「fn 抛错
|
|
41
|
+
* 不阻断后续 handler」的准确语义仍是**守卫不吞 fn 的错误**(调用方 catch 守卫调用
|
|
42
|
+
* 即可继续 handler 后续逻辑,先例:base-tool-enhance runSessionStartMaintenance
|
|
43
|
+
* 的 try/catch 形态)。
|
|
42
44
|
* - **粒度边界**:只包「跨 session 副作用操作」(进程级全局维护类,正确频率就是每
|
|
43
45
|
* 进程至多一次)。session 级幂等操作(如 pending 对账——读当前 session 的 entries
|
|
44
46
|
* 与 registry,appendEntry 幂等)必须保持每 session_start 执行,不要挂本守卫;需要
|
|
@@ -47,29 +49,40 @@ const executions = new Map<string, ExecutionRecord>();
|
|
|
47
49
|
* (如 "base-tool-enhance:reap")避免撞 key。
|
|
48
50
|
*
|
|
49
51
|
* @param key 去重键(进程内全局,建议带包名前缀)
|
|
50
|
-
* @param fn
|
|
51
|
-
*
|
|
52
|
-
*
|
|
52
|
+
* @param fn 无参函数——首次调用时求值一次(直到成功),后续调用不再执行;需要上下文
|
|
53
|
+
* (pi/ctx)的调用方在闭包里捕获,被闭包捕获的是首次调用处的值(这正是「每进程
|
|
54
|
+
* 至多一次成功」的字面语义)
|
|
53
55
|
* @returns 首次执行的返回值(后续调用重放同一结果;async fn 重放同一 Promise 实例)
|
|
54
56
|
*/
|
|
55
57
|
export function oncePerProcess<T>(key: string, fn: () => T): T {
|
|
56
58
|
const hit = executions.get(key);
|
|
57
59
|
if (hit !== undefined) {
|
|
58
|
-
if (hit.outcome === "threw") {
|
|
59
|
-
throw hit.error;
|
|
60
|
-
}
|
|
61
60
|
// 同 key 的值由首次调用的泛型参数化记录,模块级 Map 无法按 key 参数化类型,
|
|
62
61
|
// 断言仅收窄回该泛型(构造点与重放点同函数,无跨来源混装)。
|
|
63
62
|
return hit.value as T;
|
|
64
63
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
const result = fn();
|
|
65
|
+
// 只缓存成功结果:同步抛错不经过本行(原样上抛 + 无缓存,下次调用可重试);
|
|
66
|
+
// Promise 的 rejection 在落定后经下方 handler 释放 key(同样可重试)。
|
|
67
|
+
executions.set(key, { value: result });
|
|
68
|
+
if (isPromiseLike(result)) {
|
|
69
|
+
// 内部挂 rejection 释放 handler(同时消除本条 rejection 的 unhandled 面——
|
|
70
|
+
// 调用方是否自行接 rejection 均安全)。仅当缓存里仍是本条失败记录时删除:
|
|
71
|
+
// rejection 落定前同 key 已被重试成功覆盖的场景不误删新记录。
|
|
72
|
+
void Promise.resolve(result).catch(() => {
|
|
73
|
+
const record = executions.get(key);
|
|
74
|
+
if (record !== undefined && record.value === result) executions.delete(key);
|
|
75
|
+
});
|
|
72
76
|
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 测试专用:清空 oncePerProcess 的进程级守卫 Map。供静态 import 形态的测试重置
|
|
82
|
+
* 去重状态(无 resetModules 重建模块图时的等价手段);生产代码禁止调用。
|
|
83
|
+
*/
|
|
84
|
+
export function _resetOncePerProcessForTest(): void {
|
|
85
|
+
executions.clear();
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
/**
|
|
@@ -109,14 +122,14 @@ export function isEnoentError(err: unknown): boolean {
|
|
|
109
122
|
// (base-tool-enhance background 降级——bte background 生命周期设计的 D14 项,
|
|
110
123
|
// commit 8599abd0c,该设计未在现行 docs 留档 / smart-context R6 不注册工具,
|
|
111
124
|
// 规则定义见 smart-context src/index.ts R 规则族注释)。判据锚
|
|
112
|
-
//
|
|
125
|
+
// TAIJI_AGENT_SUBAGENT=1——subagent-engine-sdk env.ts 的 buildEngineChildEnv 对每个引擎
|
|
113
126
|
// 子进程恒注入(L0 层最后写入),再经引擎内二次 spawn(buildOutboundChildEnv 全量
|
|
114
127
|
// 继承)传到 subagent 内的 bash。旧判据(PI_SUBAGENT_ROOT_SESSION_ID /
|
|
115
128
|
// PI_SUBAGENT_SELF_RECORD_ID 两键)在引擎协议化后已无写入方(20260914 探针证实,
|
|
116
129
|
// 报告 .tmp/dev-flow/ext-simplify-17-d4-probe.md),判据失效态由 D4 重锚收敛于此。
|
|
117
130
|
|
|
118
131
|
/** subagent 进程标记 env 键(引擎 spawn 链恒注入 "1",subagent-engine-sdk env.ts L0)。 */
|
|
119
|
-
export const SUBAGENT_MARKER_ENV = "
|
|
132
|
+
export const SUBAGENT_MARKER_ENV = "TAIJI_AGENT_SUBAGENT";
|
|
120
133
|
|
|
121
134
|
/**
|
|
122
135
|
* 判断当前进程是否引擎链上的 subagent 子进程。
|
|
@@ -137,7 +150,7 @@ export function isSubagentProcess(
|
|
|
137
150
|
|
|
138
151
|
// ── stale ctx 守卫(guardStaleCtx,崩溃韧性 D1)────────────────────────
|
|
139
152
|
//
|
|
140
|
-
//
|
|
153
|
+
// 背景:pi 的 extension API
|
|
141
154
|
// 对象(pi / ctx)在 session 替换(newSession/fork/switchSession/reload)后被 runner
|
|
142
155
|
// 标记 stale,再调用其方法会**同步抛错**(loader.js/runner.js 的 assertActive,错误文案
|
|
143
156
|
// 含 STALE_CTX_MARKER)。跨 session 生命周期存活的异步回调(compact 的 onComplete/onError、
|
|
@@ -172,7 +185,7 @@ export interface GuardStaleCtxOptions {
|
|
|
172
185
|
isCtxStale?: () => boolean;
|
|
173
186
|
/**
|
|
174
187
|
* stale 降级通知(静默降级的观测点):前置检查命中时无参调用;fn 抛错被分诊为
|
|
175
|
-
* stale 时以捕获的错误为参调用。缺省 = 仅
|
|
188
|
+
* stale 时以捕获的错误为参调用。缺省 = 仅 TAIJI_AGENT_DEBUG=1 时写一行 stderr
|
|
176
189
|
* debug(本包零依赖、无 pi handle,不能走 extension-logger;需要落盘日志的
|
|
177
190
|
* 接入方传入自己的 debugLog/logger.warn)。
|
|
178
191
|
*/
|
|
@@ -189,9 +202,9 @@ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
|
189
202
|
);
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
/** 缺省降级通知:
|
|
205
|
+
/** 缺省降级通知:TAIJI_AGENT_DEBUG=1 时 stderr 一行(对齐 extensions 包 TAIJI_AGENT_DEBUG 惯例)。 */
|
|
193
206
|
function defaultOnStale(label: string, error?: unknown): void {
|
|
194
|
-
if (process.env.
|
|
207
|
+
if (process.env.TAIJI_AGENT_DEBUG !== "1") return;
|
|
195
208
|
const detail =
|
|
196
209
|
error === undefined
|
|
197
210
|
? "generation check (isCtxStale)"
|