principles-disciple 1.7.1 → 1.7.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.
@@ -10,6 +10,16 @@
10
10
  */
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
+ export class LockAcquisitionError extends Error {
14
+ filePath;
15
+ lockPath;
16
+ constructor(message, filePath, lockPath) {
17
+ super(message);
18
+ this.name = 'LockAcquisitionError';
19
+ this.filePath = filePath;
20
+ this.lockPath = lockPath;
21
+ }
22
+ }
13
23
  /** 默认锁选项 */
14
24
  const DEFAULT_OPTIONS = {
15
25
  maxRetries: 50,
@@ -134,15 +144,39 @@ function calculateBackoff(attempt, baseMs, maxMs) {
134
144
  const jitter = exponentialDelay * 0.2 * Math.random();
135
145
  return Math.floor(exponentialDelay + jitter);
136
146
  }
137
- /**
138
- * 同步 sleep
139
- */
147
+ function sleep(ms) {
148
+ return new Promise((resolve) => setTimeout(resolve, ms));
149
+ }
140
150
  function sleepSync(ms) {
141
151
  const end = Date.now() + ms;
142
152
  while (Date.now() < end) {
143
- // busy wait(同步函数无法使用 setTimeout)
153
+ // busy wait for synchronous retry
144
154
  }
145
155
  }
156
+ function buildLockError(filePath, lockPath) {
157
+ const holderPid = readLockPid(lockPath);
158
+ const holderStatus = holderPid !== null
159
+ ? (isProcessAlive(holderPid) ? `alive (PID ${holderPid})` : `dead (PID ${holderPid})`)
160
+ : 'unknown';
161
+ return new LockAcquisitionError(`Failed to acquire lock for ${filePath}. Lock holder: ${holderStatus}.`, filePath, lockPath);
162
+ }
163
+ function tryAcquireWithStaleCleanup(filePath, opts, pid) {
164
+ const lockPath = filePath + opts.lockSuffix;
165
+ if (tryAcquireLock(lockPath, pid)) {
166
+ const actualPid = readLockPid(lockPath);
167
+ if (actualPid === pid) {
168
+ return { lockPath, pid, acquiredAt: Date.now() };
169
+ }
170
+ }
171
+ cleanupStaleLock(lockPath, opts.lockStaleMs);
172
+ if (tryAcquireLock(lockPath, pid)) {
173
+ const actualPid = readLockPid(lockPath);
174
+ if (actualPid === pid) {
175
+ return { lockPath, pid, acquiredAt: Date.now() };
176
+ }
177
+ }
178
+ return null;
179
+ }
146
180
  /**
147
181
  * 获取文件锁
148
182
  *
@@ -153,37 +187,33 @@ function sleepSync(ms) {
153
187
  */
154
188
  export function acquireLock(filePath, options = {}) {
155
189
  const opts = { ...DEFAULT_OPTIONS, ...options };
156
- const lockPath = filePath + opts.lockSuffix;
157
190
  const pid = process.pid;
158
191
  for (let attempt = 0; attempt < opts.maxRetries; attempt++) {
159
- // 1. 尝试原子获取锁
160
- if (tryAcquireLock(lockPath, pid)) {
161
- // 2. 二次验证:确保我们真的持有锁
162
- const actualPid = readLockPid(lockPath);
163
- if (actualPid === pid) {
164
- return {
165
- lockPath,
166
- pid,
167
- acquiredAt: Date.now(),
168
- };
169
- }
170
- // PID 不匹配,说明被其他进程抢占,继续重试
192
+ const ctx = tryAcquireWithStaleCleanup(filePath, opts, pid);
193
+ if (ctx) {
194
+ return ctx;
171
195
  }
172
- // 3. 获取失败,检查是否需要清理过期锁
173
- cleanupStaleLock(lockPath, opts.lockStaleMs);
174
- // 4. 计算退避时间并等待
175
196
  if (attempt < opts.maxRetries - 1) {
176
197
  const delay = calculateBackoff(attempt, opts.baseRetryDelayMs, opts.maxRetryDelayMs);
177
198
  sleepSync(delay);
178
199
  }
179
200
  }
180
- // 5. 所有重试都失败,抛出异常(不静默丢弃!)
181
- const holderPid = readLockPid(lockPath);
182
- const holderStatus = holderPid !== null
183
- ? (isProcessAlive(holderPid) ? `alive (PID ${holderPid})` : `dead (PID ${holderPid})`)
184
- : 'unknown';
185
- throw new Error(`Failed to acquire lock for ${filePath} after ${opts.maxRetries} attempts. ` +
186
- `Lock holder: ${holderStatus}. Consider increasing maxRetries or checking for zombie processes.`);
201
+ throw buildLockError(filePath, filePath + opts.lockSuffix);
202
+ }
203
+ export async function acquireLockAsync(filePath, options = {}) {
204
+ const opts = { ...DEFAULT_OPTIONS, ...options };
205
+ const pid = process.pid;
206
+ for (let attempt = 0; attempt < opts.maxRetries; attempt++) {
207
+ const ctx = tryAcquireWithStaleCleanup(filePath, opts, pid);
208
+ if (ctx) {
209
+ return ctx;
210
+ }
211
+ if (attempt < opts.maxRetries - 1) {
212
+ const delay = calculateBackoff(attempt, opts.baseRetryDelayMs, opts.maxRetryDelayMs);
213
+ await sleep(delay);
214
+ }
215
+ }
216
+ throw buildLockError(filePath, filePath + opts.lockSuffix);
187
217
  }
188
218
  /**
189
219
  * 释放文件锁
@@ -210,6 +240,15 @@ export function withLock(filePath, fn, options = {}) {
210
240
  releaseLock(ctx);
211
241
  }
212
242
  }
243
+ export async function withLockAsync(filePath, fn, options = {}) {
244
+ const ctx = await acquireLockAsync(filePath, options);
245
+ try {
246
+ return await fn();
247
+ }
248
+ finally {
249
+ releaseLock(ctx);
250
+ }
251
+ }
213
252
  /**
214
253
  * 异步版本的文件锁(使用 Promise 链)
215
254
  *
@@ -1,8 +1,8 @@
1
- {
1
+ {
2
2
  "id": "principles-disciple",
3
3
  "name": "Principles Disciple",
4
4
  "description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
5
- "version": "1.7.1",
5
+ "version": "1.7.2",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
@@ -17,7 +17,7 @@
17
17
  "zh"
18
18
  ],
19
19
  "default": "zh",
20
- "description": "系统提示词和交互语言 (默认推荐: zh)"
20
+ "description": "绯荤粺鎻愮ず璇嶅拰浜や簰璇█ (榛樿鎺ㄨ崘: zh)"
21
21
  },
22
22
  "auditLevel": {
23
23
  "type": "string",
@@ -27,7 +27,7 @@
27
27
  "high"
28
28
  ],
29
29
  "default": "medium",
30
- "description": "安全防爆级别:\n- low: 极客模式,几乎不拦截,AI 可以自由飞翔。\n- medium (推荐): 平衡模式,允许 AI 大胆尝试,但拦截毁灭性操作。\n- high: 严格模式,所有大范围修改都需要你明确授权。"
30
+ "description": "瀹夊叏闃茬垎绾у埆锛歕n- low: 鏋佸妯″紡锛屽嚑涔庝笉鎷︽埅锛孉I 鍙互鑷敱椋炵繑銆俓n- medium (鎺ㄨ崘): 骞宠 妯″紡锛屽厑璁?AI 澶ц儐灏濊瘯锛屼絾鎷︽埅姣佺伃鎬ф搷浣溿€俓n- high: 涓ユ牸妯″紡锛屾墍鏈夊ぇ鑼冨洿淇敼閮介渶瑕佷綘鏄庣‘鎺堟潈銆?
31
31
  },
32
32
  "riskPaths": {
33
33
  "type": "array",
@@ -35,17 +35,17 @@
35
35
  "type": "string"
36
36
  },
37
37
  "default": [],
38
- "description": "自定义高危目录(例如 .git/, prod_db/)。AI 试图修改这些目录前,将被强制拦截并要求出具安全计划。"
38
+ "description": "鑷畾涔夐珮鍗辩洰褰曪紙渚嬪 .git/, prod_db/锛夈€侫I 璇曞浘淇敼杩欎簺鐩綍鍓嶏紝灏嗚寮哄埗鎷︽埅骞惰姹傚嚭鍏峰畨鍏ㄨ鍒掋€?
39
39
  },
40
40
  "deep_reflection": {
41
41
  "type": "object",
42
42
  "additionalProperties": false,
43
- "description": "AI 遇到复杂问题或连续报错时,是否允许它停下来进行深度自我反思?",
43
+ "description": "褰?AI 閬囧埌澶嶆潅闂鎴栬繛缁姤閿欐椂锛屾槸鍚﹀厑璁稿畠鍋滀笅鏉ヨ繘琛屾繁搴﹁嚜鎴戝弽鎬濓紵",
44
44
  "properties": {
45
45
  "enabled": {
46
46
  "type": "boolean",
47
47
  "default": true,
48
- "description": "开启 AI 深度反思功能"
48
+ "description": "寮€鍚?AI 娣卞害鍙嶆€濆姛鑳?
49
49
  },
50
50
  "mode": {
51
51
  "type": "string",
@@ -54,7 +54,7 @@
54
54
  "forced"
55
55
  ],
56
56
  "default": "auto",
57
- "description": "auto: 遇到困难自动触发; forced: 每次回答前都强制反思(极耗时间,不推荐)"
57
+ "description": "auto: 閬囧埌鍥伴毦鑷姩瑙﹀彂; forced: 姣忔鍥炵瓟鍓嶉兘寮哄埗鍙嶆€?鏋佽€楁椂闂达紝涓嶆帹鑽?"
58
58
  }
59
59
  }
60
60
  }
@@ -62,17 +62,18 @@
62
62
  },
63
63
  "uiHints": {
64
64
  "language": {
65
- "label": "交互语言"
65
+ "label": "浜や簰璇█"
66
66
  },
67
67
  "auditLevel": {
68
- "label": "防爆拦截级别",
68
+ "label": "闃茬垎鎷︽埅绾у埆",
69
69
  "placeholder": "medium"
70
70
  },
71
71
  "riskPaths": {
72
- "label": "☠️ 绝对高危目录 (空表示不设限)"
72
+ "label": "鈽狅笍 缁濆楂樺嵄鐩綍 (绌鸿〃绀轰笉璁鹃檺)"
73
73
  },
74
74
  "deep_reflection": {
75
- "label": "💡 AI 深度反思功能"
75
+ "label": "馃挕 AI 娣卞害鍙嶆€濆姛鑳?
76
76
  }
77
77
  }
78
78
  }
79
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",