@yoooclaw/phone-notifications 1.11.14 → 1.11.16

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/README.md CHANGED
@@ -152,7 +152,7 @@ bash install.sh --tgz-url ./yoooclaw-phone-notifications-1.2.3.tgz
152
152
  5. 写入 `<configPath>` 配置:
153
153
  - `plugins.entries` — 启用插件,合并用户配置
154
154
  - `plugins.installs` — 记录安装来源和版本
155
- - `tools.alsoAllow` / `agents.main.tools.alsoAllow` — 自动放行 `light_control` 与 `lightrules_*` 工具
155
+ - `tools.allow` / `tools.alsoAllow` 与 `agents.main.tools.*` — 自动放行 `light_control` 与 `lightrules_*` 工具
156
156
  - 若宿主已启用 `plugins.allow` 白名单,会自动补入 `phone-notifications`,确保插件能被宿主加载
157
157
  - 清理该插件旧的 `plugins.load.paths` / 旧安装记录,避免继续命中之前通过 `openclaw plugins install` 或 `--link` 保留的副本
158
158
  6. 如传入 `--api-key` / `--token`,写入 `<stateDir>/credentials.json`
package/dist/index.cjs CHANGED
@@ -5574,7 +5574,7 @@ function readBuildInjectedVersion() {
5574
5574
  if (false) {
5575
5575
  return void 0;
5576
5576
  }
5577
- const version = "1.11.14".trim();
5577
+ const version = "1.11.16".trim();
5578
5578
  return version || void 0;
5579
5579
  }
