@itpay/cli 2.0.36 → 2.0.37

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.
@@ -12,8 +12,8 @@ import { DeviceAuthority } from "./device_authority.js";
12
12
  import { OperationJournal } from "./operation_journal.js";
13
13
  export const DEFAULT_BASE_URL = "https://app.itpay.ai";
14
14
  export const DEV_BASE_URL = "https://dev.itpay.ai";
15
- export const CLI_VERSION = "2.0.36";
16
- export const API_CONTRACT_REVISION = "sha256:20986f3d7a770f9c696c0576f3ae4898f0d23a519bd489c1727382a16270fab2";
15
+ export const CLI_VERSION = "2.0.37";
16
+ export const API_CONTRACT_REVISION = "sha256:b97025ee4e6f7539757d6aedbe193dd65b3a0f84f1111f678abb5e757065f888";
17
17
  const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
18
18
  const CART_SESSION_FILENAME = "cart.json";
19
19
  const OPERATION_JOURNAL_FILENAME = "operations.json";
@@ -1,5 +1,5 @@
1
1
  import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomUUID, sign, } from "node:crypto";
2
- import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmdirSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, resolve } from "node:path";
5
5
  const PROTECTED_PATHS = ["/v1/carts", "/v1/service-executions", "/v1/agent-instances", "/v1/orders", "/v1/refunds", "/v1/me", "/v1/vault"];
@@ -292,10 +292,12 @@ async function withFileLock(path, run) {
292
292
  catch (error) {
293
293
  throw asDeviceStatePathError(error, "prepare_lock") ?? error;
294
294
  }
295
+ const ownerToken = randomUUID();
295
296
  let acquired = false;
296
297
  for (let attempt = 0; attempt < 200; attempt += 1) {
297
298
  try {
298
- mkdirSync(path, { mode: 0o700 });
299
+ writeFileSync(path, ownerToken, { encoding: "utf8", flag: "wx", mode: 0o600 });
300
+ chmodSync(path, 0o600);
299
301
  acquired = true;
300
302
  break;
301
303
  }
@@ -305,7 +307,7 @@ async function withFileLock(path, run) {
305
307
  throw asDeviceStateError(error, "acquire_lock") ?? error;
306
308
  try {
307
309
  if (Date.now() - statSync(path).mtimeMs > 30_000)
308
- removeLock(path, "remove_stale_lock");
310
+ moveLockAside(path, "stale", "remove_stale_lock");
309
311
  }
310
312
  catch (statError) {
311
313
  if (statError.code !== "ENOENT") {
@@ -321,19 +323,33 @@ async function withFileLock(path, run) {
321
323
  return await run();
322
324
  }
323
325
  finally {
324
- removeLock(path, "release_lock");
326
+ releaseLock(path, ownerToken);
325
327
  }
326
328
  }
327
- function removeLock(path, operation) {
329
+ function releaseLock(path, ownerToken) {
328
330
  try {
329
- if (statSync(path).isDirectory())
330
- rmdirSync(path);
331
- else
332
- unlinkSync(path);
331
+ if (readFileSync(path, "utf8") !== ownerToken)
332
+ return;
333
+ moveLockAside(path, "released", "release_lock");
333
334
  }
334
335
  catch (error) {
335
336
  if (error.code !== "ENOENT")
336
- throw asDeviceStateError(error, operation) ?? error;
337
+ throw asDeviceStateError(error, "release_lock") ?? error;
338
+ }
339
+ }
340
+ function moveLockAside(path, suffix, operation) {
341
+ try {
342
+ renameSync(path, `${path}.${suffix}`);
343
+ }
344
+ catch (error) {
345
+ const code = error.code;
346
+ if (code === "ENOENT")
347
+ return;
348
+ if (code === "EEXIST" || code === "ENOTEMPTY") {
349
+ renameSync(path, `${path}.${suffix}.${randomUUID()}`);
350
+ return;
351
+ }
352
+ throw asDeviceStateError(error, operation) ?? error;
337
353
  }
338
354
  }
339
355
  function asDeviceStateError(error, operation) {
@@ -14,6 +14,14 @@ itpay --agent-type <agent_type> device recover --confirm-backend-reset --json
14
14
 
15
15
  命令只作用于当前官方 Backend 的 Device registration,并保留本地 Ed25519 私钥、Cart 和业务资源。默认是 `https://app.itpay.ai`;显式测试可使用准确的 `ITPAY_BACKEND_URL=https://dev.itpay.ai`。该命令不访问 Backend、不自动创建新身份;返回的只读 `services list` 会保留同一 Backend,是重新登记入口。
16
16
 
17
+ 同一台电脑上的多个 Local Agent Type 共享本地 Device key,但各自使用独立 Agent
18
+ Instance。CLI 只在原子更新 Device state 时使用短期本地锁;释放和 stale recovery
19
+ 通过 rename 完成,不依赖 Host 删除文件,因此 WorkBuddy 等 sandbox 的
20
+ safe-delete/trash shim 不应阻断正常命令。该锁不在 Backend,不会让另一台电脑或
21
+ 另一个 Buyer 等待。遇到本地锁错误时不得删除 `~/.itpay-v3/device`、切换 Agent
22
+ Type 或执行 `device recover`;应保留身份并重试原命令一次,持续失败时报告
23
+ `device_state_unwritable` 或 lock timeout。
24
+
17
25
  缺少确认参数返回 `backend_reset_confirmation_required`。普通 session 失效由 CLI 自动续期;revoked、quota、权限或未知 Backend 故障不得使用本命令。所有 Agent Type 使用相同输入和输出合同。
18
26
 
19
27
  ## 参数
@@ -73,6 +73,9 @@ Markdown:
73
73
 
74
74
  没有用户评论时省略 Summary,但仍保存 Context。只写用户明确内容和已知安全上下文;禁止 Token、Session、联系方式、内部身份、
75
75
  Provider 响应、Vault payload、完整命令输出、stack trace 或环境变量。
76
+ CLI 不读取或上传 Host 对话、WorkBuddy/ZCode sandbox 日志或其他本地 Agent
77
+ 记录。用户自愿提供本地证据时,Agent 也只能提炼与本单有关的安全摘要,不能把原始
78
+ 日志作为 Feedback note 上传。
76
79
 
77
80
  ## 成功 JSON
78
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itpay/cli",
3
- "version": "2.0.36",
3
+ "version": "2.0.37",
4
4
  "description": "The ItPay CLI for services, orders, and human-authorized purchased content.",
5
5
  "type": "module",
6
6
  "bin": {