@itpay/cli 2.0.29 → 2.0.30

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,7 +12,7 @@ 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.29";
15
+ export const CLI_VERSION = "2.0.30";
16
16
  export const API_CONTRACT_REVISION = "sha256:9e46ee5a429994b3a9b86c94f210995053f78b3971621dbc2629c1a57107b070";
17
17
  const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
18
18
  const CART_SESSION_FILENAME = "cart.json";
@@ -108,12 +108,26 @@ export class DeviceAuthority {
108
108
  }
109
109
  async ensureRegistrationAgentType(registration, agentType, privateKey, forceSession) {
110
110
  if (!registration.agentInstances[agentType]) {
111
- const existingType = firstAgentType(registration);
112
- if (!existingType)
111
+ const existingTypes = Object.keys(registration.agentInstances).sort();
112
+ if (existingTypes.length === 0)
113
113
  throw new Error("device has no registered agent instance");
114
- const existingSession = await this.ensureSession(registration, existingType, privateKey, forceSession);
115
- const registered = await this.signedJSON("/v1/agent-instances", { agent_type: agentType }, registration, existingType, existingSession, privateKey);
116
- registration.agentInstances[agentType] = registered.agent_instance_id;
114
+ let revokedError;
115
+ for (const existingType of existingTypes) {
116
+ try {
117
+ const existingSession = await this.ensureSession(registration, existingType, privateKey, forceSession);
118
+ const registered = await this.signedJSON("/v1/agent-instances", { agent_type: agentType }, registration, existingType, existingSession, privateKey);
119
+ registration.agentInstances[agentType] = registered.agent_instance_id;
120
+ break;
121
+ }
122
+ catch (error) {
123
+ if (!(error instanceof DeviceAuthorizationError) || error.code !== "agent_device_revoked")
124
+ throw error;
125
+ delete registration.sessions[existingType];
126
+ revokedError = error;
127
+ }
128
+ }
129
+ if (!registration.agentInstances[agentType])
130
+ throw revokedError ?? new Error("device has no active registered agent instance");
117
131
  }
118
132
  return this.ensureSession(registration, agentType, privateKey, forceSession);
119
133
  }
@@ -240,7 +254,6 @@ export class DeviceStateError extends Error {
240
254
  function emptyDeviceState() {
241
255
  return { schemaVersion: "itpay.device.v2", registrations: {} };
242
256
  }
243
- function firstAgentType(state) { return Object.keys(state.agentInstances)[0]; }
244
257
  function canMovePastLegacyRegistration(error) {
245
258
  return error instanceof DeviceAuthorizationError && (error.code === "agent_device_revoked" || error.status === 404);
246
259
  }
@@ -20,6 +20,7 @@
20
20
  "The CLI reuses a valid cached device session.",
21
21
  "On one HTTP 401 with code agent_device_session_required, it invalidates only the current Backend and Agent Type session, renews it, and retries the exact request once.",
22
22
  "A second failure is returned to the Agent. The CLI does not loop, switch Agent Type, rotate the key, or create a replacement v2 device.",
23
+ "When a previously unseen Agent Type is registered under an existing Device, the CLI skips locally known Agent Instances that the Backend reports as revoked and uses the first remaining valid Instance. It never reactivates a revoked Instance or replaces the Device; if none is valid, registration stops.",
23
24
  "A revoked v2 registration requires explicit operator recovery; it is never silently replaced."
24
25
  ],
25
26
  "local_state_recovery": [
@@ -33,6 +33,7 @@
33
33
  - OpenClaw 的 IM Host 必须提供 `--target`。缺失时在任何 Checkout 创建前返回 `target_required`。
34
34
  - `kimi-code` 是 CLI 型 Agent,复用 `terminal` Host 和现有 CLI 展示,不增加 Kimi 专属交易协议。
35
35
  - session 失效时 CLI 只续期并重试原请求一次;再次失败立即返回。revoked v2 Device 不自动换身份。
36
+ - 同一 Device 首次登记新的 Agent Type 时,CLI 只使用本地已登记且 Backend 仍接受的既有 Agent Instance 完成签名登记;被撤销的 Instance 会被跳过且不会恢复。若没有任何既有 Instance 可用,CLI 必须停止,不得重新登记 Device、旋转私钥或借用其他 Backend。
36
37
 
37
38
  ## Checkout Handoff 最小合同
38
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itpay/cli",
3
- "version": "2.0.29",
3
+ "version": "2.0.30",
4
4
  "description": "The single ItPay CLI entry point for buy workflows and future sell workflows.",
5
5
  "type": "module",
6
6
  "bin": {