5580
5580
  function readPluginVersionFromPackageJson() {
@@ -9243,6 +9243,39 @@ function ensureArray(obj, key) {
9243
9243
  obj[key] = next;
9244
9244
  return next;
9245
9245
  }
9246
+ function hasOwnProperty(obj, key) {
9247
+ return Object.prototype.hasOwnProperty.call(obj, key);
9248
+ }
9249
+ function appendUniqueValues(target, values) {
9250
+ let changed = false;
9251
+ for (const value of values) {
9252
+ if (!target.includes(value)) {
9253
+ target.push(value);
9254
+ changed = true;
9255
+ }
9256
+ }
9257
+ return changed;
9258
+ }
9259
+ function upsertToolsForPolicy(policy, tools) {
9260
+ if (Array.isArray(policy.allow)) {
9261
+ let changed = false;
9262
+ if (Array.isArray(policy.alsoAllow)) {
9263
+ changed = appendUniqueValues(
9264
+ policy.allow,
9265
+ policy.alsoAllow.filter((value) => typeof value === "string")
9266
+ );
9267
+ }
9268
+ changed = appendUniqueValues(policy.allow, tools) || changed;
9269
+ if (hasOwnProperty(policy, "alsoAllow")) {
9270
+ delete policy.alsoAllow;
9271
+ changed = true;
9272
+ }
9273
+ return changed;
9274
+ }
9275
+ if (hasOwnProperty(policy, "allow")) return false;
9276
+ const alsoAllow = ensureArray(policy, "alsoAllow");
9277
+ return appendUniqueValues(alsoAllow, tools);
9278
+ }
9246
9279
  function resolveConfigPath2() {
9247
9280
  const fromEnv = process.env.OPENCLAW_CONFIG_PATH?.trim();
9248
9281
  if (fromEnv) return fromEnv;
@@ -9251,14 +9284,7 @@ function resolveConfigPath2() {
9251
9284
  var LIGHT_TOOLS = ["light_control", ...LIGHT_RULE_TOOL_NAME_LIST];
9252
9285
  function upsertLightControlAlsoAllow(cfg) {
9253
9286
  if (!isObject(cfg.tools)) cfg.tools = {};
9254
- const toolsAlsoAllow = ensureArray(cfg.tools, "alsoAllow");
9255
- let globalChanged = false;
9256
- for (const tool of LIGHT_TOOLS) {
9257
- if (!toolsAlsoAllow.includes(tool)) {
9258
- toolsAlsoAllow.push(tool);
9259
- globalChanged = true;
9260
- }
9261
- }
9287
+ const globalChanged = upsertToolsForPolicy(cfg.tools, LIGHT_TOOLS);
9262
9288
  if (!isObject(cfg.agents)) cfg.agents = {};
9263
9289
  const agents = cfg.agents;
9264
9290
  const list = ensureArray(agents, "list");
@@ -9270,18 +9296,11 @@ function upsertLightControlAlsoAllow(cfg) {
9270
9296
  list.push(mainAgent);
9271
9297
  }
9272
9298
  if (!isObject(mainAgent.tools)) mainAgent.tools = {};
9273
- const mainAlsoAllow = ensureArray(mainAgent.tools, "alsoAllow");
9274
- let mainAgentChanged = false;
9275
- for (const tool of LIGHT_TOOLS) {
9276
- if (!mainAlsoAllow.includes(tool)) {
9277
- mainAlsoAllow.push(tool);
9278
- mainAgentChanged = true;
9279
- }
9280
- }
9299
+ const mainAgentChanged = upsertToolsForPolicy(mainAgent.tools, LIGHT_TOOLS);
9281
9300
  return { globalChanged, mainAgentChanged };
9282
9301
  }
9283
9302
  function registerLightSetupTools(light) {
9284
- light.command("setup").description("\u81EA\u52A8\u653E\u884C light_control\uFF08\u5199\u5165 tools.alsoAllow \u4E0E agents.main.tools.alsoAllow\uFF09").action(() => {
9303
+ light.command("setup").description("\u81EA\u52A8\u653E\u884C light_control\uFF08\u517C\u5BB9 tools.allow / tools.alsoAllow\uFF09").action(() => {
9285
9304
  const configPath = resolveConfigPath2();
9286
9305
  if (!(0, import_node_fs12.existsSync)(configPath)) {
9287
9306
  exitError("CONFIG_NOT_FOUND", `\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
@@ -12009,56 +12028,123 @@ init_asr();
12009
12028
  // src/recording/account-oss.ts
12010
12029
  init_credentials();
12011
12030
  init_env();
12012
- async function deleteAccountOssFile(fileUrl, logger) {
12013
- const trimmed = fileUrl?.trim();
12014
- if (!trimmed) {
12015
- return { ok: false, error: "fileUrl is empty" };
12031
+ var DEFAULT_DELETE_MAX_ATTEMPTS = 3;
12032
+ var DEFAULT_DELETE_BACKOFF_MS = 500;
12033
+ function getDeleteMaxAttempts() {
12034
+ const raw = process.env.OPENCLAW_OSS_DELETE_MAX_ATTEMPTS;
12035
+ if (raw) {
12036
+ const parsed = Number(raw);
12037
+ if (Number.isFinite(parsed) && parsed >= 1) return Math.floor(parsed);
12016
12038
  }
12017
- const apiKey = loadApiKey();
12018
- if (!apiKey) {
12019
- return { ok: false, error: "API Key \u672A\u8BBE\u7F6E\uFF0C\u8DF3\u8FC7 OSS \u6587\u4EF6\u5220\u9664" };
12039
+ return DEFAULT_DELETE_MAX_ATTEMPTS;
12040
+ }
12041
+ function getDeleteBackoffMs() {
12042
+ const raw = process.env.OPENCLAW_OSS_DELETE_BACKOFF_MS;
12043
+ if (raw) {
12044
+ const parsed = Number(raw);
12045
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
12020
12046
  }
12021
- const baseEndpoint = getEnvUrls().accountFileDeleteUrl;
12022
- const headerKey = apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey;
12023
- const url = new URL(baseEndpoint);
12024
- url.searchParams.set("fileUrl", trimmed);
12025
- const endpoint = url.toString();
12026
- logger.info(`[account-oss-delete] \u63D0\u4EA4 OSS \u6587\u4EF6\u5220\u9664: endpoint=${endpoint}`);
12047
+ return DEFAULT_DELETE_BACKOFF_MS;
12048
+ }
12049
+ function sleep3(ms) {
12050
+ return new Promise((resolve) => setTimeout(resolve, ms));
12051
+ }
12052
+ function isRetryableStatus(status) {
12053
+ return status === 429 || status >= 500;
12054
+ }
12055
+ function parseBody(text) {
12056
+ if (!text) return void 0;
12057
+ try {
12058
+ return JSON.parse(text);
12059
+ } catch {
12060
+ return "invalid-json";
12061
+ }
12062
+ }
12063
+ function extractCode(payload) {
12064
+ if (!payload) return void 0;
12065
+ if (typeof payload.code === "number") return String(payload.code);
12066
+ return payload.code?.trim?.();
12067
+ }
12068
+ async function attemptDelete(endpoint, headerKey, logger) {
12027
12069
  let res;
12028
12070
  try {
12029
12071
  res = await fetch(endpoint, {
12030
12072
  method: "POST",
12031
- headers: {
12032
- "X-Api-Key-Id": headerKey
12033
- }
12073
+ headers: { "X-Api-Key-Id": headerKey }
12034
12074
  });
12035
12075
  } catch (err2) {
12036
- const error = err2?.message ?? String(err2);
12037
- logger.warn(`[account-oss-delete] \u7F51\u7EDC\u5F02\u5E38: ${error}`);
12038
- return { ok: false, error };
12076
+ return { kind: "retryable", error: err2?.message ?? String(err2) };
12039
12077
  }
12040
12078
  const text = await res.text();
12041
12079
  if (!res.ok) {
12080
+ const error = `HTTP ${res.status} ${text.slice(0, 200)}`;
12081
+ if (isRetryableStatus(res.status)) {
12082
+ return { kind: "retryable", error };
12083
+ }
12042
12084
  logger.warn(
12043
12085
  `[account-oss-delete] HTTP \u9519\u8BEF: status=${res.status}, body=${text.slice(0, 300)}`
12044
12086
  );
12045
- return { ok: false, error: `HTTP ${res.status} ${text.slice(0, 200)}` };
12087
+ return { kind: "permanent", error };
12046
12088
  }
12047
- let payload;
12048
- try {
12049
- payload = text ? JSON.parse(text) : void 0;
12050
- } catch {
12089
+ const payload = parseBody(text);
12090
+ if (payload === "invalid-json") {
12051
12091
  logger.warn(`[account-oss-delete] \u54CD\u5E94\u975E JSON: body=${text.slice(0, 300)}`);
12052
- return { ok: false, error: "response is not JSON" };
12092
+ return { kind: "permanent", error: "response is not JSON" };
12053
12093
  }
12054
- const code = typeof payload?.code === "number" ? String(payload.code) : payload?.code?.trim?.();
12094
+ const code = extractCode(payload);
12055
12095
  if (code !== "000000") {
12056
12096
  const msg = payload?.msg?.trim?.() || text.slice(0, 200);
12057
12097
  logger.warn(`[account-oss-delete] \u4E1A\u52A1\u5931\u8D25: code=${code ?? "n/a"}, msg=${msg}`);
12058
- return { ok: false, error: `${code ?? "unknown"} ${msg}`.trim() };
12098
+ return { kind: "permanent", error: `${code ?? "unknown"} ${msg}`.trim() };
12099
+ }
12100
+ return { kind: "ok" };
12101
+ }
12102
+ async function deleteAccountOssFile(fileUrl, logger, options) {
12103
+ const trimmed = fileUrl?.trim();
12104
+ if (!trimmed) {
12105
+ return { ok: false, error: "fileUrl is empty" };
12106
+ }
12107
+ const apiKey = loadApiKey();
12108
+ if (!apiKey) {
12109
+ return { ok: false, error: "API Key \u672A\u8BBE\u7F6E\uFF0C\u8DF3\u8FC7 OSS \u6587\u4EF6\u5220\u9664" };
12110
+ }
12111
+ const baseEndpoint = getEnvUrls().accountFileDeleteUrl;
12112
+ const headerKey = apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey;
12113
+ const url = new URL(baseEndpoint);
12114
+ url.searchParams.set("fileUrl", trimmed);
12115
+ const endpoint = url.toString();
12116
+ const maxAttempts = Math.max(
12117
+ 1,
12118
+ Math.floor(options?.maxAttempts ?? getDeleteMaxAttempts())
12119
+ );
12120
+ const baseBackoff = Math.max(0, options?.backoffMs ?? getDeleteBackoffMs());
12121
+ logger.info(
12122
+ `[account-oss-delete] \u63D0\u4EA4 OSS \u6587\u4EF6\u5220\u9664: endpoint=${endpoint}, maxAttempts=${maxAttempts}`
12123
+ );
12124
+ let lastError = "unknown error";
12125
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
12126
+ const outcome = await attemptDelete(endpoint, headerKey, logger);
12127
+ if (outcome.kind === "ok") {
12128
+ logger.info(`[account-oss-delete] OSS \u6587\u4EF6\u5DF2\u5220\u9664: fileUrl=${trimmed}`);
12129
+ return { ok: true };
12130
+ }
12131
+ if (outcome.kind === "permanent") {
12132
+ return { ok: false, error: outcome.error };
12133
+ }
12134
+ lastError = outcome.error;
12135
+ if (attempt < maxAttempts) {
12136
+ const delay = baseBackoff * Math.pow(2, attempt - 1);
12137
+ logger.warn(
12138
+ `[account-oss-delete] \u4E34\u65F6\u5931\u8D25 (attempt ${attempt}/${maxAttempts}): ${outcome.error}, ${delay}ms \u540E\u91CD\u8BD5`
12139
+ );
12140
+ await sleep3(delay);
12141
+ } else {
12142
+ logger.warn(
12143
+ `[account-oss-delete] \u4E34\u65F6\u5931\u8D25 (attempt ${attempt}/${maxAttempts}, \u653E\u5F03): ${outcome.error}`
12144
+ );
12145
+ }
12059
12146
  }
12060
- logger.info(`[account-oss-delete] OSS \u6587\u4EF6\u5DF2\u5220\u9664: fileUrl=${trimmed}`);
12061
- return { ok: true };
12147
+ return { ok: false, error: lastError };
12062
12148
  }
12063
12149
 
12064
12150
  // src/recording/handler.ts
@@ -13386,6 +13472,15 @@ var WsProxy = class {
13386
13472
 
13387
13473
  // src/tunnel/proxy.ts
13388
13474
  var RELAY_TUNNEL_GATEWAY_CLIENT_INSTANCE_ID = "phone-notifications-relay-tunnel";
13475
+ var GATEWAY_OPERATOR_SCOPES = [
13476
+ "operator.admin",
13477
+ "operator.approvals",
13478
+ "operator.pairing",
13479
+ "operator.read",
13480
+ "operator.write"
13481
+ ];
13482
+ var GATEWAY_RPC_MIN_PROTOCOL = 3;
13483
+ var GATEWAY_RPC_MAX_PROTOCOL = 4;
13389
13484
  var MAX_AUTO_PAIRING_APPROVALS = 3;
13390
13485
  var RECENT_ABORTED_CHAT_RUN_TTL_MS = 6e4;
13391
13486
  var GATEWAY_RPC_HANDSHAKE_TIMEOUT_MS = 1e4;
@@ -13981,7 +14076,7 @@ var TunnelProxy = class {
13981
14076
  clearHandshakeTimer();
13982
14077
  this.storeIssuedDeviceToken({
13983
14078
  fallbackRole: "operator",
13984
- fallbackScopes: ["operator.admin"],
14079
+ fallbackScopes: [...GATEWAY_OPERATOR_SCOPES],
13985
14080
  authInfo: frame.payload?.auth
13986
14081
  });
13987
14082
  this.gatewayWsAutoPairingApprovals = 0;
@@ -14090,7 +14185,7 @@ var TunnelProxy = class {
14090
14185
  const challengeNonce = frame.payload?.nonce ?? "";
14091
14186
  const connectRequestId = `tunnel-connect-${(0, import_node_crypto5.randomUUID)()}`;
14092
14187
  const role = "operator";
14093
- const scopes = ["operator.admin"];
14188
+ const scopes = [...GATEWAY_OPERATOR_SCOPES];
14094
14189
  const gatewayConnectAuth = this.buildGatewayConnectAuth(role);
14095
14190
  this.opts.logger.info(
14096
14191
  `TunnelProxy: received connect.challenge (nonce=${challengeNonce}, connectReqId=${connectRequestId}, hasToken=${!!gatewayConnectAuth.authToken}, hasPassword=${!!gatewayConnectAuth.authPassword}, hasDeviceToken=${!!gatewayConnectAuth.deviceToken}), sending connect request with device identity`
@@ -14117,8 +14212,8 @@ var TunnelProxy = class {
14117
14212
  id: connectRequestId,
14118
14213
  method: "connect",
14119
14214
  params: {
14120
- minProtocol: 3,
14121
- maxProtocol: 3,
14215
+ minProtocol: GATEWAY_RPC_MIN_PROTOCOL,
14216
+ maxProtocol: GATEWAY_RPC_MAX_PROTOCOL,
14122
14217
  client: {
14123
14218
  id: clientId,
14124
14219
  version: "1.0.0",