@clawos-dev/clawd 0.2.437 → 0.2.439

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/dist/cli.cjs CHANGED
@@ -6944,13 +6944,14 @@ var init_github = __esm({
6944
6944
  });
6945
6945
 
6946
6946
  // ../protocol/src/lark-bot-schemas.ts
6947
- var LarkBotPersonaArgs, LarkBotUnbindArgs, LarkBotStatusArgs, LarkBotProvisionArgs, LarkBotProvisionCancelArgs, LARK_BOT_PROVISION_ERROR_REASONS, LarkBotProvisionErrorReasonSchema, LarkBotBindManualArgs, LarkBotGroupSchema, LarkBotStatusResultSchema, LarkBotStateFrameSchema;
6947
+ var LarkBotPersonaArgs, LarkBotUnbindArgs, LarkBotUpgradeArgs, LarkBotStatusArgs, LarkBotProvisionArgs, LarkBotProvisionCancelArgs, LARK_BOT_PROVISION_ERROR_REASONS, LarkBotProvisionErrorReasonSchema, LarkBotBindManualArgs, LarkBotGroupSchema, LarkBotStatusResultSchema, LarkBotStateFrameSchema;
6948
6948
  var init_lark_bot_schemas = __esm({
6949
6949
  "../protocol/src/lark-bot-schemas.ts"() {
6950
6950
  "use strict";
6951
6951
  init_zod();
6952
6952
  LarkBotPersonaArgs = external_exports.object({ personaId: external_exports.string().min(1) });
6953
6953
  LarkBotUnbindArgs = LarkBotPersonaArgs;
6954
+ LarkBotUpgradeArgs = external_exports.object({ personaId: external_exports.string().min(1) });
6954
6955
  LarkBotStatusArgs = LarkBotPersonaArgs;
6955
6956
  LarkBotProvisionArgs = LarkBotPersonaArgs;
6956
6957
  LarkBotProvisionCancelArgs = LarkBotPersonaArgs;
@@ -6984,7 +6985,21 @@ var init_lark_bot_schemas = __esm({
6984
6985
  qrUrl: external_exports.string().optional(),
6985
6986
  /** provision 失败后的 unbound 帧携带;正常 unbound 无此字段 */
6986
6987
  errorReason: LarkBotProvisionErrorReasonSchema.optional(),
6987
- groups: external_exports.array(LarkBotGroupSchema)
6988
+ groups: external_exports.array(LarkBotGroupSchema),
6989
+ /**
6990
+ * bound 态:该 bot 实际授予的 scope 缺了 LARK_PROVISION_ADDONS 里声明的项
6991
+ * (spec 2026-09-01)——addons 是扫码时一次性写进飞书应用配置的,后来新增的
6992
+ * scope / 事件对存量 bot 不生效,不提示的话用户只会觉得「功能没生效」。
6993
+ * 判定真源是飞书回读的 granted scopes,不落库任何版本号。
6994
+ */
6995
+ needsUpgrade: external_exports.boolean().optional(),
6996
+ /**
6997
+ * bound 态叠加:正在扫码更新授权(spec 2026-09-01)。
6998
+ * **不复用 provisioning 态**——升级期间 bot 仍在正常服务(长连接不断、群照常响应),
6999
+ * 降级成 provisioning 会丢掉 botName / groups,UI 上像是断了。
7000
+ * 有值即渲染二维码弹层;升级结束(成功或取消)该字段消失。
7001
+ */
7002
+ upgradeQrUrl: external_exports.string().optional()
6988
7003
  });
6989
7004
  LarkBotStateFrameSchema = LarkBotStatusResultSchema.extend({
6990
7005
  personaId: external_exports.string().min(1)
@@ -7036,7 +7051,7 @@ var init_onboarding_schemas = __esm({
7036
7051
  });
7037
7052
 
7038
7053
  // ../protocol/src/persona-schemas.ts
7039
- var PersonaFileSchema, PersonaSkillSummarySchema, PersonaPluginSummarySchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, CodexSandboxSettingsSchema;
7054
+ var PersonaFileSchema, PersonaSkillSummarySchema, PersonaPluginSummarySchema, CodexSandboxSettingsSchema, PersonaSandboxSettingsSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema;
7040
7055
  var init_persona_schemas = __esm({
7041
7056
  "../protocol/src/persona-schemas.ts"() {
7042
7057
  "use strict";
@@ -7069,6 +7084,17 @@ var init_persona_schemas = __esm({
7069
7084
  // settings.json 中 enabledPlugins 的 key,例如 'superpowers@claude-plugins-official'
7070
7085
  id: external_exports.string().min(1)
7071
7086
  });
7087
+ CodexSandboxSettingsSchema = external_exports.object({
7088
+ // 额外可写的绝对路径或 ~ 路径(pnpm 工具链等);workspace(cwd) 和 userWorkDir 由 daemon 自动加,不用列。
7089
+ writableRoots: external_exports.array(external_exports.string()).optional(),
7090
+ // 禁读 glob(相对 workspace root 或绝对),如 ".secrets/**"、"**/*.env"。
7091
+ denyRead: external_exports.array(external_exports.string()).optional(),
7092
+ // true → 网络全放(*);false → 断网。**缺省等于联网**:daemon 写入的默认值是 network:true
7093
+ // (对齐 claude guest 一贯的域名全放,见 spec 2026-09-01-codex-guest-network §1.3),
7094
+ // 且 manager / UI 读到 undefined 时都回落 true —— 两侧必须同向,否则出现
7095
+ // 「卡片显示开、实际断网」的幽灵态。
7096
+ network: external_exports.boolean().optional()
7097
+ }).strict();
7072
7098
  PersonaSandboxSettingsSchema = external_exports.object({
7073
7099
  permissions: external_exports.object({
7074
7100
  defaultMode: external_exports.string().optional(),
@@ -7117,7 +7143,10 @@ var init_persona_schemas = __esm({
7117
7143
  skills: external_exports.array(PersonaSkillSummarySchema).optional(),
7118
7144
  // 仅 'persona:get' 携带;空数组 = 没启用任何插件 / settings.json 不存在;undefined = 不在该响应类型上
7119
7145
  plugins: external_exports.array(PersonaPluginSummarySchema).optional(),
7120
- sandboxSettings: PersonaSandboxSettingsSchema.nullable().optional()
7146
+ sandboxSettings: PersonaSandboxSettingsSchema.nullable().optional(),
7147
+ // 仅 'persona:get' 携带;null = codex-sandbox.json 不存在 / parse 失败。
7148
+ // UI 的「访客联网」开关读它(见 PersonaCardSettings)。
7149
+ codexSandbox: CodexSandboxSettingsSchema.nullable().optional()
7121
7150
  });
7122
7151
  PersonaCreateArgsSchema = external_exports.object({
7123
7152
  // persona 身份。daemon 拼成 personaId = `persona-<slug>`——前缀由 daemon 拥有,调用方只给后半段,
@@ -7149,17 +7178,13 @@ var init_persona_schemas = __esm({
7149
7178
  personalityLocal: external_exports.string().optional(),
7150
7179
  public: external_exports.boolean().optional(),
7151
7180
  // pass `null` to clear; daemon strips the field from PersonaFile on null
7152
- iconKey: external_exports.string().nullable().optional()
7181
+ iconKey: external_exports.string().nullable().optional(),
7182
+ // codex guest 沙箱(落盘 .clawd/codex-sandbox.json,不在 PersonaFile 里)。与 personality
7183
+ // 同款分层写:handler 剥出来单独落盘,且**局部合并**——只带 network 的 patch 不能抹掉
7184
+ // owner 手配的 writableRoots / denyRead。
7185
+ codexSandbox: CodexSandboxSettingsSchema.optional()
7153
7186
  }).strict()
7154
7187
  }).strict();
7155
- CodexSandboxSettingsSchema = external_exports.object({
7156
- // 额外可写的绝对路径或 ~ 路径(pnpm 工具链等);workspace(cwd) 和 userWorkDir 由 daemon 自动加,不用列。
7157
- writableRoots: external_exports.array(external_exports.string()).optional(),
7158
- // 禁读 glob(相对 workspace root 或绝对),如 ".secrets/**"、"**/*.env"。
7159
- denyRead: external_exports.array(external_exports.string()).optional(),
7160
- // true → 网络全放(*);缺省/false → 断网。
7161
- network: external_exports.boolean().optional()
7162
- }).strict();
7163
7188
  }
7164
7189
  });
7165
7190
 
@@ -8712,6 +8737,10 @@ var init_methods = __esm({
8712
8737
  summary: "\u89E3\u7ED1 persona \u7684\u98DE\u4E66 bot\uFF08\u5220\u4E91\u7AEF\u7ED1\u5B9A + \u65AD\u957F\u8FDE\u63A5\uFF0C\u98DE\u4E66\u4FA7 app \u4FDD\u7559\uFF09\uFF1Bowner-only",
8713
8738
  args: LarkBotUnbindArgs
8714
8739
  },
8740
+ "larkBot:upgrade": {
8741
+ summary: "\u626B\u7801\u66F4\u65B0\u5DF2\u7ED1\u5B9A bot \u7684\u6388\u6743\uFF08\u8865\u540E\u6765\u65B0\u589E\u7684 scope / \u4E8B\u4EF6\uFF09\uFF1B\u5168\u7A0B\u4FDD\u6301 bound\uFF0Cbot \u4E0D\u65AD\u8FDE\uFF1Bowner-only",
8742
+ args: LarkBotUpgradeArgs
8743
+ },
8715
8744
  "larkBot:status": {
8716
8745
  summary: "\u67E5 persona \u7684\u98DE\u4E66 bot \u72B6\u6001\uFF08unbound/bound/broken + \u670D\u52A1\u4E2D\u7FA4\u6E05\u5355\uFF09\uFF1Bowner-only",
8717
8746
  args: LarkBotStatusArgs
@@ -47040,7 +47069,7 @@ var SessionManager = class {
47040
47069
  subSessionMeta.codexSandbox = {
47041
47070
  writableRoots: [subSessionMeta.userWorkDir, ...(codexCfg?.writableRoots ?? []).map(expand)],
47042
47071
  denyRead: codexCfg?.denyRead ?? [],
47043
- network: codexCfg?.network ?? false
47072
+ network: codexCfg?.network ?? true
47044
47073
  };
47045
47074
  }
47046
47075
  if (subSessionMeta?.personaMode === "owner" && file.projectPath) {
@@ -49778,6 +49807,24 @@ var PersonaManager = class {
49778
49807
  return this.deps.store.readEnabledPlugins(personaId2);
49779
49808
  }
49780
49809
  /** 读 sandbox-settings.json;不存在 / 损坏返回 null。供 persona:get handler 拼响应。 */
49810
+ /** 读 codex guest 沙箱配置(`.clawd/codex-sandbox.json`);缺失 / 坏 → null。透传给 handler 拼响应。 */
49811
+ readCodexSandboxSettings(personaId2) {
49812
+ return this.deps.store.readCodexSandboxSettings(personaId2);
49813
+ }
49814
+ /**
49815
+ * 局部合并写 codex guest 沙箱配置,返回合并后的全量。
49816
+ *
49817
+ * **必须是合并不是覆盖**:UI 的「访客联网」开关只发 `{network}`,整体覆盖会静默抹掉 owner
49818
+ * 手配的 writableRoots / denyRead(丢的是安全边界,且用户不会立刻发现)。
49819
+ * 不走 PersonaManager.update 是因为它管的是 PersonaFile meta + 人格两层文本,
49820
+ * 而这份配置是另一个文件、另一套 schema。
49821
+ */
49822
+ mergeCodexSandbox(personaId2, patch) {
49823
+ if (!this.deps.registry.get(personaId2)) throw new Error(`persona not found: ${personaId2}`);
49824
+ const merged = { ...this.deps.store.readCodexSandboxSettings(personaId2) ?? {}, ...patch };
49825
+ this.deps.store.writeCodexSandboxSettings(personaId2, merged);
49826
+ return merged;
49827
+ }
49781
49828
  readSandboxSettings(personaId2) {
49782
49829
  return this.deps.store.readSandboxSettings(personaId2);
49783
49830
  }
@@ -50179,13 +50226,15 @@ function migrateAgentsMirror(args) {
50179
50226
  }
50180
50227
  if (mirrored > 0) args.logger.info("persona.agents-mirror.done", { mirrored });
50181
50228
  }
50229
+ var PKG_CACHE_ROOTS = ["~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store"];
50230
+ var CODEX_SANDBOX_DEFAULT = { writableRoots: [...PKG_CACHE_ROOTS], denyRead: [], network: true };
50182
50231
  function migrateCodexSandbox(args) {
50183
50232
  const entries = args.entries ?? DEFAULT_PERSONAS;
50184
50233
  let n = 0;
50185
50234
  for (const personaId2 of args.store.list()) {
50186
50235
  if (args.store.readCodexSandboxSettings(personaId2)) continue;
50187
50236
  try {
50188
- const def = entries.find((e) => e.personaId === personaId2)?.codexSandbox ?? { writableRoots: [], denyRead: [], network: false };
50237
+ const def = entries.find((e) => e.personaId === personaId2)?.codexSandbox ?? CODEX_SANDBOX_DEFAULT;
50189
50238
  args.store.writeCodexSandboxSettings(personaId2, def);
50190
50239
  n++;
50191
50240
  args.logger.info("persona.codex-sandbox.synced", { personaId: personaId2 });
@@ -54030,6 +54079,7 @@ function buildShiftInternalHandlers(deps) {
54030
54079
  init_protocol();
54031
54080
  var PROVISION_ERROR_SET = new Set(LARK_BOT_PROVISION_ERROR_REASONS);
54032
54081
  var PROVISION_POLL_INTERVAL_MS = 2e3;
54082
+ var UPGRADE_POLL_MAX_FAILURES = 5;
54033
54083
  var PROVISION_MAX_CONSECUTIVE_FAILURES = 5;
54034
54084
  var PROVISION_POLL_MAX_MS = 60 * 60 * 1e3;
54035
54085
  function normalizeProvisionReason(reason) {
@@ -54043,6 +54093,7 @@ function buildLarkBotHandlers(deps) {
54043
54093
  const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
54044
54094
  const pollIntervalMs = deps.pollIntervalMs ?? PROVISION_POLL_INTERVAL_MS;
54045
54095
  const inFlight = /* @__PURE__ */ new Map();
54096
+ const upgradeInFlight = /* @__PURE__ */ new Map();
54046
54097
  const groupsOf = (personaId2) => {
54047
54098
  const removed = new Set(deps.store.read(personaId2)?.removedChatIds ?? []);
54048
54099
  return deps.listLarkSessions(personaId2).filter((g2) => !removed.has(g2.chatId));
@@ -54141,8 +54192,121 @@ function buildLarkBotHandlers(deps) {
54141
54192
  void runProvisionPolling(personaId2, provisionId);
54142
54193
  return { response: { type: "larkBot:provision:ok" } };
54143
54194
  };
54195
+ const runUpgradePolling = async (personaId2) => {
54196
+ const backToBound = (needsUpgrade) => {
54197
+ upgradeInFlight.delete(personaId2);
54198
+ const fresh = deps.store.read(personaId2);
54199
+ if (!fresh) {
54200
+ emit(personaId2, { state: "unbound", groups: [] });
54201
+ return;
54202
+ }
54203
+ emit(personaId2, {
54204
+ state: "bound",
54205
+ appId: fresh.appId,
54206
+ botName: fresh.botName,
54207
+ ...needsUpgrade ? { needsUpgrade: true } : {},
54208
+ groups: groupsOf(personaId2)
54209
+ });
54210
+ };
54211
+ const startedAt = now();
54212
+ let failures = 0;
54213
+ for (; ; ) {
54214
+ await sleep2(pollIntervalMs);
54215
+ const entry = upgradeInFlight.get(personaId2);
54216
+ if (!entry || entry.cancelled) return;
54217
+ if (now() - startedAt >= PROVISION_POLL_MAX_MS) {
54218
+ deps.logger?.warn(`larkBot upgrade poll deadline exceeded: ${personaId2}`);
54219
+ backToBound(true);
54220
+ return;
54221
+ }
54222
+ let stillNeeds;
54223
+ try {
54224
+ const binding = (await deps.cloud.listBindings()).find((b2) => b2.personaId === personaId2);
54225
+ if (!binding) {
54226
+ backToBound(false);
54227
+ return;
54228
+ }
54229
+ stillNeeds = binding.needsUpgrade !== false;
54230
+ failures = 0;
54231
+ if (!upgradeInFlight.get(personaId2) || upgradeInFlight.get(personaId2)?.cancelled) return;
54232
+ } catch (err) {
54233
+ failures += 1;
54234
+ deps.logger?.warn(`larkBot upgrade poll failed (${failures}): ${err.message}`);
54235
+ if (failures >= UPGRADE_POLL_MAX_FAILURES) {
54236
+ backToBound(true);
54237
+ return;
54238
+ }
54239
+ await sleep2(pollIntervalMs * 2 ** failures);
54240
+ continue;
54241
+ }
54242
+ if (!stillNeeds) {
54243
+ backToBound(false);
54244
+ return;
54245
+ }
54246
+ }
54247
+ };
54248
+ const upgrade = async (frame) => {
54249
+ const { personaId: personaId2 } = parseArgs2(LarkBotUpgradeArgs, frame, "larkBot:upgrade");
54250
+ const meta = deps.store.read(personaId2);
54251
+ if (!meta) throw new Error(`larkBot not bound: ${personaId2}`);
54252
+ if (inFlight.has(personaId2) || upgradeInFlight.has(personaId2)) {
54253
+ throw new Error(`larkBot provision already in flight: ${personaId2}`);
54254
+ }
54255
+ upgradeInFlight.set(personaId2, { provisionId: "", qrUrl: "", cancelled: false });
54256
+ let provisionId;
54257
+ let qrUrl;
54258
+ try {
54259
+ ;
54260
+ ({ provisionId, qrUrl } = await deps.cloud.provisionUpgrade({ personaId: personaId2 }));
54261
+ } catch (err) {
54262
+ upgradeInFlight.delete(personaId2);
54263
+ throw err;
54264
+ }
54265
+ const placeholder = upgradeInFlight.get(personaId2);
54266
+ if (!placeholder || placeholder.cancelled) {
54267
+ upgradeInFlight.delete(personaId2);
54268
+ void deps.cloud.provisionCancel(provisionId).catch(
54269
+ (err) => deps.logger?.warn(`larkBot upgrade cancel-during-begin: ${err.message}`)
54270
+ );
54271
+ return { response: { type: "larkBot:upgrade:ok" } };
54272
+ }
54273
+ upgradeInFlight.set(personaId2, { provisionId, qrUrl, cancelled: false });
54274
+ emit(personaId2, {
54275
+ state: "bound",
54276
+ appId: meta.appId,
54277
+ botName: meta.botName,
54278
+ needsUpgrade: true,
54279
+ upgradeQrUrl: qrUrl,
54280
+ groups: groupsOf(personaId2)
54281
+ });
54282
+ void runUpgradePolling(personaId2);
54283
+ return { response: { type: "larkBot:upgrade:ok" } };
54284
+ };
54144
54285
  const provisionCancel = async (frame) => {
54145
54286
  const { personaId: personaId2 } = parseArgs2(LarkBotProvisionCancelArgs, frame, "larkBot:provisionCancel");
54287
+ const upgrading = upgradeInFlight.get(personaId2);
54288
+ if (upgrading) {
54289
+ upgrading.cancelled = true;
54290
+ upgradeInFlight.delete(personaId2);
54291
+ if (upgrading.provisionId) {
54292
+ await deps.cloud.provisionCancel(upgrading.provisionId).catch(
54293
+ (err) => deps.logger?.warn(`larkBot upgrade cancel cloud failed: ${err.message}`)
54294
+ );
54295
+ }
54296
+ const meta = deps.store.read(personaId2);
54297
+ emit(
54298
+ personaId2,
54299
+ meta ? {
54300
+ state: "bound",
54301
+ appId: meta.appId,
54302
+ botName: meta.botName,
54303
+ // 取消了就是没升成,提示条留着
54304
+ needsUpgrade: true,
54305
+ groups: groupsOf(personaId2)
54306
+ } : { state: "unbound", groups: [] }
54307
+ );
54308
+ return { response: { type: "larkBot:provisionCancel:ok" } };
54309
+ }
54146
54310
  const entry = inFlight.get(personaId2);
54147
54311
  if (!entry) throw new Error(`larkBot provision not in flight: ${personaId2}`);
54148
54312
  entry.cancelled = true;
@@ -54182,6 +54346,7 @@ function buildLarkBotHandlers(deps) {
54182
54346
  const ownerId = deps.getOwnerId();
54183
54347
  if (!ownerId) throw new Error("larkBot bind requires feishu login (owner identity)");
54184
54348
  const name = botName ?? deps.getPersonaName(personaId2) ?? personaId2;
54349
+ upgradeInFlight.delete(personaId2);
54185
54350
  await deps.cloud.bind({ personaId: personaId2, appId, appSecret, botName: name, tunnelUrl, ownerId });
54186
54351
  deps.store.write(personaId2, { appId, botName: name, boundAt: now() });
54187
54352
  emit(personaId2, { state: "bound", appId, botName: name, groups: groupsOf(personaId2) });
@@ -54189,6 +54354,7 @@ function buildLarkBotHandlers(deps) {
54189
54354
  };
54190
54355
  const unbind = async (frame) => {
54191
54356
  const { personaId: personaId2 } = parseArgs2(LarkBotUnbindArgs, frame, "larkBot:unbind");
54357
+ upgradeInFlight.delete(personaId2);
54192
54358
  const meta = deps.store.read(personaId2);
54193
54359
  if (!meta) throw new Error(`larkBot not bound: ${personaId2}`);
54194
54360
  try {
@@ -54214,6 +54380,8 @@ function buildLarkBotHandlers(deps) {
54214
54380
  }
54215
54381
  };
54216
54382
  }
54383
+ const upgrading = upgradeInFlight.get(personaId2);
54384
+ const upgradingQr = upgrading && !upgrading.cancelled ? upgrading.qrUrl : void 0;
54217
54385
  const meta = deps.store.read(personaId2);
54218
54386
  let cloudReachable = false;
54219
54387
  let cloudBinding;
@@ -54235,7 +54403,10 @@ function buildLarkBotHandlers(deps) {
54235
54403
  state: broken2 ? "broken" : "bound",
54236
54404
  appId: cloudBinding.appId,
54237
54405
  botName: cloudBinding.botName,
54406
+ ...upgradingQr ? { needsUpgrade: true, upgradeQrUrl: upgradingQr } : {},
54238
54407
  ...broken2 ? { brokenReason: cloudBinding.brokenReason ?? "unknown" } : {},
54408
+ // 云端判定(它持有飞书回读的 granted scopes),daemon 只透传(spec 2026-09-01)
54409
+ ...cloudBinding.needsUpgrade ? { needsUpgrade: true } : {},
54239
54410
  groups: groupsOf(personaId2)
54240
54411
  };
54241
54412
  return { response: { type: "larkBot:status:ok", ...result2 } };
@@ -54262,7 +54433,10 @@ function buildLarkBotHandlers(deps) {
54262
54433
  state: broken ? "broken" : "bound",
54263
54434
  appId: cloudBinding?.appId ?? meta.appId,
54264
54435
  botName: cloudBinding?.botName ?? meta.botName,
54436
+ ...upgradingQr ? { needsUpgrade: true, upgradeQrUrl: upgradingQr } : {},
54265
54437
  ...broken ? { brokenReason: cloudBinding?.brokenReason ?? "unknown" } : {},
54438
+ // 云端不可达时 cloudBinding 为空 → 不提示升级(宁可不提示也不误报)
54439
+ ...cloudBinding?.needsUpgrade ? { needsUpgrade: true } : {},
54266
54440
  groups: groupsOf(personaId2)
54267
54441
  };
54268
54442
  return { response: { type: "larkBot:status:ok", ...result } };
@@ -54272,7 +54446,8 @@ function buildLarkBotHandlers(deps) {
54272
54446
  "larkBot:provisionCancel": provisionCancel,
54273
54447
  "larkBot:bindManual": bindManual,
54274
54448
  "larkBot:unbind": unbind,
54275
- "larkBot:status": status
54449
+ "larkBot:status": status,
54450
+ "larkBot:upgrade": upgrade
54276
54451
  };
54277
54452
  }
54278
54453
  async function refreshTunnelUrlOnStartup(deps) {
@@ -54392,6 +54567,10 @@ function createLarkBotCloudClient(opts) {
54392
54567
  const json = await request("POST", "/api/lark-bot/provision", { ...args });
54393
54568
  return { provisionId: String(json.provisionId), qrUrl: String(json.qrUrl) };
54394
54569
  },
54570
+ async provisionUpgrade(args) {
54571
+ const json = await request("POST", "/api/lark-bot/upgrade", { ...args });
54572
+ return { provisionId: String(json.provisionId), qrUrl: String(json.qrUrl) };
54573
+ },
54395
54574
  async provisionStatus(provisionId) {
54396
54575
  const json = await request("GET", `/api/lark-bot/provision/${encodeURIComponent(provisionId)}`);
54397
54576
  if (json.state === "success") {
@@ -62521,6 +62700,7 @@ var METHOD_GRANT_MAP = {
62521
62700
  "larkBot:bindManual": ADMIN_ANY,
62522
62701
  "larkBot:unbind": ADMIN_ANY,
62523
62702
  "larkBot:status": ADMIN_ANY,
62703
+ "larkBot:upgrade": ADMIN_ANY,
62524
62704
  // ---- onboarding:* / tools:probe / session:externalScan(首启 wizard,owner-only)----
62525
62705
  "onboarding:complete": ADMIN_ANY,
62526
62706
  "onboarding:iosInterest": ADMIN_ANY,
@@ -62657,7 +62837,7 @@ function computeMethodAccess(args) {
62657
62837
  }
62658
62838
 
62659
62839
  // src/version.ts
62660
- var version = "0.2.437".length > 0 ? "0.2.437" : "dev";
62840
+ var version = "0.2.439".length > 0 ? "0.2.439" : "dev";
62661
62841
 
62662
62842
  // src/cli-probe/probe.ts
62663
62843
  var fs65 = __toESM(require("fs"), 1);
@@ -62821,6 +63001,7 @@ function buildPersonaHandlers(deps) {
62821
63001
  const skills = personaManager.listSkills(args.personaId);
62822
63002
  const plugins = personaManager.listEnabledPlugins(args.personaId);
62823
63003
  const sandboxSettings = personaManager.readSandboxSettings(args.personaId);
63004
+ const codexSandbox = personaManager.readCodexSandboxSettings(args.personaId);
62824
63005
  return {
62825
63006
  response: {
62826
63007
  type: "persona:info",
@@ -62830,16 +63011,26 @@ function buildPersonaHandlers(deps) {
62830
63011
  personalityManaged,
62831
63012
  skills,
62832
63013
  plugins,
62833
- sandboxSettings
63014
+ sandboxSettings,
63015
+ codexSandbox
62834
63016
  }
62835
63017
  };
62836
63018
  };
62837
63019
  const update = async (frame) => {
62838
63020
  const { type: _type, requestId: _requestId, ...rest } = frame;
62839
63021
  const args = parseArgs2(PersonaUpdateArgsSchema, frame, "persona:update");
62840
- const { personality, personalityLocal, ...metaPatch } = args.patch;
63022
+ const { personality, personalityLocal, codexSandbox, ...metaPatch } = args.patch;
62841
63023
  const persona = personaManager.update(args.personaId, metaPatch, { personality, personalityLocal });
62842
- return { response: { type: "persona:info", ...persona } };
63024
+ const mergedCodexSandbox = codexSandbox ? personaManager.mergeCodexSandbox(args.personaId, codexSandbox) : void 0;
63025
+ return {
63026
+ response: {
63027
+ type: "persona:info",
63028
+ ...persona,
63029
+ // 只在本次确实动过时回带,让调用方拿到合并后的真值而不用再 get 一次;
63030
+ // 没动就不带(与 personality 等分层字段同口径:persona:update 的响应是 meta 视图)。
63031
+ ...mergedCodexSandbox ? { codexSandbox: mergedCodexSandbox } : {}
63032
+ }
63033
+ };
62843
63034
  };
62844
63035
  const del = async (frame) => {
62845
63036
  const args = parseArgs2(PersonaIdArgsSchema, frame, "persona:delete");
@@ -22118,6 +22118,7 @@ var GithubPrRequestReviewersArgs = external_exports.object({
22118
22118
  // ../protocol/src/lark-bot-schemas.ts
22119
22119
  var LarkBotPersonaArgs = external_exports.object({ personaId: external_exports.string().min(1) });
22120
22120
  var LarkBotUnbindArgs = LarkBotPersonaArgs;
22121
+ var LarkBotUpgradeArgs = external_exports.object({ personaId: external_exports.string().min(1) });
22121
22122
  var LarkBotStatusArgs = LarkBotPersonaArgs;
22122
22123
  var LarkBotProvisionArgs = LarkBotPersonaArgs;
22123
22124
  var LarkBotProvisionCancelArgs = LarkBotPersonaArgs;
@@ -22151,7 +22152,21 @@ var LarkBotStatusResultSchema = external_exports.object({
22151
22152
  qrUrl: external_exports.string().optional(),
22152
22153
  /** provision 失败后的 unbound 帧携带;正常 unbound 无此字段 */
22153
22154
  errorReason: LarkBotProvisionErrorReasonSchema.optional(),
22154
- groups: external_exports.array(LarkBotGroupSchema)
22155
+ groups: external_exports.array(LarkBotGroupSchema),
22156
+ /**
22157
+ * bound 态:该 bot 实际授予的 scope 缺了 LARK_PROVISION_ADDONS 里声明的项
22158
+ * (spec 2026-09-01)——addons 是扫码时一次性写进飞书应用配置的,后来新增的
22159
+ * scope / 事件对存量 bot 不生效,不提示的话用户只会觉得「功能没生效」。
22160
+ * 判定真源是飞书回读的 granted scopes,不落库任何版本号。
22161
+ */
22162
+ needsUpgrade: external_exports.boolean().optional(),
22163
+ /**
22164
+ * bound 态叠加:正在扫码更新授权(spec 2026-09-01)。
22165
+ * **不复用 provisioning 态**——升级期间 bot 仍在正常服务(长连接不断、群照常响应),
22166
+ * 降级成 provisioning 会丢掉 botName / groups,UI 上像是断了。
22167
+ * 有值即渲染二维码弹层;升级结束(成功或取消)该字段消失。
22168
+ */
22169
+ upgradeQrUrl: external_exports.string().optional()
22155
22170
  });
22156
22171
  var LarkBotStateFrameSchema = LarkBotStatusResultSchema.extend({
22157
22172
  personaId: external_exports.string().min(1)
@@ -22222,6 +22237,17 @@ var PersonaPluginSummarySchema = external_exports.object({
22222
22237
  // settings.json 中 enabledPlugins 的 key,例如 'superpowers@claude-plugins-official'
22223
22238
  id: external_exports.string().min(1)
22224
22239
  });
22240
+ var CodexSandboxSettingsSchema = external_exports.object({
22241
+ // 额外可写的绝对路径或 ~ 路径(pnpm 工具链等);workspace(cwd) 和 userWorkDir 由 daemon 自动加,不用列。
22242
+ writableRoots: external_exports.array(external_exports.string()).optional(),
22243
+ // 禁读 glob(相对 workspace root 或绝对),如 ".secrets/**"、"**/*.env"。
22244
+ denyRead: external_exports.array(external_exports.string()).optional(),
22245
+ // true → 网络全放(*);false → 断网。**缺省等于联网**:daemon 写入的默认值是 network:true
22246
+ // (对齐 claude guest 一贯的域名全放,见 spec 2026-09-01-codex-guest-network §1.3),
22247
+ // 且 manager / UI 读到 undefined 时都回落 true —— 两侧必须同向,否则出现
22248
+ // 「卡片显示开、实际断网」的幽灵态。
22249
+ network: external_exports.boolean().optional()
22250
+ }).strict();
22225
22251
  var PersonaSandboxSettingsSchema = external_exports.object({
22226
22252
  permissions: external_exports.object({
22227
22253
  defaultMode: external_exports.string().optional(),
@@ -22270,7 +22296,10 @@ var PersonaInfoResponseSchema = PersonaFileSchema.extend({
22270
22296
  skills: external_exports.array(PersonaSkillSummarySchema).optional(),
22271
22297
  // 仅 'persona:get' 携带;空数组 = 没启用任何插件 / settings.json 不存在;undefined = 不在该响应类型上
22272
22298
  plugins: external_exports.array(PersonaPluginSummarySchema).optional(),
22273
- sandboxSettings: PersonaSandboxSettingsSchema.nullable().optional()
22299
+ sandboxSettings: PersonaSandboxSettingsSchema.nullable().optional(),
22300
+ // 仅 'persona:get' 携带;null = codex-sandbox.json 不存在 / parse 失败。
22301
+ // UI 的「访客联网」开关读它(见 PersonaCardSettings)。
22302
+ codexSandbox: CodexSandboxSettingsSchema.nullable().optional()
22274
22303
  });
22275
22304
  var PersonaCreateArgsSchema = external_exports.object({
22276
22305
  // persona 身份。daemon 拼成 personaId = `persona-<slug>`——前缀由 daemon 拥有,调用方只给后半段,
@@ -22302,17 +22331,13 @@ var PersonaUpdateArgsSchema = external_exports.object({
22302
22331
  personalityLocal: external_exports.string().optional(),
22303
22332
  public: external_exports.boolean().optional(),
22304
22333
  // pass `null` to clear; daemon strips the field from PersonaFile on null
22305
- iconKey: external_exports.string().nullable().optional()
22334
+ iconKey: external_exports.string().nullable().optional(),
22335
+ // codex guest 沙箱(落盘 .clawd/codex-sandbox.json,不在 PersonaFile 里)。与 personality
22336
+ // 同款分层写:handler 剥出来单独落盘,且**局部合并**——只带 network 的 patch 不能抹掉
22337
+ // owner 手配的 writableRoots / denyRead。
22338
+ codexSandbox: CodexSandboxSettingsSchema.optional()
22306
22339
  }).strict()
22307
22340
  }).strict();
22308
- var CodexSandboxSettingsSchema = external_exports.object({
22309
- // 额外可写的绝对路径或 ~ 路径(pnpm 工具链等);workspace(cwd) 和 userWorkDir 由 daemon 自动加,不用列。
22310
- writableRoots: external_exports.array(external_exports.string()).optional(),
22311
- // 禁读 glob(相对 workspace root 或绝对),如 ".secrets/**"、"**/*.env"。
22312
- denyRead: external_exports.array(external_exports.string()).optional(),
22313
- // true → 网络全放(*);缺省/false → 断网。
22314
- network: external_exports.boolean().optional()
22315
- }).strict();
22316
22341
 
22317
22342
  // ../protocol/src/events.ts
22318
22343
  var SESSION_STATUS_VALUES = [
@@ -23772,6 +23797,10 @@ var METHOD_DOCS = {
23772
23797
  summary: "\u89E3\u7ED1 persona \u7684\u98DE\u4E66 bot\uFF08\u5220\u4E91\u7AEF\u7ED1\u5B9A + \u65AD\u957F\u8FDE\u63A5\uFF0C\u98DE\u4E66\u4FA7 app \u4FDD\u7559\uFF09\uFF1Bowner-only",
23773
23798
  args: LarkBotUnbindArgs
23774
23799
  },
23800
+ "larkBot:upgrade": {
23801
+ summary: "\u626B\u7801\u66F4\u65B0\u5DF2\u7ED1\u5B9A bot \u7684\u6388\u6743\uFF08\u8865\u540E\u6765\u65B0\u589E\u7684 scope / \u4E8B\u4EF6\uFF09\uFF1B\u5168\u7A0B\u4FDD\u6301 bound\uFF0Cbot \u4E0D\u65AD\u8FDE\uFF1Bowner-only",
23802
+ args: LarkBotUpgradeArgs
23803
+ },
23775
23804
  "larkBot:status": {
23776
23805
  summary: "\u67E5 persona \u7684\u98DE\u4E66 bot \u72B6\u6001\uFF08unbound/bound/broken + \u670D\u52A1\u4E2D\u7FA4\u6E05\u5355\uFF09\uFF1Bowner-only",
23777
23806
  args: LarkBotStatusArgs