@clawos-dev/clawd 0.2.581 → 0.2.583

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
@@ -6168,7 +6168,7 @@ function cloudEngineOk(tool) {
6168
6168
  function repoSlug(url) {
6169
6169
  return url.trim().replace(/^git@[^:]+:/, "").replace(/^https?:\/\/[^/]+\//, "").replace(/\.git$/, "");
6170
6170
  }
6171
- var DeployFileSchema, SCAN_BASE_KINDS, ScanBaseKindSchema, DeployScanStatusSchema, DeployScanSchema, DeployReleasedSchema, DeployScanFileSchema, DeployRepoSchema, DeploySecretPresenceSchema, DEPLOY_STAGE_STATUSES, DeployStageStatusSchema, DEPLOY_STAGE_KEYS, DeployStageKeySchema, DeployJobSchema, DeployRunSchema, CLOUD_ENGINES, DeployStateSchema, DeployReleaseResultSchema, DeployPersonaArgsSchema, DeployGetArgsSchema, DeployScanArgsSchema, DeployReleaseArgsSchema, DeployPatchArgsSchema, DeployPutSecretArgsSchema, DeployRerunJobArgsSchema, DeployReportFindingArgsSchema, DeployFinishScanArgsSchema, PERSONA_REPO_ORG;
6171
+ var DeployFileSchema, SCAN_BASE_KINDS, ScanBaseKindSchema, DeployScanStatusSchema, DeployScanSchema, DeployReleasedSchema, DeployScanFileSchema, DeployRepoSchema, DeploySecretPresenceSchema, DEPLOY_STAGE_STATUSES, DeployStageStatusSchema, DEPLOY_STAGE_KEYS, DeployStageKeySchema, DeployJobSchema, DeployRunSchema, CLOUD_ENGINES, DeployUnpublishRunSchema, DeployStateSchema, DeployUnpublishResultSchema, DeployReleaseResultSchema, DeployPersonaArgsSchema, DeployGetArgsSchema, DeployScanArgsSchema, DeployReleaseArgsSchema, DeployPatchArgsSchema, DeployPutSecretArgsSchema, DeployRerunJobArgsSchema, DeployReportFindingArgsSchema, DeployFinishScanArgsSchema, PERSONA_REPO_ORG;
6172
6172
  var init_deploy_schemas = __esm({
6173
6173
  "../protocol/src/deploy-schemas.ts"() {
6174
6174
  "use strict";
@@ -6277,6 +6277,12 @@ var init_deploy_schemas = __esm({
6277
6277
  jobs: external_exports.array(DeployJobSchema)
6278
6278
  }).strict();
6279
6279
  CLOUD_ENGINES = ["codex", "pi"];
6280
+ DeployUnpublishRunSchema = external_exports.object({
6281
+ runId: external_exports.number(),
6282
+ status: DeployRunSchema.shape.status,
6283
+ conclusion: external_exports.enum(["success", "failure"]).optional(),
6284
+ htmlUrl: external_exports.string().min(1)
6285
+ }).strict();
6280
6286
  DeployStateSchema = external_exports.object({
6281
6287
  personaId: external_exports.string().min(1),
6282
6288
  /** 本机 GitHub 身份(机器级,githubAuth:* 的同一份;上云页顶上那张卡用)。老 daemon 不给(可选只为兼容),UI 缺席时按未知处理 */
@@ -6293,12 +6299,23 @@ var init_deploy_schemas = __esm({
6293
6299
  * **缺席是常态**:没推过、没授权 GitHub、GitHub 查不到、仓库还没有 run,都不该让整页打不开——同 `secrets` 的处理。
6294
6300
  */
6295
6301
  run: DeployRunSchema.optional(),
6302
+ /**
6303
+ * 现在在不在线上 = gateway 的正式登记表里有没有它(`/healthz`,跟 persona 列表里云卡片同一个来源)。
6304
+ * **缺席 = 查不到**(没登录飞书 / 连不上云上服务),不是「不在」——页面据此不给下线按钮,也不撤「线上」标。
6305
+ * 不看 `releases`:发布过不等于还在线上(下线过、或者被人用脚本撤了)
6306
+ */
6307
+ online: external_exports.boolean().optional(),
6308
+ /** 最新那次下线流水线。没下线过 / 没仓库 / 没 GitHub 身份 / 查不到都缺席 */
6309
+ unpublish: DeployUnpublishRunSchema.optional(),
6296
6310
  /**
6297
6311
  * `.clawd/deploy-scan.json` 读不出来(坏 JSON / 不合形状)时的人话。有它 `scans` 为空。
6298
6312
  * 页面据此给一条能走的路(重新扫描会重建这个文件),而不是白屏。
6299
6313
  */
6300
6314
  historyError: external_exports.string().optional()
6301
6315
  }).strict();
6316
+ DeployUnpublishResultSchema = external_exports.object({
6317
+ before: external_exports.number().nullable()
6318
+ }).strict();
6302
6319
  DeployReleaseResultSchema = external_exports.object({
6303
6320
  sha: external_exports.string().min(1),
6304
6321
  actionsUrl: external_exports.string().min(1)
@@ -7674,7 +7691,7 @@ var init_dashboard_schemas = __esm({
7674
7691
  "../protocol/src/dashboard-schemas.ts"() {
7675
7692
  "use strict";
7676
7693
  init_zod();
7677
- DASHBOARD_PAGES = ["cloud-persona-metrics"];
7694
+ DASHBOARD_PAGES = ["cloud-persona-metrics", "cloud-persona-conversations"];
7678
7695
  DashboardPageSchema = external_exports.enum(DASHBOARD_PAGES);
7679
7696
  DASHBOARD_LINK_TTL_SECONDS = 24 * 3600;
7680
7697
  DashboardOpenUrlArgs = external_exports.object({ page: DashboardPageSchema }).strict();
@@ -9139,6 +9156,10 @@ var init_methods = __esm({
9139
9156
  summary: "\u539F\u6837\u91CD\u8DD1\u6700\u65B0\u90A3\u6761\u6D41\u6C34\u7EBF\u91CC\u5931\u8D25\u7684\u67D0\u4E00\u6BB5\uFF08GitHub REST rerun job\uFF09\u3002**\u540C\u4E00\u4E2A commit\u3001\u540C\u4E00\u4EFD\u8F93\u5165**\uFF0C\u4E0D\u4EA7\u751F\u65B0\u63D0\u4EA4\u2014\u2014\u6539\u4E86 persona \u5185\u5BB9\u8981 deploy:scan \u518D deploy:release\u3002jobId \u5FC5\u987B\u5C5E\u4E8E\u6700\u65B0\u90A3\u6761 run\uFF1Bowner-only",
9140
9157
  args: DeployRerunJobArgsSchema
9141
9158
  },
9159
+ "deploy:unpublish": {
9160
+ summary: "\u4E0B\u7EBF\uFF1A\u89E6\u53D1 persona \u4ED3\u5E93\u91CC\u7684 clawd-unpublish.yml\uFF08workflow_dispatch\uFF09\uFF0C\u7531\u6D41\u6C34\u7EBF\u5728 gateway \u64A4\u6389\u6B63\u5F0F\u767B\u8BB0\u2014\u2014\u522B\u4EBA\u770B\u4E0D\u5230\u3001\u7528\u4E0D\u4E86\uFF1B\u4ED3\u5E93 / \u955C\u50CF / \u672C\u673A\u4E0A\u4E91\u8BB0\u5F55\u90FD\u4E0D\u52A8\uFF0C\u91CD\u65B0\u53D1\u5E03\u5C31\u56DE\u6765\u3002\u6700\u540E\u4E00\u6B21\u53D1\u5E03\u7684 commit \u91CC\u6CA1\u6709\u8FD9\u4EFD workflow\uFF08\u65E7\u6A21\u677F\uFF09\u65F6\u62D2\uFF0C\u8981\u5148\u91CD\u65B0\u53D1\u5E03\u4E00\u6B21\u3002\u56DE { before }\uFF1A\u89E6\u53D1\u4E4B\u524D\u73B0\u67E5\u7684\u6700\u65B0\u4E0B\u7EBF run id\uFF08\u6CA1\u4E0B\u7EBF\u8FC7 = null\uFF09\uFF0C\u9875\u9762\u62FF\u5B83\u8BA4\u65B0 run\uFF1Bowner-only\u3001human-only",
9161
+ args: DeployPersonaArgsSchema
9162
+ },
9142
9163
  // ---- githubAuth:*(本机 GitHub 身份,机器级;spec 2026-09-15-github-auth-unify-design §4.3)----
9143
9164
  "githubAuth:get": {
9144
9165
  summary: "\u672C\u673A\u7684 GitHub \u8EAB\u4EFD\uFF1Aauthorized\uFF08@login\uFF0C\u6765\u81EA git \u51ED\u636E\u5B58\u50A8 / \u672C\u673A\u51ED\u636E\u8868\uFF09/ missing / invalid\uFF08GitHub \u4E0D\u8BA4\uFF09/ pending\uFF08\u7B49 GitHub \u8DF3\u56DE\u4E2D\u5FC3\uFF09\u3002\u8BBE\u7F6E\u9875\u300C\u8D26\u53F7\u300D\u3001\u4E0A\u4E91\u9875\u9876\u4E0A\u90A3\u5F20\u5361\u3001PR \u6536\u4EF6\u7BB1\u90FD\u770B\u5B83\uFF1Bpending \u671F\u9875\u9762\u6BCF 2 \u79D2\u8F6E\u8BE2\uFF0Cdaemon \u987A\u624B\u5411\u4E2D\u5FC3\u9886 token\u3002\u65E0\u53C2\u6570",
@@ -15890,13 +15911,13 @@ var init_claude_history = __esm({
15890
15911
  function probeViaWhich() {
15891
15912
  try {
15892
15913
  const out = (0, import_node_child_process2.execFileSync)("which", ["claude"], { encoding: "utf8" }).trim();
15893
- if (out && import_node_fs17.default.existsSync(out)) return out;
15914
+ if (out && import_node_fs20.default.existsSync(out)) return out;
15894
15915
  } catch {
15895
15916
  }
15896
15917
  return null;
15897
15918
  }
15898
15919
  async function probeClaude(env = process.env) {
15899
- if (env.CLAUDE_BIN && import_node_fs17.default.existsSync(env.CLAUDE_BIN)) {
15920
+ if (env.CLAUDE_BIN && import_node_fs20.default.existsSync(env.CLAUDE_BIN)) {
15900
15921
  return { available: true, path: env.CLAUDE_BIN };
15901
15922
  }
15902
15923
  const w3 = probeViaWhich();
@@ -16362,13 +16383,13 @@ function encodeClaudeStdin(text) {
16362
16383
  };
16363
16384
  return JSON.stringify(frame) + "\n";
16364
16385
  }
16365
- var import_node_child_process, import_node_child_process2, import_node_fs17, ATTACHMENT_SILENT_SUBTYPES2, unknownTypeHandler, ATTACHMENT_RE, IMAGE_EXT_MIME, CLAUDE_MODEL_IDS, CLAUDE_MODELS, _assertModelIdsAligned, CLAUDE_PERMISSION_MODES, CLAUDE_CAPABILITIES, ClaudeAdapter;
16386
+ var import_node_child_process, import_node_child_process2, import_node_fs20, ATTACHMENT_SILENT_SUBTYPES2, unknownTypeHandler, ATTACHMENT_RE, IMAGE_EXT_MIME, CLAUDE_MODEL_IDS, CLAUDE_MODELS, _assertModelIdsAligned, CLAUDE_PERMISSION_MODES, CLAUDE_CAPABILITIES, ClaudeAdapter;
16366
16387
  var init_claude = __esm({
16367
16388
  "src/tools/claude.ts"() {
16368
16389
  "use strict";
16369
16390
  import_node_child_process = require("child_process");
16370
16391
  import_node_child_process2 = require("child_process");
16371
- import_node_fs17 = __toESM(require("fs"), 1);
16392
+ import_node_fs20 = __toESM(require("fs"), 1);
16372
16393
  init_protocol();
16373
16394
  init_claude_history();
16374
16395
  init_tool_result_extra();
@@ -51402,19 +51423,37 @@ function verifyDashboardLink(secret, page, eRaw, s2, now = Date.now) {
51402
51423
  }
51403
51424
 
51404
51425
  // src/dashboard/routes.ts
51426
+ var DashboardDataError = class extends Error {
51427
+ constructor(status, code, message) {
51428
+ super(message);
51429
+ this.status = status;
51430
+ this.code = code;
51431
+ this.name = "DashboardDataError";
51432
+ }
51433
+ status;
51434
+ code;
51435
+ };
51436
+ var SUBROUTES = {
51437
+ "cloud-persona-metrics": ["personas", "data"],
51438
+ "cloud-persona-conversations": ["personas", "threads", "thread"]
51439
+ };
51440
+ var UPSTREAM_DOWN = { personas: "UPSTREAM_UNREACHABLE", data: "GATEWAY_UNREACHABLE", threads: "CENTER_UNREACHABLE", thread: "CENTER_UNREACHABLE" };
51405
51441
  var NO_STORE = { "cache-control": "no-store" };
51406
51442
  function send(res, status, contentType, body) {
51407
51443
  res.writeHead(status, { "content-type": contentType, ...NO_STORE });
51408
51444
  res.end(body);
51409
51445
  }
51410
51446
  var sendJson = (res, status, body) => send(res, status, "application/json; charset=utf-8", JSON.stringify(body));
51447
+ var sendPassthrough = (res, r2) => send(res, r2.status, "application/json; charset=utf-8", r2.body);
51411
51448
  var PERSONA_RE = /^[a-z0-9-]+$/;
51449
+ var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
51412
51450
  function createDashboardRoutes(deps) {
51413
51451
  const now = deps.now ?? Date.now;
51414
51452
  return async (req, res, url) => {
51415
- const m3 = /^\/dashboard\/([^/]+)(?:\/(data|personas))?$/.exec(url.pathname);
51453
+ const m3 = /^\/dashboard\/([^/]+)(?:\/([a-z]+))?$/.exec(url.pathname);
51416
51454
  const page = m3 && DASHBOARD_PAGES.includes(m3[1]) ? m3[1] : null;
51417
- if (!m3 || !page || req.method !== "GET") {
51455
+ const sub = m3?.[2];
51456
+ if (!m3 || !page || req.method !== "GET" || sub !== void 0 && !SUBROUTES[page].includes(sub)) {
51418
51457
  sendJson(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
51419
51458
  return true;
51420
51459
  }
@@ -51424,8 +51463,7 @@ function createDashboardRoutes(deps) {
51424
51463
  return true;
51425
51464
  }
51426
51465
  const v3 = verifyDashboardLink(secret, page, url.searchParams.get("e"), url.searchParams.get("s"), now);
51427
- const sub = m3[2];
51428
- if (!sub) {
51466
+ if (sub === void 0) {
51429
51467
  if (!v3.ok && v3.code !== "EXPIRED") {
51430
51468
  send(res, 403, "text/plain; charset=utf-8", "\u94FE\u63A5\u65E0\u6548\uFF1A\u56DE\u5230 clawd \u7684\u300C\u8BBE\u7F6E \u2192 Dashboard\u300D\u91CD\u65B0\u6253\u5F00");
51431
51469
  return true;
@@ -51442,290 +51480,1517 @@ function createDashboardRoutes(deps) {
51442
51480
  sendJson(res, 403, v3.code === "EXPIRED" ? { code: "LINK_EXPIRED", message: "\u94FE\u63A5\u5DF2\u8FC7\u671F" } : { code: "LINK_INVALID", message: "\u94FE\u63A5\u65E0\u6548" });
51443
51481
  return true;
51444
51482
  }
51483
+ const q3 = url.searchParams;
51445
51484
  try {
51446
51485
  if (sub === "personas") {
51447
- sendJson(res, 200, { personas: await deps.gateway.personas() });
51486
+ sendJson(res, 200, { personas: await deps.data.personas() });
51448
51487
  return true;
51449
51488
  }
51450
- const personaId2 = url.searchParams.get("persona") ?? "";
51489
+ const personaId2 = q3.get("persona") ?? "";
51451
51490
  if (!PERSONA_RE.test(personaId2)) {
51452
51491
  sendJson(res, 400, { code: "INVALID_PARAM", message: "persona \u8981\u662F\u4E91 persona \u540D\uFF08[a-z0-9-]\uFF09" });
51453
51492
  return true;
51454
51493
  }
51455
- const r2 = await deps.gateway.metrics({ personaId: personaId2, window: url.searchParams.get("window"), entry: url.searchParams.get("entry") });
51456
- send(res, r2.status, "application/json; charset=utf-8", r2.body);
51494
+ if (sub === "data") {
51495
+ sendPassthrough(res, await deps.data.metrics({ personaId: personaId2, window: q3.get("window"), entry: q3.get("entry") }));
51496
+ return true;
51497
+ }
51498
+ if (sub === "threads") {
51499
+ const limit = q3.get("limit");
51500
+ if (limit !== null && !/^\d{1,3}$/.test(limit)) {
51501
+ sendJson(res, 400, { code: "INVALID_PARAM", message: "limit \u8981\u662F\u6B63\u6574\u6570" });
51502
+ return true;
51503
+ }
51504
+ sendPassthrough(res, await deps.data.threads({ personaId: personaId2, before: q3.get("before"), limit: limit === null ? null : Number(limit) }));
51505
+ return true;
51506
+ }
51507
+ const unionId = q3.get("unionId") ?? "";
51508
+ const threadId = q3.get("threadId") ?? "";
51509
+ if (!ID_RE.test(unionId) || !ID_RE.test(threadId)) {
51510
+ sendJson(res, 400, { code: "INVALID_PARAM", message: "unionId / threadId \u53EA\u8BA4 [A-Za-z0-9_-]" });
51511
+ return true;
51512
+ }
51513
+ sendJson(res, 200, await deps.data.thread({ personaId: personaId2, unionId, threadId }));
51457
51514
  } catch (e) {
51458
- sendJson(res, 502, { code: "GATEWAY_UNREACHABLE", message: e.message });
51515
+ if (e instanceof DashboardDataError) {
51516
+ sendJson(res, e.status, { code: e.code, message: e.message });
51517
+ return true;
51518
+ }
51519
+ sendJson(res, 502, { code: UPSTREAM_DOWN[sub], message: e.message });
51459
51520
  }
51460
51521
  return true;
51461
51522
  };
51462
51523
  }
51463
51524
 
51464
- // src/share-ui/load.ts
51465
- var import_node_fs15 = __toESM(require("fs"), 1);
51466
- var import_node_path14 = __toESM(require("path"), 1);
51467
- var import_node_url4 = require("url");
51468
-
51469
- // src/share-ui/asset-loader.ts
51470
- var import_node_fs14 = __toESM(require("fs"), 1);
51471
- var import_node_path13 = __toESM(require("path"), 1);
51472
- function createShareUiAssetLoader(opts) {
51473
- let indexHtml;
51474
- try {
51475
- indexHtml = import_node_fs14.default.readFileSync(opts.indexHtmlPath, "utf8");
51476
- } catch (err) {
51477
- throw new Error(`share-ui index.html not found at ${opts.indexHtmlPath}: ${err.message}`);
51478
- }
51479
- const assetsDir = import_node_path13.default.resolve(opts.assetsDir);
51480
- return {
51481
- renderIndexHtml() {
51482
- return indexHtml;
51483
- },
51484
- resolveAssetPath(rel) {
51485
- const resolved = import_node_path13.default.resolve(assetsDir, rel);
51486
- if (resolved !== assetsDir && !resolved.startsWith(assetsDir + import_node_path13.default.sep)) return null;
51487
- try {
51488
- if (import_node_fs14.default.statSync(resolved).isFile()) return resolved;
51489
- } catch {
51490
- }
51491
- return null;
51492
- }
51493
- };
51525
+ // src/tools/codex-rollout.ts
51526
+ function rec(v3) {
51527
+ return v3 && typeof v3 === "object" && !Array.isArray(v3) ? v3 : void 0;
51494
51528
  }
51495
-
51496
- // src/share-ui/load.ts
51497
- var import_meta4 = {};
51498
- function bundleDirFromArgv(argv1) {
51499
- if (!argv1) return null;
51500
- let real = argv1;
51529
+ function str(v3) {
51530
+ return typeof v3 === "string" ? v3 : void 0;
51531
+ }
51532
+ function num(v3) {
51533
+ return typeof v3 === "number" && Number.isFinite(v3) ? v3 : void 0;
51534
+ }
51535
+ function parseLine(line) {
51536
+ const t2 = line.trim();
51537
+ if (!t2) return void 0;
51501
51538
  try {
51502
- real = import_node_fs15.default.realpathSync(argv1);
51539
+ return rec(JSON.parse(t2));
51503
51540
  } catch {
51541
+ return void 0;
51504
51542
  }
51505
- return import_node_path14.default.dirname(real);
51506
51543
  }
51507
- function tryLoadShareUi(logger) {
51508
- const candidates = [];
51509
- try {
51510
- const here = import_node_path14.default.dirname((0, import_node_url4.fileURLToPath)(import_meta4.url));
51511
- candidates.push(here);
51512
- candidates.push(import_node_path14.default.resolve(here, ".."));
51513
- candidates.push(import_node_path14.default.resolve(here, "..", "..", "dist"));
51514
- } catch {
51544
+ function metaFrom(obj) {
51545
+ if (obj.type !== "session_meta") return null;
51546
+ const p3 = rec(obj.payload);
51547
+ const threadId = str(p3?.id) ?? str(p3?.session_id);
51548
+ if (!p3 || !threadId) return null;
51549
+ const meta = { threadId };
51550
+ const cwd = str(p3.cwd);
51551
+ if (cwd) meta.cwd = cwd;
51552
+ const createdAt = str(p3.timestamp) ?? str(obj.timestamp);
51553
+ if (createdAt) meta.createdAt = createdAt;
51554
+ const parent = str(p3.parent_thread_id);
51555
+ if (parent) meta.parentThreadId = parent;
51556
+ const cli = str(p3.cli_version);
51557
+ if (cli) meta.cliVersion = cli;
51558
+ return meta;
51559
+ }
51560
+ function parseCodexRolloutMeta(firstLine) {
51561
+ const obj = parseLine(firstLine);
51562
+ return obj ? metaFrom(obj) : null;
51563
+ }
51564
+ function firstUserMessageText(text) {
51565
+ for (const line of text.split("\n")) {
51566
+ const obj = parseLine(line);
51567
+ if (!obj || obj.type !== "event_msg") continue;
51568
+ const p3 = rec(obj.payload);
51569
+ if (p3?.type === "user_message") return str(p3.message);
51515
51570
  }
51516
- const argvDir = bundleDirFromArgv(process.argv[1]);
51517
- if (argvDir) candidates.push(argvDir);
51518
- for (const root of candidates) {
51519
- const shareUiDir = import_node_path14.default.join(root, "share-ui");
51520
- const indexHtmlPath = import_node_path14.default.join(shareUiDir, "guest.html");
51521
- const assetsDir = import_node_path14.default.join(shareUiDir, "assets");
51571
+ return void 0;
51572
+ }
51573
+ function outputText(output) {
51574
+ if (typeof output === "string") return output;
51575
+ if (Array.isArray(output)) {
51576
+ return output.map((part) => typeof part === "string" ? part : str(rec(part)?.text) ?? "").join("");
51577
+ }
51578
+ return "";
51579
+ }
51580
+ function functionCallInput(name, rawArgs) {
51581
+ let args = rawArgs;
51582
+ if (typeof rawArgs === "string") {
51522
51583
  try {
51523
- if (import_node_fs15.default.statSync(indexHtmlPath).isFile() && import_node_fs15.default.statSync(assetsDir).isDirectory()) {
51524
- logger?.info("share-ui.assets-root.resolved", { root: shareUiDir });
51525
- return createShareUiAssetLoader({ indexHtmlPath, assetsDir });
51526
- }
51584
+ args = JSON.parse(rawArgs);
51527
51585
  } catch {
51586
+ args = { arguments: rawArgs };
51528
51587
  }
51529
51588
  }
51530
- logger?.warn("share-ui.assets-not-found", {
51531
- candidates,
51532
- hint: "run `pnpm -F @clawos/clawd-ui build && pnpm -F @clawos-dev/clawd build`"
51533
- });
51534
- return null;
51589
+ const a2 = rec(args) ?? {};
51590
+ if (name === "exec_command" || name === "shell" || name === "container.exec") {
51591
+ const cmd = a2.cmd ?? a2.command;
51592
+ const command = Array.isArray(cmd) ? cmd.map(String).join(" ") : str(cmd) ?? "";
51593
+ return { tool: "commandExecution", input: { command } };
51594
+ }
51595
+ return { tool: name, input: args };
51596
+ }
51597
+ function parseCodexRollout(text) {
51598
+ const out = { meta: null, messages: [], subagents: [], turns: [] };
51599
+ const collabCallIds = /* @__PURE__ */ new Set();
51600
+ for (const line of text.split("\n")) {
51601
+ const obj = parseLine(line);
51602
+ if (!obj) continue;
51603
+ const ts = str(obj.timestamp);
51604
+ const p3 = rec(obj.payload);
51605
+ if (!p3) continue;
51606
+ if (obj.type === "session_meta") {
51607
+ if (!out.meta) out.meta = metaFrom(obj);
51608
+ continue;
51609
+ }
51610
+ if (obj.type === "event_msg") {
51611
+ switch (p3.type) {
51612
+ case "user_message": {
51613
+ const t2 = str(p3.message);
51614
+ if (t2) out.messages.push({ kind: "user-text", text: t2, raw: obj, ...ts ? { ts } : {} });
51615
+ break;
51616
+ }
51617
+ case "agent_message": {
51618
+ const t2 = str(p3.message);
51619
+ if (t2) out.messages.push({ kind: "assistant-text", text: t2, raw: obj, ...ts ? { ts } : {} });
51620
+ break;
51621
+ }
51622
+ case "sub_agent_activity": {
51623
+ if (p3.kind !== "started") break;
51624
+ const toolUseId = str(p3.event_id);
51625
+ const agentId = str(p3.agent_thread_id);
51626
+ const agentPath = str(p3.agent_path);
51627
+ if (!toolUseId || !agentId || !agentPath) break;
51628
+ const segments = agentPath.split("/").filter(Boolean);
51629
+ const description = segments[segments.length - 1] ?? agentId;
51630
+ out.subagents.push({ toolUseId, agentId, agentPath, description });
51631
+ out.messages.push({
51632
+ kind: "tool_call",
51633
+ tool: "Agent",
51634
+ toolUseId,
51635
+ input: { agentId, description, agentPath },
51636
+ raw: obj,
51637
+ ...ts ? { ts } : {}
51638
+ });
51639
+ break;
51640
+ }
51641
+ case "task_complete":
51642
+ case "turn_aborted": {
51643
+ const durationMs = num(p3.duration_ms);
51644
+ const turnId = str(p3.turn_id);
51645
+ const reason = str(p3.reason);
51646
+ out.turns.push({
51647
+ status: p3.type === "task_complete" ? "completed" : "interrupted",
51648
+ ...turnId ? { turnId } : {},
51649
+ ...durationMs !== void 0 ? { durationMs } : {},
51650
+ ...reason ? { reason } : {}
51651
+ });
51652
+ out.messages.push({
51653
+ kind: "turn-end",
51654
+ raw: obj,
51655
+ ...ts ? { ts } : {},
51656
+ ...durationMs !== void 0 ? { durationMs } : {}
51657
+ });
51658
+ break;
51659
+ }
51660
+ default:
51661
+ break;
51662
+ }
51663
+ continue;
51664
+ }
51665
+ if (obj.type === "response_item") {
51666
+ switch (p3.type) {
51667
+ case "function_call": {
51668
+ const callId = str(p3.call_id);
51669
+ const name = str(p3.name);
51670
+ if (!callId || !name) break;
51671
+ if (p3.namespace === "collaboration") {
51672
+ collabCallIds.add(callId);
51673
+ break;
51674
+ }
51675
+ const { tool, input } = functionCallInput(name, p3.arguments);
51676
+ out.messages.push({ kind: "tool_call", tool, toolUseId: callId, input, raw: obj, ...ts ? { ts } : {} });
51677
+ break;
51678
+ }
51679
+ case "custom_tool_call": {
51680
+ const callId = str(p3.call_id);
51681
+ const name = str(p3.name);
51682
+ if (!callId || !name) break;
51683
+ out.messages.push({
51684
+ kind: "tool_call",
51685
+ tool: name,
51686
+ toolUseId: callId,
51687
+ input: { input: str(p3.input) ?? "" },
51688
+ raw: obj,
51689
+ ...ts ? { ts } : {}
51690
+ });
51691
+ break;
51692
+ }
51693
+ case "function_call_output":
51694
+ case "custom_tool_call_output": {
51695
+ const callId = str(p3.call_id);
51696
+ if (!callId || collabCallIds.has(callId)) break;
51697
+ out.messages.push({
51698
+ kind: "tool_result",
51699
+ toolUseId: callId,
51700
+ output: outputText(p3.output),
51701
+ raw: obj,
51702
+ ...ts ? { ts } : {}
51703
+ });
51704
+ break;
51705
+ }
51706
+ default:
51707
+ break;
51708
+ }
51709
+ continue;
51710
+ }
51711
+ }
51712
+ return out;
51713
+ }
51714
+ function subagentTerminalResult(started, child) {
51715
+ const first = child.turns[0];
51716
+ if (!first) return void 0;
51717
+ if (first.status === "completed") return { kind: "tool_result", toolUseId: started.toolUseId };
51718
+ return { kind: "tool_result", toolUseId: started.toolUseId, error: first.reason ?? "Codex subagent interrupted" };
51535
51719
  }
51536
51720
 
51537
- // src/visitor/visitor-token.ts
51538
- var import_node_crypto6 = __toESM(require("crypto"), 1);
51539
-
51540
- // ../protocol/src/index.ts
51541
- init_runtime();
51721
+ // src/tools/codex-history.ts
51722
+ var import_node_fs15 = __toESM(require("fs"), 1);
51723
+ var import_node_path14 = __toESM(require("path"), 1);
51542
51724
 
51543
- // src/visitor/visitor-token.ts
51544
- var DOMAIN_PREFIX3 = "clawd-visitor-v1|";
51545
- function hmac(secret, body) {
51546
- return import_node_crypto6.default.createHmac("sha256", secret).update(`${DOMAIN_PREFIX3}${body}`).digest("base64url");
51725
+ // src/tools/transcript-search.ts
51726
+ var import_node_fs14 = __toESM(require("fs"), 1);
51727
+ var import_node_path13 = __toESM(require("path"), 1);
51728
+ var SNIPPET_PAD = 60;
51729
+ var INJECTED_ENVELOPE_RE = /^<(system-reminder|command-name|command-message|command-args|local-command-stdout|local-command-stderr|task-notification|user-prompt-submit-hook|environment_context|user_instructions)\b/;
51730
+ var CODEX_WRAPPER_RE = /<(related-dir|environment_context|user_instructions)>[\s\S]*?<\/\1>/g;
51731
+ function asRecord(v3) {
51732
+ return typeof v3 === "object" && v3 !== null ? v3 : null;
51547
51733
  }
51548
- function signVisitorToken(secret, payload) {
51549
- const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
51550
- return `${body}.${hmac(secret, body)}`;
51734
+ function textOfBlocks(content, types) {
51735
+ if (!Array.isArray(content)) return null;
51736
+ const parts = [];
51737
+ for (const block of content) {
51738
+ const b3 = asRecord(block);
51739
+ if (!b3 || typeof b3.type !== "string" || !types.includes(b3.type)) continue;
51740
+ const text = b3.text;
51741
+ if (typeof text === "string" && text !== "") parts.push(text);
51742
+ }
51743
+ return parts.length > 0 ? parts.join("\n") : null;
51551
51744
  }
51552
- function verifyVisitorToken(secret, token, now) {
51553
- const dot = token.indexOf(".");
51554
- if (dot <= 0) return { ok: false, reason: "BAD_FORMAT" };
51555
- const body = token.slice(0, dot);
51556
- const sig = token.slice(dot + 1);
51557
- const expected = hmac(secret, body);
51558
- if (sig.length !== expected.length) return { ok: false, reason: "BAD_SIGNATURE" };
51559
- if (!import_node_crypto6.default.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
51560
- return { ok: false, reason: "BAD_SIGNATURE" };
51745
+ function lineToSearchableText(raw) {
51746
+ const line = asRecord(raw);
51747
+ if (!line) return null;
51748
+ if (line.type === "response_item") {
51749
+ const payload = asRecord(line.payload);
51750
+ if (!payload || payload.type !== "message") return null;
51751
+ const role = payload.role;
51752
+ if (role !== "user" && role !== "assistant") return null;
51753
+ const text2 = textOfBlocks(payload.content, ["input_text", "output_text"]);
51754
+ if (text2 === null) return null;
51755
+ if (role !== "user") return text2;
51756
+ const stripped = text2.replace(CODEX_WRAPPER_RE, "").trim();
51757
+ if (stripped === "") return null;
51758
+ return INJECTED_ENVELOPE_RE.test(stripped) ? null : stripped;
51561
51759
  }
51562
- let payload;
51760
+ if (line.type !== "user" && line.type !== "assistant") return null;
51761
+ const message = asRecord(line.message);
51762
+ if (!message) return null;
51763
+ const content = message.content;
51764
+ const text = typeof content === "string" && content !== "" ? content : textOfBlocks(content, ["text"]);
51765
+ if (text === null) return null;
51766
+ if (line.type === "user" && (line.isMeta === true || line.isSynthetic === true)) return null;
51767
+ if (line.type === "user" && INJECTED_ENVELOPE_RE.test(text.trimStart())) return null;
51768
+ return text;
51769
+ }
51770
+ function escapeRegExp(s2) {
51771
+ return s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
51772
+ }
51773
+ function makeSnippet(text, at3, needleLength) {
51774
+ const from = Math.max(0, at3 - SNIPPET_PAD);
51775
+ const to = Math.min(text.length, at3 + needleLength + SNIPPET_PAD);
51776
+ const body = text.slice(from, to).replace(/\s+/g, " ").trim();
51777
+ return `${from > 0 ? "\u2026 " : ""}${body}${to < text.length ? " \u2026" : ""}`;
51778
+ }
51779
+ function searchTranscriptFile(file, query) {
51780
+ let buf;
51563
51781
  try {
51564
- const parsed = VisitorTokenPayloadSchema.safeParse(
51565
- JSON.parse(Buffer.from(body, "base64url").toString("utf8"))
51566
- );
51567
- if (!parsed.success) return { ok: false, reason: "BAD_FORMAT" };
51568
- payload = parsed.data;
51782
+ buf = import_node_fs14.default.readFileSync(file);
51569
51783
  } catch {
51570
- return { ok: false, reason: "BAD_FORMAT" };
51784
+ return null;
51571
51785
  }
51572
- if (now >= payload.exp) return { ok: false, reason: "EXPIRED" };
51573
- return { ok: true, payload };
51574
- }
51575
-
51576
- // src/visitor/visitor-login.ts
51577
- function buildVisitorLogin(deps) {
51578
- return async function exchangeVisitorToken(args) {
51579
- let id;
51786
+ const haystack = buf.toString("latin1");
51787
+ const needle = Buffer.from(query, "utf8").toString("latin1");
51788
+ const re2 = new RegExp(escapeRegExp(needle), "gi");
51789
+ let m3;
51790
+ while ((m3 = re2.exec(haystack)) !== null) {
51791
+ const lineStart = haystack.lastIndexOf("\n", m3.index) + 1;
51792
+ let lineEnd = haystack.indexOf("\n", m3.index);
51793
+ if (lineEnd < 0) lineEnd = haystack.length;
51794
+ re2.lastIndex = lineEnd + 1;
51795
+ let parsed;
51580
51796
  try {
51581
- id = await deps.fetchUserInfo(args.ttcToken);
51797
+ parsed = JSON.parse(Buffer.from(haystack.slice(lineStart, lineEnd), "latin1").toString("utf8"));
51582
51798
  } catch {
51583
- return { ok: false, reason: "BAD_TTC_TOKEN" };
51799
+ continue;
51584
51800
  }
51585
- deps.visitorStore.upsert({
51586
- visitorId: id.ownerId,
51587
- displayName: id.displayName,
51588
- provider: id.provider,
51589
- ...id.avatarUrl ? { avatarUrl: id.avatarUrl } : {},
51590
- now: deps.now()
51591
- });
51592
- const iat = Math.floor(deps.now() / 1e3);
51593
- const visitorToken = signVisitorToken(deps.getSecret(), {
51594
- v: 1,
51595
- visitorId: id.ownerId,
51596
- displayName: id.displayName,
51597
- provider: id.provider,
51598
- iat,
51599
- exp: iat + deps.ttlSeconds
51600
- });
51601
- return { ok: true, visitorToken };
51602
- };
51801
+ const text = lineToSearchableText(parsed);
51802
+ if (text === null) continue;
51803
+ const at3 = text.toLowerCase().indexOf(query.toLowerCase());
51804
+ if (at3 < 0) continue;
51805
+ return makeSnippet(text, at3, query.length);
51806
+ }
51807
+ return null;
51603
51808
  }
51604
-
51605
- // src/visitor/visitor-store.ts
51606
- var import_node_fs16 = __toESM(require("fs"), 1);
51607
- var import_node_path15 = __toESM(require("path"), 1);
51608
- function createVisitorStore(opts) {
51609
- const file = import_node_path15.default.join(opts.dir, "visitors.json");
51610
- const read = () => {
51809
+ var ROLLOUT_NAME_RE = /-([0-9a-fA-F-]{36})\.jsonl$/;
51810
+ function createCodexRolloutLocator(roots) {
51811
+ let index = null;
51812
+ const walk2 = (dir, into) => {
51813
+ let entries;
51611
51814
  try {
51612
- return JSON.parse(import_node_fs16.default.readFileSync(file, "utf8"));
51815
+ entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
51613
51816
  } catch {
51614
- return [];
51817
+ return;
51615
51818
  }
51616
- };
51617
- const write = (rows) => {
51618
- import_node_fs16.default.mkdirSync(opts.dir, { recursive: true });
51619
- import_node_fs16.default.writeFileSync(file, JSON.stringify(rows, null, 2));
51620
- };
51621
- return {
51622
- upsert(r2) {
51623
- const rows = read();
51624
- const ex = rows.find((x2) => x2.visitorId === r2.visitorId);
51625
- if (ex) {
51626
- ex.displayName = r2.displayName;
51627
- ex.provider = r2.provider;
51628
- ex.lastSeen = r2.now;
51629
- if (r2.avatarUrl) ex.avatarUrl = r2.avatarUrl;
51630
- } else {
51631
- rows.push({
51632
- visitorId: r2.visitorId,
51633
- displayName: r2.displayName,
51634
- provider: r2.provider,
51635
- ...r2.avatarUrl ? { avatarUrl: r2.avatarUrl } : {},
51636
- firstSeen: r2.now,
51637
- lastSeen: r2.now
51638
- });
51819
+ for (const entry of entries) {
51820
+ const full = import_node_path13.default.join(dir, entry.name);
51821
+ if (entry.isDirectory()) {
51822
+ walk2(full, into);
51823
+ continue;
51639
51824
  }
51640
- write(rows);
51641
- },
51642
- list() {
51643
- return read();
51825
+ const matched = ROLLOUT_NAME_RE.exec(entry.name);
51826
+ if (matched) into.set(matched[1].toLowerCase(), full);
51644
51827
  }
51645
51828
  };
51646
- }
51647
-
51648
- // src/index.ts
51649
- init_claude();
51650
-
51651
- // src/tools/codex.ts
51652
- var import_node_child_process7 = require("child_process");
51653
- var import_node_fs21 = __toESM(require("fs"), 1);
51654
-
51655
- // src/tools/codex-session.ts
51656
- var import_node_child_process4 = require("child_process");
51657
- var import_node_path19 = __toESM(require("path"), 1);
51658
-
51659
- // src/tools/codex-app-server-client.ts
51660
- var import_node_readline = require("readline");
51661
- function stdioLineStream(proc) {
51662
- const rl = (0, import_node_readline.createInterface)({ input: proc.stdout, crlfDelay: Infinity });
51663
- const lineCbs = [];
51664
- const closeCbs = [];
51665
- rl.on("line", (l2) => {
51666
- for (const cb of lineCbs) cb(l2);
51667
- });
51668
- rl.on("close", () => {
51669
- for (const cb of closeCbs) cb();
51670
- });
51671
- return {
51672
- write: (line) => {
51673
- proc.stdin.write(line + "\n");
51674
- },
51675
- onLine: (cb) => {
51676
- lineCbs.push(cb);
51677
- },
51678
- onClose: (cb) => {
51679
- closeCbs.push(cb);
51680
- },
51681
- close: () => {
51682
- proc.stdin?.end();
51829
+ return (toolSessionId) => {
51830
+ if (index === null) {
51831
+ index = /* @__PURE__ */ new Map();
51832
+ for (const root of roots) walk2(root, index);
51683
51833
  }
51834
+ return index.get(toolSessionId.toLowerCase()) ?? null;
51684
51835
  };
51685
51836
  }
51686
- var CodexAppServerClient = class {
51687
- constructor(stream, opts = {}) {
51688
- this.stream = stream;
51689
- this.opts = opts;
51690
- stream.onLine((l2) => this.onLine(l2));
51691
- stream.onClose(() => {
51692
- for (const p3 of this.pending.values()) p3.reject(new Error("app-server connection closed"));
51693
- this.pending.clear();
51694
- this.opts.onClose?.();
51695
- });
51837
+
51838
+ // src/tools/codex-history.ts
51839
+ var CODEX_LIST_SESSIONS_LIMIT = 100;
51840
+ var CODEX_LIST_SESSIONS_SCAN_CAP = 4e3;
51841
+ var PEEK_BYTES = 8 * 1024;
51842
+ var TITLE_BYTES = 256 * 1024;
51843
+ function readHead(file, bytes) {
51844
+ let fd;
51845
+ try {
51846
+ fd = import_node_fs15.default.openSync(file, "r");
51847
+ } catch {
51848
+ return null;
51696
51849
  }
51697
- stream;
51698
- opts;
51699
- nextId = 1;
51700
- pending = /* @__PURE__ */ new Map();
51701
- request(method, params) {
51702
- const id = this.nextId++;
51703
- return new Promise((resolve9, reject) => {
51704
- this.pending.set(id, { resolve: resolve9, reject });
51705
- this.write({ jsonrpc: "2.0", method, id, params });
51706
- });
51850
+ try {
51851
+ const buf = Buffer.alloc(bytes);
51852
+ const n2 = import_node_fs15.default.readSync(fd, buf, 0, bytes, 0);
51853
+ return buf.subarray(0, n2).toString("utf8");
51854
+ } catch {
51855
+ return null;
51856
+ } finally {
51857
+ import_node_fs15.default.closeSync(fd);
51707
51858
  }
51708
- notify(method, params) {
51709
- this.write({ jsonrpc: "2.0", method, params });
51859
+ }
51860
+ function peekSessionCwd(head) {
51861
+ if (!head.includes('"session_meta"')) return null;
51862
+ const m3 = /"cwd":"((?:[^"\\]|\\.)*)"/.exec(head);
51863
+ if (!m3) return null;
51864
+ try {
51865
+ return JSON.parse(`"${m3[1]}"`);
51866
+ } catch {
51867
+ return null;
51710
51868
  }
51711
- // server-request 的响应也必须带 jsonrpc:'2.0',否则 codex app-server 忽略它 → 审批/问答永远卡住。
51712
- respond(id, result) {
51713
- this.write({ jsonrpc: "2.0", id, result });
51869
+ }
51870
+ function readFirstLine(file, max = 512 * 1024) {
51871
+ let fd;
51872
+ try {
51873
+ fd = import_node_fs15.default.openSync(file, "r");
51874
+ } catch {
51875
+ return null;
51714
51876
  }
51715
- onLine(line) {
51716
- const t2 = line.trim();
51717
- if (!t2) return;
51718
- let m3;
51719
- try {
51720
- m3 = JSON.parse(t2);
51721
- } catch {
51722
- return;
51723
- }
51724
- if (!isRecord(m3)) return;
51725
- const hasId = typeof m3.id === "number", hasMethod = typeof m3.method === "string";
51726
- if (hasId && !hasMethod) {
51727
- const p3 = this.pending.get(m3.id);
51728
- if (!p3) return;
51877
+ try {
51878
+ const chunk = Buffer.alloc(64 * 1024);
51879
+ const parts = [];
51880
+ let total = 0;
51881
+ while (total < max) {
51882
+ const n2 = import_node_fs15.default.readSync(fd, chunk, 0, chunk.length, total);
51883
+ if (n2 <= 0) break;
51884
+ const nl = chunk.subarray(0, n2).indexOf(10);
51885
+ if (nl >= 0) {
51886
+ parts.push(Buffer.from(chunk.subarray(0, nl)));
51887
+ return Buffer.concat(parts).toString("utf8");
51888
+ }
51889
+ parts.push(Buffer.from(chunk.subarray(0, n2)));
51890
+ total += n2;
51891
+ }
51892
+ return total < max && parts.length > 0 ? Buffer.concat(parts).toString("utf8") : null;
51893
+ } catch {
51894
+ return null;
51895
+ } finally {
51896
+ import_node_fs15.default.closeSync(fd);
51897
+ }
51898
+ }
51899
+ function readThreadNames(sharedHome) {
51900
+ const names = /* @__PURE__ */ new Map();
51901
+ if (!sharedHome) return names;
51902
+ let raw;
51903
+ try {
51904
+ raw = import_node_fs15.default.readFileSync(import_node_path14.default.join(sharedHome, "session_index.jsonl"), "utf8");
51905
+ } catch {
51906
+ return names;
51907
+ }
51908
+ for (const line of raw.split("\n")) {
51909
+ if (!line.trim()) continue;
51910
+ try {
51911
+ const o2 = JSON.parse(line);
51912
+ if (typeof o2.id === "string" && typeof o2.thread_name === "string" && o2.thread_name) {
51913
+ names.set(o2.id.toLowerCase(), o2.thread_name);
51914
+ }
51915
+ } catch {
51916
+ }
51917
+ }
51918
+ return names;
51919
+ }
51920
+ function walkRollouts(dir, visit) {
51921
+ let entries;
51922
+ try {
51923
+ entries = import_node_fs15.default.readdirSync(dir, { withFileTypes: true });
51924
+ } catch {
51925
+ return true;
51926
+ }
51927
+ entries.sort((a2, b3) => a2.name < b3.name ? 1 : a2.name > b3.name ? -1 : 0);
51928
+ for (const entry of entries) {
51929
+ const full = import_node_path14.default.join(dir, entry.name);
51930
+ if (entry.isDirectory()) {
51931
+ if (!walkRollouts(full, visit)) return false;
51932
+ } else if (ROLLOUT_NAME_RE.test(entry.name)) {
51933
+ if (visit(full) === false) return false;
51934
+ }
51935
+ }
51936
+ return true;
51937
+ }
51938
+ async function withSubagentResults(parent, parentThreadId, loadChild, logger) {
51939
+ const messages = [];
51940
+ const started = new Map(parent.subagents.map((s2) => [s2.toolUseId, s2]));
51941
+ for (const m3 of parent.messages) {
51942
+ messages.push(m3);
51943
+ const sub = m3.kind === "tool_call" && m3.toolUseId ? started.get(m3.toolUseId) : void 0;
51944
+ if (!sub) continue;
51945
+ let text;
51946
+ try {
51947
+ text = await loadChild(sub.agentId);
51948
+ } catch (error) {
51949
+ logger?.warn("codex subagent rollout unreadable", { childThreadId: sub.agentId, message: error.message });
51950
+ continue;
51951
+ }
51952
+ if (text === null) {
51953
+ logger?.warn("codex subagent rollout not found", { parentThreadId, childThreadId: sub.agentId });
51954
+ continue;
51955
+ }
51956
+ const child = parseCodexRollout(text);
51957
+ if (child.meta?.parentThreadId !== parentThreadId) {
51958
+ logger?.warn("codex subagent rollout does not belong to parent", {
51959
+ parentThreadId,
51960
+ childThreadId: sub.agentId,
51961
+ actualParent: child.meta?.parentThreadId ?? null
51962
+ });
51963
+ continue;
51964
+ }
51965
+ const result = subagentTerminalResult(sub, child);
51966
+ if (result) messages.push(result);
51967
+ }
51968
+ return messages;
51969
+ }
51970
+ function pageMessages(messages, args) {
51971
+ const total = messages.length;
51972
+ const limit = args.limit ?? total;
51973
+ const offset = typeof args.offset === "number" ? Math.min(Math.max(0, args.offset), total) : Math.max(0, total - limit);
51974
+ return { messages: messages.slice(offset, offset + limit), total, offset };
51975
+ }
51976
+ function subagentSummaries(parent) {
51977
+ const seen = /* @__PURE__ */ new Set();
51978
+ const out = [];
51979
+ for (const s2 of parent.subagents) {
51980
+ if (seen.has(s2.agentId)) continue;
51981
+ seen.add(s2.agentId);
51982
+ out.push({ subagentId: s2.agentId, summary: s2.description, messageCount: 0 });
51983
+ }
51984
+ return out;
51985
+ }
51986
+ var CodexHistoryReader = class {
51987
+ deps;
51988
+ constructor(deps) {
51989
+ this.deps = deps;
51990
+ }
51991
+ // 仍返回 []:projects/list 非 session-scoped、不知 tool,跨-tool 项目浏览器合并排后续。
51992
+ // codex 旧会话仍能经 clawd 活跃列表(session:list, tool-agnostic) + read 打开。
51993
+ async listProjects() {
51994
+ return [];
51995
+ }
51996
+ async listSessions(args) {
51997
+ const want = import_node_path14.default.resolve(args.projectPath);
51998
+ const names = readThreadNames(this.deps.sharedHome);
51999
+ const found = [];
52000
+ let scanned = 0;
52001
+ for (const root of this.deps.roots) {
52002
+ const goOn = walkRollouts(root, (file) => {
52003
+ if (++scanned > CODEX_LIST_SESSIONS_SCAN_CAP) return false;
52004
+ const head = readHead(file, PEEK_BYTES);
52005
+ const cwd = head === null ? null : peekSessionCwd(head);
52006
+ if (!cwd || import_node_path14.default.resolve(cwd) !== want) return;
52007
+ const first = readFirstLine(file);
52008
+ const meta = first === null ? null : parseCodexRolloutMeta(first);
52009
+ if (!meta) return;
52010
+ let mtimeMs;
52011
+ try {
52012
+ mtimeMs = import_node_fs15.default.statSync(file).mtimeMs;
52013
+ } catch {
52014
+ return;
52015
+ }
52016
+ found.push({ file, threadId: meta.threadId, mtimeMs });
52017
+ });
52018
+ if (!goOn) break;
52019
+ }
52020
+ found.sort((a2, b3) => b3.mtimeMs - a2.mtimeMs);
52021
+ return found.slice(0, CODEX_LIST_SESSIONS_LIMIT).map((f2) => {
52022
+ let summary = names.get(f2.threadId.toLowerCase());
52023
+ if (!summary) {
52024
+ const head = readHead(f2.file, TITLE_BYTES);
52025
+ summary = head === null ? void 0 : firstUserMessageText(head);
52026
+ }
52027
+ return {
52028
+ toolSessionId: f2.threadId,
52029
+ summary: summary || "(\u7A7A\u4F1A\u8BDD)",
52030
+ messageCount: 0,
52031
+ // 与此前 thread/list 一致:列表不数条数
52032
+ updatedAt: new Date(f2.mtimeMs).toISOString()
52033
+ };
52034
+ });
52035
+ }
52036
+ async read(args) {
52037
+ const locate = createCodexRolloutLocator(this.deps.roots);
52038
+ const parent = this.parseThread(locate, args.toolSessionId);
52039
+ const messages = await withSubagentResults(
52040
+ parent,
52041
+ args.toolSessionId,
52042
+ async (agentId) => {
52043
+ const file = locate(agentId);
52044
+ return file ? import_node_fs15.default.readFileSync(file, "utf8") : null;
52045
+ },
52046
+ this.deps.logger
52047
+ );
52048
+ return pageMessages(messages, args);
52049
+ }
52050
+ async listSubagents(args) {
52051
+ return subagentSummaries(this.parseThread(createCodexRolloutLocator(this.deps.roots), args.toolSessionId));
52052
+ }
52053
+ async readSubagent(args) {
52054
+ const child = this.parseThread(createCodexRolloutLocator(this.deps.roots), args.subagentId);
52055
+ if (child.meta?.threadId !== args.subagentId || child.meta.parentThreadId !== args.toolSessionId) {
52056
+ throw new Error(
52057
+ `Codex subagent thread ${args.subagentId} does not belong to parent thread ${args.toolSessionId}`
52058
+ );
52059
+ }
52060
+ return { messages: child.messages };
52061
+ }
52062
+ computeRewindDiff() {
52063
+ return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
52064
+ }
52065
+ listRewindableUserMessageIds() {
52066
+ return [];
52067
+ }
52068
+ parseThread(locate, threadId) {
52069
+ const file = locate(threadId);
52070
+ if (!file) throw new Error(`Codex rollout not found for thread ${threadId}`);
52071
+ return parseCodexRollout(import_node_fs15.default.readFileSync(file, "utf8"));
52072
+ }
52073
+ };
52074
+
52075
+ // src/tools/codex-cloud-history.ts
52076
+ function personaIdFromCloudCwd(cwd) {
52077
+ const m3 = /\/p\/([^/?#]+)(?:[/?#]|$)/.exec(cwd);
52078
+ if (!m3) return void 0;
52079
+ try {
52080
+ return decodeURIComponent(m3[1]);
52081
+ } catch {
52082
+ return void 0;
52083
+ }
52084
+ }
52085
+ var CloudCodexHistoryReader = class {
52086
+ constructor(client, logger) {
52087
+ this.client = client;
52088
+ this.logger = logger;
52089
+ }
52090
+ client;
52091
+ logger;
52092
+ async listProjects() {
52093
+ return [];
52094
+ }
52095
+ /** 云 persona 没有「关联已有 thread」的动线:会话列表就是本机的会话记录 */
52096
+ async listSessions() {
52097
+ return [];
52098
+ }
52099
+ async read(args) {
52100
+ const parent = await this.fetchThread(args.toolSessionId);
52101
+ const messages = await withSubagentResults(
52102
+ parent,
52103
+ args.toolSessionId,
52104
+ (agentId) => this.client.fetchRollout(agentId),
52105
+ this.logger
52106
+ );
52107
+ return pageMessages(messages, args);
52108
+ }
52109
+ async listSubagents(args) {
52110
+ return subagentSummaries(await this.fetchThread(args.toolSessionId));
52111
+ }
52112
+ async readSubagent(args) {
52113
+ const child = await this.fetchThread(args.subagentId);
52114
+ if (child.meta?.threadId !== args.subagentId || child.meta.parentThreadId !== args.toolSessionId) {
52115
+ throw new Error(
52116
+ `Codex subagent thread ${args.subagentId} does not belong to parent thread ${args.toolSessionId}`
52117
+ );
52118
+ }
52119
+ return { messages: child.messages };
52120
+ }
52121
+ computeRewindDiff() {
52122
+ return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
52123
+ }
52124
+ listRewindableUserMessageIds() {
52125
+ return [];
52126
+ }
52127
+ async fetchThread(threadId) {
52128
+ const text = await this.client.fetchRollout(threadId);
52129
+ if (text === null) throw new Error(`Codex rollout not found for thread ${threadId}`);
52130
+ return parseCodexRollout(text);
52131
+ }
52132
+ };
52133
+
52134
+ // src/tools/pi-history.ts
52135
+ var import_node_fs16 = __toESM(require("fs"), 1);
52136
+ var import_node_os8 = __toESM(require("os"), 1);
52137
+ var import_node_path15 = __toESM(require("path"), 1);
52138
+
52139
+ // src/tools/pi-json.ts
52140
+ function rec2(v3) {
52141
+ return typeof v3 === "object" && v3 !== null && !Array.isArray(v3) ? v3 : void 0;
52142
+ }
52143
+ function str2(v3) {
52144
+ return typeof v3 === "string" ? v3 : void 0;
52145
+ }
52146
+ function num2(v3) {
52147
+ return typeof v3 === "number" && Number.isFinite(v3) ? v3 : void 0;
52148
+ }
52149
+ function arr(v3) {
52150
+ return Array.isArray(v3) ? v3 : [];
52151
+ }
52152
+ function contentText(v3, imagePlaceholder = "") {
52153
+ if (typeof v3 === "string") return v3;
52154
+ return arr(v3).map((b3) => {
52155
+ const r2 = rec2(b3);
52156
+ if (!r2) return "";
52157
+ if (r2.type === "text") return str2(r2.text) ?? "";
52158
+ if (r2.type === "image") return imagePlaceholder;
52159
+ return "";
52160
+ }).filter(Boolean).join("\n");
52161
+ }
52162
+
52163
+ // src/tools/pi-history.ts
52164
+ var PI_LIST_SESSIONS_LIMIT = 100;
52165
+ var PI_LIST_SESSIONS_SCAN_CAP = 4e3;
52166
+ function piAgentDir(env = process.env) {
52167
+ return env.PI_CODING_AGENT_DIR || import_node_path15.default.join(import_node_os8.default.homedir(), ".pi", "agent");
52168
+ }
52169
+ function piSessionsRoot(env = process.env) {
52170
+ return import_node_path15.default.join(piAgentDir(env), "sessions");
52171
+ }
52172
+ function encodePiCwd(cwd) {
52173
+ return `--${cwd.replace(/^[\\/]+/, "").replace(/[\\/:]/g, "-")}--`;
52174
+ }
52175
+ function piProjectSessionDir(sessionsRoot, cwd) {
52176
+ return import_node_path15.default.join(sessionsRoot, encodePiCwd(cwd));
52177
+ }
52178
+ function findInDir(dir, toolSessionId) {
52179
+ let names;
52180
+ try {
52181
+ names = import_node_fs16.default.readdirSync(dir);
52182
+ } catch {
52183
+ return null;
52184
+ }
52185
+ const suffix = `_${toolSessionId}.jsonl`;
52186
+ const hit = names.filter((n2) => n2.endsWith(suffix)).sort().at(-1);
52187
+ return hit ? import_node_path15.default.join(dir, hit) : null;
52188
+ }
52189
+ function locatePiSessionFile(sessionsRoot, cwd, toolSessionId) {
52190
+ const direct = findInDir(piProjectSessionDir(sessionsRoot, cwd), toolSessionId);
52191
+ if (direct) return direct;
52192
+ let dirs;
52193
+ try {
52194
+ dirs = import_node_fs16.default.readdirSync(sessionsRoot, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) => d2.name);
52195
+ } catch {
52196
+ return null;
52197
+ }
52198
+ for (const d2 of dirs) {
52199
+ const hit = findInDir(import_node_path15.default.join(sessionsRoot, d2), toolSessionId);
52200
+ if (hit) return hit;
52201
+ }
52202
+ return null;
52203
+ }
52204
+ function parseLine2(line) {
52205
+ const t2 = line.trim();
52206
+ if (!t2) return null;
52207
+ try {
52208
+ return rec2(JSON.parse(t2)) ?? null;
52209
+ } catch {
52210
+ return null;
52211
+ }
52212
+ }
52213
+ function parsePiSession(text) {
52214
+ let meta = null;
52215
+ const entries = /* @__PURE__ */ new Map();
52216
+ let last = null;
52217
+ for (const line of text.split("\n")) {
52218
+ const o2 = parseLine2(line);
52219
+ if (!o2) continue;
52220
+ if (o2.type === "session") {
52221
+ if (!meta) {
52222
+ const timestamp = str2(o2.timestamp);
52223
+ meta = { id: str2(o2.id) ?? "", cwd: str2(o2.cwd) ?? "", ...timestamp ? { timestamp } : {} };
52224
+ }
52225
+ continue;
52226
+ }
52227
+ const id = str2(o2.id);
52228
+ if (!id) continue;
52229
+ entries.set(id, o2);
52230
+ last = o2;
52231
+ }
52232
+ const chain = [];
52233
+ const seen = /* @__PURE__ */ new Set();
52234
+ let cur = last;
52235
+ while (cur) {
52236
+ const id = str2(cur.id);
52237
+ if (seen.has(id)) break;
52238
+ seen.add(id);
52239
+ chain.push(cur);
52240
+ const pid = str2(cur.parentId);
52241
+ cur = pid ? entries.get(pid) ?? null : null;
52242
+ }
52243
+ chain.reverse();
52244
+ const messages = [];
52245
+ for (const e of chain) {
52246
+ if (e.type !== "message") continue;
52247
+ const m3 = rec2(e.message);
52248
+ if (!m3) continue;
52249
+ const ts = str2(e.timestamp);
52250
+ const base = { raw: e, uuid: str2(e.id), ...ts ? { ts } : {} };
52251
+ switch (m3.role) {
52252
+ case "user": {
52253
+ const t2 = contentText(m3.content, "[image]");
52254
+ if (t2) messages.push({ kind: "user-text", text: t2, ...base });
52255
+ break;
52256
+ }
52257
+ case "assistant": {
52258
+ for (const b3 of arr(m3.content)) {
52259
+ const block = rec2(b3);
52260
+ if (!block) continue;
52261
+ if (block.type === "text") {
52262
+ const t2 = str2(block.text);
52263
+ if (t2) messages.push({ kind: "assistant-text", text: t2, ...base });
52264
+ } else if (block.type === "toolCall") {
52265
+ const toolUseId = str2(block.id);
52266
+ const tool = str2(block.name);
52267
+ if (toolUseId && tool) messages.push({ kind: "tool_call", tool, toolUseId, input: block.arguments ?? {}, ...base });
52268
+ }
52269
+ }
52270
+ if (str2(m3.stopReason) !== "toolUse") messages.push({ kind: "turn-end", ...base });
52271
+ break;
52272
+ }
52273
+ case "toolResult": {
52274
+ const toolUseId = str2(m3.toolCallId);
52275
+ if (!toolUseId) break;
52276
+ const t2 = contentText(m3.content);
52277
+ const msg = { kind: "tool_result", toolUseId, output: t2, ...base };
52278
+ if (m3.isError === true) msg.error = t2 || "tool failed";
52279
+ messages.push(msg);
52280
+ break;
52281
+ }
52282
+ default:
52283
+ break;
52284
+ }
52285
+ }
52286
+ return { meta, messages };
52287
+ }
52288
+ var PiHistoryReader = class {
52289
+ constructor(deps) {
52290
+ this.deps = deps;
52291
+ }
52292
+ deps;
52293
+ // projects/list 非 session-scoped、不知 tool(同 codex 的取舍)
52294
+ async listProjects() {
52295
+ return [];
52296
+ }
52297
+ async listSessions(args) {
52298
+ const want = import_node_path15.default.resolve(args.projectPath);
52299
+ const dir = piProjectSessionDir(this.deps.sessionsRoot, want);
52300
+ let names;
52301
+ try {
52302
+ names = import_node_fs16.default.readdirSync(dir).filter((n2) => n2.endsWith(".jsonl"));
52303
+ } catch {
52304
+ return [];
52305
+ }
52306
+ const found = [];
52307
+ let scanned = 0;
52308
+ for (const n2 of names) {
52309
+ if (++scanned > PI_LIST_SESSIONS_SCAN_CAP) break;
52310
+ const file = import_node_path15.default.join(dir, n2);
52311
+ const head = parseLine2(readFirstLine(file) ?? "");
52312
+ if (!head || head.type !== "session") continue;
52313
+ const cwd = str2(head.cwd);
52314
+ const id = str2(head.id);
52315
+ if (!cwd || !id || import_node_path15.default.resolve(cwd) !== want) continue;
52316
+ let mtimeMs;
52317
+ try {
52318
+ mtimeMs = import_node_fs16.default.statSync(file).mtimeMs;
52319
+ } catch {
52320
+ continue;
52321
+ }
52322
+ found.push({ file, id, mtimeMs });
52323
+ }
52324
+ found.sort((a2, b3) => b3.mtimeMs - a2.mtimeMs);
52325
+ return found.slice(0, PI_LIST_SESSIONS_LIMIT).map((f2) => {
52326
+ const parsed = parsePiSession(import_node_fs16.default.readFileSync(f2.file, "utf8"));
52327
+ const firstUser = parsed.messages.find((m3) => m3.kind === "user-text");
52328
+ return {
52329
+ toolSessionId: f2.id,
52330
+ summary: firstUser?.text?.slice(0, 80) || "(\u7A7A\u4F1A\u8BDD)",
52331
+ messageCount: parsed.messages.length,
52332
+ updatedAt: new Date(f2.mtimeMs).toISOString()
52333
+ };
52334
+ });
52335
+ }
52336
+ async read(args) {
52337
+ const file = locatePiSessionFile(this.deps.sessionsRoot, args.cwd, args.toolSessionId);
52338
+ if (!file) throw new Error(`pi session not found for ${args.toolSessionId}`);
52339
+ return pageMessages(parsePiSession(import_node_fs16.default.readFileSync(file, "utf8")).messages, args);
52340
+ }
52341
+ async listSubagents(_args) {
52342
+ return [];
52343
+ }
52344
+ async readSubagent(args) {
52345
+ throw new Error(`pi has no subagent threads (${args.subagentId})`);
52346
+ }
52347
+ computeRewindDiff() {
52348
+ return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
52349
+ }
52350
+ listRewindableUserMessageIds() {
52351
+ return [];
52352
+ }
52353
+ };
52354
+
52355
+ // src/tools/pi-cloud-history.ts
52356
+ var CloudPiHistoryReader = class {
52357
+ constructor(client, logger) {
52358
+ this.client = client;
52359
+ this.logger = logger;
52360
+ }
52361
+ client;
52362
+ logger;
52363
+ async listProjects() {
52364
+ return [];
52365
+ }
52366
+ /** 云 persona 没有「关联已有会话」的动线:会话列表就是本机的会话记录 */
52367
+ async listSessions() {
52368
+ return [];
52369
+ }
52370
+ async read(args) {
52371
+ const text = await this.client.fetchRollout(args.toolSessionId);
52372
+ if (text === null) throw new Error(`pi session not found for ${args.toolSessionId}`);
52373
+ const parsed = parsePiSession(text);
52374
+ if (!parsed.meta) this.logger?.warn("cloud pi session has no header line", { toolSessionId: args.toolSessionId });
52375
+ return pageMessages(parsed.messages, args);
52376
+ }
52377
+ async listSubagents(_args) {
52378
+ return [];
52379
+ }
52380
+ async readSubagent(args) {
52381
+ throw new Error(`pi has no subagent threads (${args.subagentId})`);
52382
+ }
52383
+ computeRewindDiff() {
52384
+ return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
52385
+ }
52386
+ listRewindableUserMessageIds() {
52387
+ return [];
52388
+ }
52389
+ };
52390
+
52391
+ // src/feishu-auth/central-client.ts
52392
+ var CentralClientError = class extends Error {
52393
+ constructor(code, message, cause, status) {
52394
+ super(message);
52395
+ this.code = code;
52396
+ this.cause = cause;
52397
+ this.status = status;
52398
+ this.name = "CentralClientError";
52399
+ }
52400
+ code;
52401
+ cause;
52402
+ status;
52403
+ };
52404
+ async function centralRequest(opts, path102, init) {
52405
+ const f2 = opts.fetchImpl ?? globalThis.fetch;
52406
+ const url = `${opts.api.replace(/\/+$/, "")}${path102}`;
52407
+ const ctrl = new AbortController();
52408
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
52409
+ let res;
52410
+ try {
52411
+ res = await f2(url, {
52412
+ method: init.method,
52413
+ headers: {
52414
+ "content-type": "application/json",
52415
+ ...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
52416
+ },
52417
+ ...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
52418
+ signal: ctrl.signal
52419
+ });
52420
+ } catch (err) {
52421
+ clearTimeout(timer);
52422
+ throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
52423
+ }
52424
+ clearTimeout(timer);
52425
+ if (res.status === 401) {
52426
+ throw new CentralClientError("UNAUTHORIZED", "login pass rejected by center");
52427
+ }
52428
+ if (!res.ok) {
52429
+ let detail = "";
52430
+ try {
52431
+ const body = await res.json();
52432
+ if (typeof body?.message === "string" && body.message) detail = `: ${body.message}`;
52433
+ } catch {
52434
+ }
52435
+ throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}${detail}`, void 0, res.status);
52436
+ }
52437
+ try {
52438
+ return await res.json();
52439
+ } catch (err) {
52440
+ throw new CentralClientError("BAD_RESPONSE", "central server: invalid json response", err);
52441
+ }
52442
+ }
52443
+ async function centralExchange(opts) {
52444
+ const body = await centralRequest(opts, "/api/clawd-identity/exchange", {
52445
+ method: "POST",
52446
+ bearer: opts.loginPass,
52447
+ body: { deviceId: opts.deviceId }
52448
+ });
52449
+ if (typeof body.token !== "string" || body.token.length === 0 || typeof body.ownerId !== "string" || typeof body.provider !== "string" || typeof body.displayName !== "string") {
52450
+ throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/ownerId/provider/displayName");
52451
+ }
52452
+ const deviceUrl = typeof body.deviceUrl === "string" && body.deviceUrl.length > 0 ? body.deviceUrl : null;
52453
+ return { token: body.token, ownerId: body.ownerId, provider: body.provider, displayName: body.displayName, deviceUrl };
52454
+ }
52455
+ async function centralGetPublicKey(opts) {
52456
+ const body = await centralRequest(opts, "/api/clawd-identity/public-key", {
52457
+ method: "GET"
52458
+ });
52459
+ if (typeof body.publicKeyPem !== "string" || !body.publicKeyPem.includes("PUBLIC KEY")) {
52460
+ throw new CentralClientError("BAD_RESPONSE", "public-key: missing or malformed publicKeyPem");
52461
+ }
52462
+ return { publicKeyPem: body.publicKeyPem };
52463
+ }
52464
+ async function centralUpsertDevice(opts) {
52465
+ const body = await centralRequest(opts, "/api/clawd-identity/devices/self", {
52466
+ method: "PUT",
52467
+ bearer: opts.loginPass,
52468
+ body: {
52469
+ ...opts.url ? { url: opts.url } : {},
52470
+ ...opts.name ? { name: opts.name } : {}
52471
+ }
52472
+ });
52473
+ if (body.ok !== true) {
52474
+ throw new CentralClientError("BAD_RESPONSE", "upsertDevice: server did not ack ok");
52475
+ }
52476
+ return { ok: true };
52477
+ }
52478
+ async function centralPrincipalStatus(opts) {
52479
+ const query = `provider=${encodeURIComponent(opts.provider)}&ownerId=${encodeURIComponent(opts.ownerId)}`;
52480
+ const body = await centralRequest(opts, `/api/clawd-identity/principal-status?${query}`, {
52481
+ method: "GET"
52482
+ });
52483
+ if (typeof body.active !== "boolean") {
52484
+ throw new CentralClientError("BAD_RESPONSE", "principal-status: missing active flag");
52485
+ }
52486
+ return { active: body.active };
52487
+ }
52488
+ async function centralGetApiKey(opts) {
52489
+ const body = await centralRequest(opts, "/api/persona-cloud/api-key", {
52490
+ method: "GET",
52491
+ bearer: opts.loginPass
52492
+ });
52493
+ if (body.apiKey !== null && typeof body.apiKey !== "string") {
52494
+ throw new CentralClientError("BAD_RESPONSE", "api-key: apiKey must be string or null");
52495
+ }
52496
+ return {
52497
+ apiKey: body.apiKey,
52498
+ createdAt: typeof body.createdAt === "string" ? body.createdAt : null,
52499
+ // 中心没配就是 null —— 不当成错误,key 本身照样有效
52500
+ gatewayUrl: typeof body.gatewayUrl === "string" ? body.gatewayUrl : null
52501
+ };
52502
+ }
52503
+ async function centralGetThread(opts) {
52504
+ const f2 = opts.fetchImpl ?? globalThis.fetch;
52505
+ const url = `${opts.api.replace(/\/+$/, "")}/api/persona-cloud/threads/${encodeURIComponent(opts.threadId)}`;
52506
+ const ctrl = new AbortController();
52507
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
52508
+ let res;
52509
+ try {
52510
+ res = await f2(url, { method: "GET", headers: { authorization: `Bearer ${opts.loginPass}` }, signal: ctrl.signal });
52511
+ } catch (err) {
52512
+ clearTimeout(timer);
52513
+ throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
52514
+ }
52515
+ clearTimeout(timer);
52516
+ if (res.status === 404) return null;
52517
+ if (res.status === 401) throw new CentralClientError("UNAUTHORIZED", "login pass rejected by center");
52518
+ if (!res.ok) {
52519
+ let detail = "";
52520
+ try {
52521
+ const body = await res.json();
52522
+ if (typeof body?.message === "string" && body.message) detail = `: ${body.message}`;
52523
+ } catch {
52524
+ }
52525
+ throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}${detail}`);
52526
+ }
52527
+ return await res.text();
52528
+ }
52529
+ async function centralIssueApiKey(opts) {
52530
+ const body = await centralRequest(opts, "/api/persona-cloud/api-key", {
52531
+ method: "POST",
52532
+ bearer: opts.loginPass
52533
+ });
52534
+ if (typeof body.apiKey !== "string" || body.apiKey.length === 0) {
52535
+ throw new CentralClientError("BAD_RESPONSE", "api-key: missing apiKey");
52536
+ }
52537
+ if (typeof body.createdAt !== "string" || body.createdAt.length === 0) {
52538
+ throw new CentralClientError("BAD_RESPONSE", "api-key: missing createdAt");
52539
+ }
52540
+ return {
52541
+ apiKey: body.apiKey,
52542
+ createdAt: body.createdAt,
52543
+ gatewayUrl: typeof body.gatewayUrl === "string" ? body.gatewayUrl : null
52544
+ };
52545
+ }
52546
+ async function centralRevokeApiKey(opts) {
52547
+ const body = await centralRequest(opts, "/api/persona-cloud/api-key", {
52548
+ method: "DELETE",
52549
+ bearer: opts.loginPass
52550
+ });
52551
+ if (typeof body.revoked !== "boolean") {
52552
+ throw new CentralClientError("BAD_RESPONSE", "api-key: revoked must be boolean");
52553
+ }
52554
+ return { revoked: body.revoked };
52555
+ }
52556
+ async function centralListDevices(opts) {
52557
+ const body = await centralRequest(opts, "/api/clawd-identity/devices", {
52558
+ method: "GET",
52559
+ bearer: opts.loginPass
52560
+ });
52561
+ if (!Array.isArray(body.devices)) {
52562
+ throw new CentralClientError("BAD_RESPONSE", "devices: missing devices array");
52563
+ }
52564
+ const out = [];
52565
+ for (const raw of body.devices) {
52566
+ const d2 = raw;
52567
+ if (typeof d2.deviceId !== "string" || typeof d2.name !== "string" || typeof d2.url !== "string") {
52568
+ throw new CentralClientError("BAD_RESPONSE", "devices: malformed device entry");
52569
+ }
52570
+ out.push({ deviceId: d2.deviceId, name: d2.name, url: d2.url });
52571
+ }
52572
+ return out;
52573
+ }
52574
+ async function centralConnectorsMine(opts) {
52575
+ const body = await centralRequest(opts, "/api/persona-cloud/connectors/mine", {
52576
+ method: "POST",
52577
+ bearer: opts.loginPass,
52578
+ body: { personaId: opts.personaId, connectors: opts.connectors }
52579
+ });
52580
+ if (!Array.isArray(body.connectors)) throw new CentralClientError("BAD_RESPONSE", "connectors/mine: missing connectors array");
52581
+ return { connectors: body.connectors };
52582
+ }
52583
+ async function centralConnectorFill(opts) {
52584
+ const body = await centralRequest(opts, "/api/persona-cloud/connectors/fields", {
52585
+ method: "PUT",
52586
+ bearer: opts.loginPass,
52587
+ body: { connector: opts.connector, values: opts.values }
52588
+ });
52589
+ if (body.ok !== true) throw new CentralClientError("BAD_RESPONSE", "connectors/fields: server did not ack ok");
52590
+ }
52591
+ async function centralGithubAuthOauthStart(opts) {
52592
+ const body = await centralRequest(opts, "/api/persona-cloud/github/oauth/start", {
52593
+ method: "POST",
52594
+ bearer: opts.loginPass,
52595
+ body: { ...opts.returnTo ? { returnTo: opts.returnTo } : {} }
52596
+ });
52597
+ if (typeof body.authUrl !== "string" || !body.authUrl) throw new CentralClientError("BAD_RESPONSE", "github/oauth/start: missing authUrl");
52598
+ return { authUrl: body.authUrl };
52599
+ }
52600
+ async function centralGithubAuthToken(opts) {
52601
+ const body = await centralRequest(opts, "/api/persona-cloud/github/oauth/token", {
52602
+ method: "POST",
52603
+ bearer: opts.loginPass,
52604
+ body: {}
52605
+ });
52606
+ if (body.token === null || body.token === void 0) return void 0;
52607
+ if (typeof body.token !== "string" || !body.token) throw new CentralClientError("BAD_RESPONSE", "github/oauth/token: malformed token");
52608
+ return { token: body.token, login: typeof body.login === "string" ? body.login : "" };
52609
+ }
52610
+ async function centralClaimPublisher(opts) {
52611
+ await centralRequest(opts, `/api/persona-cloud/personas/${encodeURIComponent(opts.personaId)}/publisher`, {
52612
+ method: "PUT",
52613
+ bearer: opts.loginPass
52614
+ });
52615
+ }
52616
+ async function centralVisiblePersonas(opts) {
52617
+ const body = await centralRequest(opts, "/api/persona-cloud/personas/visible", { method: "GET", bearer: opts.loginPass });
52618
+ if (typeof body.all !== "boolean" || !Array.isArray(body.personaIds) || !body.personaIds.every((p3) => typeof p3 === "string")) {
52619
+ throw new CentralClientError("BAD_RESPONSE", "personas/visible: expected { all: boolean, personaIds: string[] }");
52620
+ }
52621
+ return { all: body.all, personaIds: body.personaIds };
52622
+ }
52623
+ async function centralPersonaThreads(opts) {
52624
+ const f2 = opts.fetchImpl ?? globalThis.fetch;
52625
+ const q3 = new URLSearchParams();
52626
+ if (opts.before) q3.set("before", opts.before);
52627
+ if (opts.limit !== null) q3.set("limit", String(opts.limit));
52628
+ const qs = String(q3) ? `?${q3}` : "";
52629
+ const url = `${opts.api.replace(/\/+$/, "")}/api/persona-cloud/personas/${encodeURIComponent(opts.personaId)}/threads${qs}`;
52630
+ const ctrl = new AbortController();
52631
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
52632
+ try {
52633
+ const res = await f2(url, { method: "GET", headers: { authorization: `Bearer ${opts.loginPass}` }, signal: ctrl.signal });
52634
+ return { status: res.status, body: await res.text() };
52635
+ } catch (err) {
52636
+ throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
52637
+ } finally {
52638
+ clearTimeout(timer);
52639
+ }
52640
+ }
52641
+ async function centralPersonaThread(opts) {
52642
+ const f2 = opts.fetchImpl ?? globalThis.fetch;
52643
+ const url = `${opts.api.replace(/\/+$/, "")}/api/persona-cloud/personas/${encodeURIComponent(opts.personaId)}/threads/${encodeURIComponent(opts.unionId)}/${encodeURIComponent(opts.threadId)}`;
52644
+ const ctrl = new AbortController();
52645
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
52646
+ let res;
52647
+ try {
52648
+ res = await f2(url, { method: "GET", headers: { authorization: `Bearer ${opts.loginPass}` }, signal: ctrl.signal });
52649
+ } catch (err) {
52650
+ clearTimeout(timer);
52651
+ throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
52652
+ }
52653
+ clearTimeout(timer);
52654
+ if (res.status === 404) return null;
52655
+ if (res.status === 401) throw new CentralClientError("UNAUTHORIZED", "login pass rejected by center");
52656
+ if (!res.ok) {
52657
+ let detail = "";
52658
+ try {
52659
+ const body = await res.json();
52660
+ const m3 = body?.message;
52661
+ const said = typeof m3 === "string" ? m3 : Array.isArray(m3) ? m3.map(String).join("\uFF1B") : "";
52662
+ if (said) detail = `: ${said}`;
52663
+ } catch {
52664
+ }
52665
+ throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}${detail}`, void 0, res.status);
52666
+ }
52667
+ return await res.text();
52668
+ }
52669
+
52670
+ // src/dashboard/conversations.ts
52671
+ function visiblePersonaIds(registered, visible) {
52672
+ if (visible.all) return registered;
52673
+ const mine = new Set(visible.personaIds);
52674
+ return registered.filter((id) => mine.has(id));
52675
+ }
52676
+ function engineOfRollout(text) {
52677
+ const nl = text.indexOf("\n");
52678
+ try {
52679
+ const first = JSON.parse(nl < 0 ? text : text.slice(0, nl));
52680
+ return first?.type === "session" ? "pi" : "codex";
52681
+ } catch {
52682
+ return "codex";
52683
+ }
52684
+ }
52685
+ async function readConversation(args) {
52686
+ const { threadId, fetchThread, logger } = args;
52687
+ const first = await fetchThread(threadId);
52688
+ if (first === null) throw new DashboardDataError(404, "THREAD_NOT_FOUND", "\u8FD9\u6761\u5BF9\u8BDD\u7684\u8BB0\u5F55\u4E0D\u5728\u4E86");
52689
+ const engine = engineOfRollout(first);
52690
+ const source = { fetchRollout: (id) => id === threadId ? Promise.resolve(first) : fetchThread(id) };
52691
+ const reader = engine === "pi" ? new CloudPiHistoryReader(source, logger) : new CloudCodexHistoryReader(source, logger);
52692
+ const { messages } = await reader.read({ cwd: "", toolSessionId: threadId, limit: Number.MAX_SAFE_INTEGER });
52693
+ return { engine, messages };
52694
+ }
52695
+ function dashboardErrorOf(err) {
52696
+ if (err instanceof CentralClientError && err.status === 403) {
52697
+ const said = err.message.replace(/^central server HTTP \d+:?\s*/, "");
52698
+ return new DashboardDataError(403, "NOT_PERSONA_VIEWER", said || "\u6CA1\u6709\u8FD9\u4E2A persona \u7684\u67E5\u770B\u6743\u9650");
52699
+ }
52700
+ return err;
52701
+ }
52702
+ function createDashboardData(deps) {
52703
+ const mustLoginPass = () => {
52704
+ const lp = deps.loginPass();
52705
+ if (!lp) throw new Error("\u8FD8\u6CA1\u767B\u5F55 clawd\uFF1A\u5148\u5728\u8BBE\u7F6E\u91CC\u767B\u5F55\u98DE\u4E66");
52706
+ return lp;
52707
+ };
52708
+ return {
52709
+ async personas() {
52710
+ const lp = mustLoginPass();
52711
+ const [registered, visible] = await Promise.all([deps.registeredPersonas(), deps.visiblePersonas(lp)]);
52712
+ return visiblePersonaIds(registered, visible);
52713
+ },
52714
+ metrics: (q3) => deps.metrics(q3),
52715
+ async threads(q3) {
52716
+ return deps.threads(mustLoginPass(), q3);
52717
+ },
52718
+ async thread({ personaId: personaId2, unionId, threadId }) {
52719
+ const lp = mustLoginPass();
52720
+ try {
52721
+ return await readConversation({ threadId, fetchThread: (id) => deps.thread(lp, { personaId: personaId2, unionId, threadId: id }), logger: deps.logger });
52722
+ } catch (err) {
52723
+ throw dashboardErrorOf(err);
52724
+ }
52725
+ }
52726
+ };
52727
+ }
52728
+
52729
+ // src/share-ui/load.ts
52730
+ var import_node_fs18 = __toESM(require("fs"), 1);
52731
+ var import_node_path17 = __toESM(require("path"), 1);
52732
+ var import_node_url4 = require("url");
52733
+
52734
+ // src/share-ui/asset-loader.ts
52735
+ var import_node_fs17 = __toESM(require("fs"), 1);
52736
+ var import_node_path16 = __toESM(require("path"), 1);
52737
+ function createShareUiAssetLoader(opts) {
52738
+ let indexHtml;
52739
+ try {
52740
+ indexHtml = import_node_fs17.default.readFileSync(opts.indexHtmlPath, "utf8");
52741
+ } catch (err) {
52742
+ throw new Error(`share-ui index.html not found at ${opts.indexHtmlPath}: ${err.message}`);
52743
+ }
52744
+ const assetsDir = import_node_path16.default.resolve(opts.assetsDir);
52745
+ return {
52746
+ renderIndexHtml() {
52747
+ return indexHtml;
52748
+ },
52749
+ resolveAssetPath(rel) {
52750
+ const resolved = import_node_path16.default.resolve(assetsDir, rel);
52751
+ if (resolved !== assetsDir && !resolved.startsWith(assetsDir + import_node_path16.default.sep)) return null;
52752
+ try {
52753
+ if (import_node_fs17.default.statSync(resolved).isFile()) return resolved;
52754
+ } catch {
52755
+ }
52756
+ return null;
52757
+ }
52758
+ };
52759
+ }
52760
+
52761
+ // src/share-ui/load.ts
52762
+ var import_meta4 = {};
52763
+ function bundleDirFromArgv(argv1) {
52764
+ if (!argv1) return null;
52765
+ let real = argv1;
52766
+ try {
52767
+ real = import_node_fs18.default.realpathSync(argv1);
52768
+ } catch {
52769
+ }
52770
+ return import_node_path17.default.dirname(real);
52771
+ }
52772
+ function tryLoadShareUi(logger) {
52773
+ const candidates = [];
52774
+ try {
52775
+ const here = import_node_path17.default.dirname((0, import_node_url4.fileURLToPath)(import_meta4.url));
52776
+ candidates.push(here);
52777
+ candidates.push(import_node_path17.default.resolve(here, ".."));
52778
+ candidates.push(import_node_path17.default.resolve(here, "..", "..", "dist"));
52779
+ } catch {
52780
+ }
52781
+ const argvDir = bundleDirFromArgv(process.argv[1]);
52782
+ if (argvDir) candidates.push(argvDir);
52783
+ for (const root of candidates) {
52784
+ const shareUiDir = import_node_path17.default.join(root, "share-ui");
52785
+ const indexHtmlPath = import_node_path17.default.join(shareUiDir, "guest.html");
52786
+ const assetsDir = import_node_path17.default.join(shareUiDir, "assets");
52787
+ try {
52788
+ if (import_node_fs18.default.statSync(indexHtmlPath).isFile() && import_node_fs18.default.statSync(assetsDir).isDirectory()) {
52789
+ logger?.info("share-ui.assets-root.resolved", { root: shareUiDir });
52790
+ return createShareUiAssetLoader({ indexHtmlPath, assetsDir });
52791
+ }
52792
+ } catch {
52793
+ }
52794
+ }
52795
+ logger?.warn("share-ui.assets-not-found", {
52796
+ candidates,
52797
+ hint: "run `pnpm -F @clawos/clawd-ui build && pnpm -F @clawos-dev/clawd build`"
52798
+ });
52799
+ return null;
52800
+ }
52801
+
52802
+ // src/visitor/visitor-token.ts
52803
+ var import_node_crypto6 = __toESM(require("crypto"), 1);
52804
+
52805
+ // ../protocol/src/index.ts
52806
+ init_runtime();
52807
+
52808
+ // src/visitor/visitor-token.ts
52809
+ var DOMAIN_PREFIX3 = "clawd-visitor-v1|";
52810
+ function hmac(secret, body) {
52811
+ return import_node_crypto6.default.createHmac("sha256", secret).update(`${DOMAIN_PREFIX3}${body}`).digest("base64url");
52812
+ }
52813
+ function signVisitorToken(secret, payload) {
52814
+ const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
52815
+ return `${body}.${hmac(secret, body)}`;
52816
+ }
52817
+ function verifyVisitorToken(secret, token, now) {
52818
+ const dot = token.indexOf(".");
52819
+ if (dot <= 0) return { ok: false, reason: "BAD_FORMAT" };
52820
+ const body = token.slice(0, dot);
52821
+ const sig = token.slice(dot + 1);
52822
+ const expected = hmac(secret, body);
52823
+ if (sig.length !== expected.length) return { ok: false, reason: "BAD_SIGNATURE" };
52824
+ if (!import_node_crypto6.default.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
52825
+ return { ok: false, reason: "BAD_SIGNATURE" };
52826
+ }
52827
+ let payload;
52828
+ try {
52829
+ const parsed = VisitorTokenPayloadSchema.safeParse(
52830
+ JSON.parse(Buffer.from(body, "base64url").toString("utf8"))
52831
+ );
52832
+ if (!parsed.success) return { ok: false, reason: "BAD_FORMAT" };
52833
+ payload = parsed.data;
52834
+ } catch {
52835
+ return { ok: false, reason: "BAD_FORMAT" };
52836
+ }
52837
+ if (now >= payload.exp) return { ok: false, reason: "EXPIRED" };
52838
+ return { ok: true, payload };
52839
+ }
52840
+
52841
+ // src/visitor/visitor-login.ts
52842
+ function buildVisitorLogin(deps) {
52843
+ return async function exchangeVisitorToken(args) {
52844
+ let id;
52845
+ try {
52846
+ id = await deps.fetchUserInfo(args.ttcToken);
52847
+ } catch {
52848
+ return { ok: false, reason: "BAD_TTC_TOKEN" };
52849
+ }
52850
+ deps.visitorStore.upsert({
52851
+ visitorId: id.ownerId,
52852
+ displayName: id.displayName,
52853
+ provider: id.provider,
52854
+ ...id.avatarUrl ? { avatarUrl: id.avatarUrl } : {},
52855
+ now: deps.now()
52856
+ });
52857
+ const iat = Math.floor(deps.now() / 1e3);
52858
+ const visitorToken = signVisitorToken(deps.getSecret(), {
52859
+ v: 1,
52860
+ visitorId: id.ownerId,
52861
+ displayName: id.displayName,
52862
+ provider: id.provider,
52863
+ iat,
52864
+ exp: iat + deps.ttlSeconds
52865
+ });
52866
+ return { ok: true, visitorToken };
52867
+ };
52868
+ }
52869
+
52870
+ // src/visitor/visitor-store.ts
52871
+ var import_node_fs19 = __toESM(require("fs"), 1);
52872
+ var import_node_path18 = __toESM(require("path"), 1);
52873
+ function createVisitorStore(opts) {
52874
+ const file = import_node_path18.default.join(opts.dir, "visitors.json");
52875
+ const read = () => {
52876
+ try {
52877
+ return JSON.parse(import_node_fs19.default.readFileSync(file, "utf8"));
52878
+ } catch {
52879
+ return [];
52880
+ }
52881
+ };
52882
+ const write = (rows) => {
52883
+ import_node_fs19.default.mkdirSync(opts.dir, { recursive: true });
52884
+ import_node_fs19.default.writeFileSync(file, JSON.stringify(rows, null, 2));
52885
+ };
52886
+ return {
52887
+ upsert(r2) {
52888
+ const rows = read();
52889
+ const ex = rows.find((x2) => x2.visitorId === r2.visitorId);
52890
+ if (ex) {
52891
+ ex.displayName = r2.displayName;
52892
+ ex.provider = r2.provider;
52893
+ ex.lastSeen = r2.now;
52894
+ if (r2.avatarUrl) ex.avatarUrl = r2.avatarUrl;
52895
+ } else {
52896
+ rows.push({
52897
+ visitorId: r2.visitorId,
52898
+ displayName: r2.displayName,
52899
+ provider: r2.provider,
52900
+ ...r2.avatarUrl ? { avatarUrl: r2.avatarUrl } : {},
52901
+ firstSeen: r2.now,
52902
+ lastSeen: r2.now
52903
+ });
52904
+ }
52905
+ write(rows);
52906
+ },
52907
+ list() {
52908
+ return read();
52909
+ }
52910
+ };
52911
+ }
52912
+
52913
+ // src/index.ts
52914
+ init_claude();
52915
+
52916
+ // src/tools/codex.ts
52917
+ var import_node_child_process7 = require("child_process");
52918
+ var import_node_fs24 = __toESM(require("fs"), 1);
52919
+
52920
+ // src/tools/codex-session.ts
52921
+ var import_node_child_process4 = require("child_process");
52922
+ var import_node_path22 = __toESM(require("path"), 1);
52923
+
52924
+ // src/tools/codex-app-server-client.ts
52925
+ var import_node_readline = require("readline");
52926
+ function stdioLineStream(proc) {
52927
+ const rl = (0, import_node_readline.createInterface)({ input: proc.stdout, crlfDelay: Infinity });
52928
+ const lineCbs = [];
52929
+ const closeCbs = [];
52930
+ rl.on("line", (l2) => {
52931
+ for (const cb of lineCbs) cb(l2);
52932
+ });
52933
+ rl.on("close", () => {
52934
+ for (const cb of closeCbs) cb();
52935
+ });
52936
+ return {
52937
+ write: (line) => {
52938
+ proc.stdin.write(line + "\n");
52939
+ },
52940
+ onLine: (cb) => {
52941
+ lineCbs.push(cb);
52942
+ },
52943
+ onClose: (cb) => {
52944
+ closeCbs.push(cb);
52945
+ },
52946
+ close: () => {
52947
+ proc.stdin?.end();
52948
+ }
52949
+ };
52950
+ }
52951
+ var CodexAppServerClient = class {
52952
+ constructor(stream, opts = {}) {
52953
+ this.stream = stream;
52954
+ this.opts = opts;
52955
+ stream.onLine((l2) => this.onLine(l2));
52956
+ stream.onClose(() => {
52957
+ for (const p3 of this.pending.values()) p3.reject(new Error("app-server connection closed"));
52958
+ this.pending.clear();
52959
+ this.opts.onClose?.();
52960
+ });
52961
+ }
52962
+ stream;
52963
+ opts;
52964
+ nextId = 1;
52965
+ pending = /* @__PURE__ */ new Map();
52966
+ request(method, params) {
52967
+ const id = this.nextId++;
52968
+ return new Promise((resolve9, reject) => {
52969
+ this.pending.set(id, { resolve: resolve9, reject });
52970
+ this.write({ jsonrpc: "2.0", method, id, params });
52971
+ });
52972
+ }
52973
+ notify(method, params) {
52974
+ this.write({ jsonrpc: "2.0", method, params });
52975
+ }
52976
+ // server-request 的响应也必须带 jsonrpc:'2.0',否则 codex app-server 忽略它 → 审批/问答永远卡住。
52977
+ respond(id, result) {
52978
+ this.write({ jsonrpc: "2.0", id, result });
52979
+ }
52980
+ onLine(line) {
52981
+ const t2 = line.trim();
52982
+ if (!t2) return;
52983
+ let m3;
52984
+ try {
52985
+ m3 = JSON.parse(t2);
52986
+ } catch {
52987
+ return;
52988
+ }
52989
+ if (!isRecord(m3)) return;
52990
+ const hasId = typeof m3.id === "number", hasMethod = typeof m3.method === "string";
52991
+ if (hasId && !hasMethod) {
52992
+ const p3 = this.pending.get(m3.id);
52993
+ if (!p3) return;
51729
52994
  this.pending.delete(m3.id);
51730
52995
  if (m3.error) {
51731
52996
  const e = isRecord(m3.error) ? m3.error : {};
@@ -51749,49 +53014,49 @@ function isRecord(v3) {
51749
53014
 
51750
53015
  // src/tools/codex-app-server-events.ts
51751
53016
  function translateNotification(method, params) {
51752
- const p3 = rec(params);
53017
+ const p3 = rec3(params);
51753
53018
  switch (method) {
51754
53019
  case "thread/started": {
51755
- const id = str(rec(p3?.thread)?.id);
53020
+ const id = str3(rec3(p3?.thread)?.id);
51756
53021
  return id ? [{ kind: "session_init", toolSessionId: id }] : [];
51757
53022
  }
51758
53023
  case "item/agentMessage/delta": {
51759
- const text = str(p3?.delta);
51760
- const id = str(p3?.itemId);
53024
+ const text = str3(p3?.delta);
53025
+ const id = str3(p3?.itemId);
51761
53026
  return text ? [{ kind: "text", text, ...id ? { partialId: id } : {} }] : [];
51762
53027
  }
51763
53028
  case "item/reasoning/textDelta":
51764
53029
  case "item/reasoning/summaryTextDelta": {
51765
- const text = str(p3?.delta);
51766
- const id = str(p3?.itemId);
53030
+ const text = str3(p3?.delta);
53031
+ const id = str3(p3?.itemId);
51767
53032
  return text ? [{ kind: "thinking", text, ...id ? { partialId: id } : {} }] : [];
51768
53033
  }
51769
53034
  case "item/commandExecution/outputDelta":
51770
53035
  return [];
51771
53036
  // 命令输出增量 v1 不逐字(完成时整段给)
51772
53037
  case "item/started":
51773
- return itemStarted(rec(p3?.item));
53038
+ return itemStarted(rec3(p3?.item));
51774
53039
  case "item/completed":
51775
- return itemCompleted(rec(p3?.item));
53040
+ return itemCompleted(rec3(p3?.item));
51776
53041
  case "thread/tokenUsage/updated":
51777
- return tokenUsage(rec(rec(p3?.tokenUsage)));
53042
+ return tokenUsage(rec3(rec3(p3?.tokenUsage)));
51778
53043
  // codex 自动生成的 thread 标题(实测 0.145:payload = { threadId, threadName })
51779
53044
  case "thread/name/updated": {
51780
- const name = str(p3?.threadName);
53045
+ const name = str3(p3?.threadName);
51781
53046
  return name ? [{ kind: "meta_update", patch: { aiLabel: name } }] : [];
51782
53047
  }
51783
53048
  case "turn/completed":
51784
- return turnCompleted(rec(p3?.turn));
53049
+ return turnCompleted(rec3(p3?.turn));
51785
53050
  default:
51786
53051
  return [];
51787
53052
  }
51788
53053
  }
51789
53054
  function itemStarted(item) {
51790
53055
  if (!item) return [];
51791
- const id = str(item.id);
53056
+ const id = str3(item.id);
51792
53057
  if (!id) return [];
51793
53058
  if (item.type === "commandExecution") {
51794
- return [{ kind: "tool_call", toolUseId: id, tool: "commandExecution", toolKind: "codex", input: { command: str(item.command) ?? "" } }];
53059
+ return [{ kind: "tool_call", toolUseId: id, tool: "commandExecution", toolKind: "codex", input: { command: str3(item.command) ?? "" } }];
51795
53060
  }
51796
53061
  if (item.type === "fileChange") {
51797
53062
  return [{ kind: "tool_call", toolUseId: id, tool: "fileChange", toolKind: "codex", input: { changes: Array.isArray(item.changes) ? item.changes : [] } }];
@@ -51805,28 +53070,28 @@ function itemCompleted(item) {
51805
53070
  return [];
51806
53071
  // 用户输入回显, clawd 自己 synthesize user_text
51807
53072
  case "agentMessage": {
51808
- const text = str(item.text);
53073
+ const text = str3(item.text);
51809
53074
  return text ? [{ kind: "text", text }] : [];
51810
53075
  }
51811
53076
  case "reasoning": {
51812
- const text = str(item.text);
53077
+ const text = str3(item.text);
51813
53078
  return text ? [{ kind: "thinking", text }] : [];
51814
53079
  }
51815
53080
  case "commandExecution": {
51816
- const id = str(item.id);
53081
+ const id = str3(item.id);
51817
53082
  if (!id) return [];
51818
- const exit = num(item.exitCode);
51819
- const ev = { kind: "tool_result", toolUseId: id, output: str(item.aggregatedOutput ?? item.output) ?? "" };
53083
+ const exit = num3(item.exitCode);
53084
+ const ev = { kind: "tool_result", toolUseId: id, output: str3(item.aggregatedOutput ?? item.output) ?? "" };
51820
53085
  if (exit !== void 0 && exit !== 0) ev.error = `exit ${exit}`;
51821
53086
  return [ev];
51822
53087
  }
51823
53088
  case "fileChange": {
51824
- const id = str(item.id);
53089
+ const id = str3(item.id);
51825
53090
  if (!id) return [];
51826
53091
  const changes = Array.isArray(item.changes) ? item.changes : [];
51827
- const output = changes.map((c2) => str(c2.diff) ?? "").filter(Boolean).join("\n");
53092
+ const output = changes.map((c2) => str3(c2.diff) ?? "").filter(Boolean).join("\n");
51828
53093
  const ev = { kind: "tool_result", toolUseId: id, output };
51829
- const status = str(item.status);
53094
+ const status = str3(item.status);
51830
53095
  if (status === "failed" || status === "declined") ev.error = `patch ${status}`;
51831
53096
  return [ev];
51832
53097
  }
@@ -51836,26 +53101,26 @@ function itemCompleted(item) {
51836
53101
  }
51837
53102
  function tokenUsage(usage) {
51838
53103
  if (!usage) return [];
51839
- const last = rec(usage.last);
53104
+ const last = rec3(usage.last);
51840
53105
  if (!last) return [];
51841
- const i2 = num(last.inputTokens), o2 = num(last.outputTokens);
53106
+ const i2 = num3(last.inputTokens), o2 = num3(last.outputTokens);
51842
53107
  const patch = {};
51843
53108
  if (i2 !== void 0 && o2 !== void 0) patch.contextUsage = { inputTokens: i2, outputTokens: o2 };
51844
- const w3 = num(usage.modelContextWindow);
53109
+ const w3 = num3(usage.modelContextWindow);
51845
53110
  if (w3 !== void 0) patch.contextWindowSize = w3;
51846
53111
  return Object.keys(patch).length ? [{ kind: "meta_update", patch }] : [];
51847
53112
  }
51848
53113
  function turnCompleted(turn) {
51849
- if (turn?.status === "failed") return [{ kind: "error", message: str(rec(turn.error)?.message) ?? "codex turn failed" }, { kind: "turn_end" }];
53114
+ if (turn?.status === "failed") return [{ kind: "error", message: str3(rec3(turn.error)?.message) ?? "codex turn failed" }, { kind: "turn_end" }];
51850
53115
  return [{ kind: "turn_end" }];
51851
53116
  }
51852
- function rec(v3) {
53117
+ function rec3(v3) {
51853
53118
  return typeof v3 === "object" && v3 !== null ? v3 : void 0;
51854
53119
  }
51855
- function str(v3) {
53120
+ function str3(v3) {
51856
53121
  return typeof v3 === "string" ? v3 : void 0;
51857
53122
  }
51858
- function num(v3) {
53123
+ function num3(v3) {
51859
53124
  return typeof v3 === "number" ? v3 : void 0;
51860
53125
  }
51861
53126
 
@@ -52319,17 +53584,17 @@ function stringValue2(value) {
52319
53584
  }
52320
53585
 
52321
53586
  // src/tools/mcp-server-defs.ts
52322
- var import_node_fs18 = __toESM(require("fs"), 1);
52323
- var import_node_path16 = __toESM(require("path"), 1);
53587
+ var import_node_fs21 = __toESM(require("fs"), 1);
53588
+ var import_node_path19 = __toESM(require("path"), 1);
52324
53589
  var HTTP_TYPES = /* @__PURE__ */ new Set(["http", "sse", "streamable_http", "streamable-http"]);
52325
53590
  function loadMcpServerDefs(args) {
52326
53591
  const out = [];
52327
53592
  const seen = /* @__PURE__ */ new Set();
52328
- const sources = [...args.mcpConfigPaths, import_node_path16.default.join(args.cwd, ".mcp.json")];
53593
+ const sources = [...args.mcpConfigPaths, import_node_path19.default.join(args.cwd, ".mcp.json")];
52329
53594
  for (const file of sources) {
52330
53595
  let parsed;
52331
53596
  try {
52332
- parsed = JSON.parse(import_node_fs18.default.readFileSync(file, "utf8"));
53597
+ parsed = JSON.parse(import_node_fs21.default.readFileSync(file, "utf8"));
52333
53598
  } catch (e) {
52334
53599
  const code = e.code;
52335
53600
  if (code !== "ENOENT") {
@@ -52393,18 +53658,18 @@ function loadMcpServerDefs(args) {
52393
53658
  }
52394
53659
 
52395
53660
  // src/tools/codex-agents-config.ts
52396
- var import_node_fs19 = __toESM(require("fs"), 1);
52397
- var import_node_path17 = __toESM(require("path"), 1);
53661
+ var import_node_fs22 = __toESM(require("fs"), 1);
53662
+ var import_node_path20 = __toESM(require("path"), 1);
52398
53663
  var AGENTS_SECTION = /^\[agents\][^\n]*\n([\s\S]*?)(?=^\[|$(?![\s\S]))/m;
52399
53664
  function readKey(section, key) {
52400
53665
  const m3 = new RegExp(`^[ \\t]*${key}[ \\t]*=[ \\t]*(["'])(.*?)\\1`, "m").exec(section);
52401
53666
  return m3?.[2].trim() || void 0;
52402
53667
  }
52403
53668
  function loadCodexAgentsConfig(args) {
52404
- const file = import_node_path17.default.join(args.cwd, ".codex", "config.toml");
53669
+ const file = import_node_path20.default.join(args.cwd, ".codex", "config.toml");
52405
53670
  let text;
52406
53671
  try {
52407
- text = import_node_fs19.default.readFileSync(file, "utf8");
53672
+ text = import_node_fs22.default.readFileSync(file, "utf8");
52408
53673
  } catch (e) {
52409
53674
  const code = e.code;
52410
53675
  if (code !== "ENOENT") {
@@ -52460,7 +53725,7 @@ function warmCodexFeatureCache() {
52460
53725
  }
52461
53726
 
52462
53727
  // src/attachment/mime.ts
52463
- var import_node_path18 = __toESM(require("path"), 1);
53728
+ var import_node_path21 = __toESM(require("path"), 1);
52464
53729
  var TEXT_PLAIN = "text/plain; charset=utf-8";
52465
53730
  var EXT_TO_NATIVE_MIME = {
52466
53731
  // 图片
@@ -52567,7 +53832,7 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
52567
53832
  ".mk"
52568
53833
  ]);
52569
53834
  function lookupMime(filePathOrName) {
52570
- const ext = import_node_path18.default.extname(filePathOrName).toLowerCase();
53835
+ const ext = import_node_path21.default.extname(filePathOrName).toLowerCase();
52571
53836
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
52572
53837
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
52573
53838
  return "application/octet-stream";
@@ -52616,7 +53881,7 @@ function createCodexSession(ctx, sink, deps = {}) {
52616
53881
  threadParams: threadStartParams(ctx),
52617
53882
  // persona 惯例装在 <cwd>/.claude/skills/,经 extraRoots 注入 discovery
52618
53883
  // (skills:list RPC 侧 codex-skills.ts 同款注入,两处保持一致)。
52619
- skillsExtraRoots: [import_node_path19.default.join(ctx.cwd, ".claude", "skills")],
53884
+ skillsExtraRoots: [import_node_path22.default.join(ctx.cwd, ".claude", "skills")],
52620
53885
  // 附件图片:验签认出本机 absPath → localImage,codex 自己读文件(TUI 拖图 / SDK local_image 同一条路)。
52621
53886
  // 0.145 起 http(s) URL 在 turn/start 入口被拒,不能再按 url 传(codex-app-server-params.ts 头注释)。
52622
53887
  // 与云上 cloudImageInput 同一道 mime 门:非图片(签过的 .md 之类)留文本,别让 codex 读到一半才炸。
@@ -52770,18 +54035,18 @@ function mergeProviderModels(native, provider) {
52770
54035
 
52771
54036
  // src/tools/codex-provider-models.ts
52772
54037
  var import_node_child_process6 = require("child_process");
52773
- var import_node_fs20 = __toESM(require("fs"), 1);
52774
- var import_node_os8 = __toESM(require("os"), 1);
52775
- var import_node_path20 = __toESM(require("path"), 1);
54038
+ var import_node_fs23 = __toESM(require("fs"), 1);
54039
+ var import_node_os9 = __toESM(require("os"), 1);
54040
+ var import_node_path23 = __toESM(require("path"), 1);
52776
54041
  var import_node_util = require("util");
52777
54042
  var PROVIDER_FETCH_TIMEOUT_MS = 3e3;
52778
- function asRecord(v3) {
54043
+ function asRecord2(v3) {
52779
54044
  return v3 && typeof v3 === "object" && !Array.isArray(v3) ? v3 : null;
52780
54045
  }
52781
54046
  function readAuthKey(codexHome) {
52782
54047
  try {
52783
- const raw = JSON.parse(import_node_fs20.default.readFileSync(import_node_path20.default.join(codexHome, "auth.json"), "utf8"));
52784
- const key = asRecord(raw)?.OPENAI_API_KEY;
54048
+ const raw = JSON.parse(import_node_fs23.default.readFileSync(import_node_path23.default.join(codexHome, "auth.json"), "utf8"));
54049
+ const key = asRecord2(raw)?.OPENAI_API_KEY;
52785
54050
  return typeof key === "string" && key ? key : void 0;
52786
54051
  } catch {
52787
54052
  return void 0;
@@ -52808,19 +54073,19 @@ async function runAuthCommand(name, auth, deps) {
52808
54073
  return void 0;
52809
54074
  }
52810
54075
  async function resolveProviderEndpoint(rawConfig, deps = {}) {
52811
- const config = asRecord(asRecord(rawConfig)?.config);
54076
+ const config = asRecord2(asRecord2(rawConfig)?.config);
52812
54077
  if (!config) return null;
52813
54078
  const name = config.model_provider;
52814
54079
  if (typeof name !== "string" || !name) return null;
52815
- const entry = asRecord(asRecord(config.model_providers)?.[name]);
54080
+ const entry = asRecord2(asRecord2(config.model_providers)?.[name]);
52816
54081
  if (!entry) return null;
52817
54082
  const baseUrl = entry.base_url;
52818
54083
  if (typeof baseUrl !== "string" || !baseUrl) return null;
52819
54084
  const env = deps.env ?? process.env;
52820
- const codexHome = deps.codexHome ?? env.CODEX_HOME ?? import_node_path20.default.join(import_node_os8.default.homedir(), ".codex");
54085
+ const codexHome = deps.codexHome ?? env.CODEX_HOME ?? import_node_path23.default.join(import_node_os9.default.homedir(), ".codex");
52821
54086
  const envKey = typeof entry.env_key === "string" && entry.env_key ? entry.env_key : null;
52822
54087
  const inlineToken = typeof entry.experimental_bearer_token === "string" && entry.experimental_bearer_token ? entry.experimental_bearer_token : void 0;
52823
- const auth = asRecord(entry.auth);
54088
+ const auth = asRecord2(entry.auth);
52824
54089
  let token;
52825
54090
  if (envKey) token = env[envKey];
52826
54091
  else if (inlineToken) token = inlineToken;
@@ -52848,11 +54113,11 @@ async function fetchProviderModels(endpoint, deps = {}) {
52848
54113
  try {
52849
54114
  const res = await fetchImpl(url, { headers: endpoint.headers, signal: controller.signal });
52850
54115
  if (!res.ok) throw new Error(`provider model list failed: HTTP ${res.status} from ${url}`);
52851
- const data = asRecord(await res.json())?.data;
54116
+ const data = asRecord2(await res.json())?.data;
52852
54117
  if (!Array.isArray(data)) throw new Error(`provider model list: \`data\` is not an array (${url})`);
52853
54118
  const out = [];
52854
54119
  for (const item of data) {
52855
- const e = asRecord(item);
54120
+ const e = asRecord2(item);
52856
54121
  if (!e || typeof e.id !== "string" || !e.id) continue;
52857
54122
  out.push({ id: e.id, ...typeof e.owned_by === "string" ? { ownedBy: e.owned_by } : {} });
52858
54123
  }
@@ -52932,10 +54197,10 @@ function sanitizeCodexModel(model) {
52932
54197
  return model;
52933
54198
  }
52934
54199
  async function probeCodex(env = process.env) {
52935
- if (env.CODEX_BIN && import_node_fs21.default.existsSync(env.CODEX_BIN)) return { available: true, path: env.CODEX_BIN };
54200
+ if (env.CODEX_BIN && import_node_fs24.default.existsSync(env.CODEX_BIN)) return { available: true, path: env.CODEX_BIN };
52936
54201
  try {
52937
54202
  const out = (0, import_node_child_process7.execFileSync)("which", ["codex"], { encoding: "utf8" }).trim();
52938
- if (out && import_node_fs21.default.existsSync(out)) return { available: true, path: out };
54203
+ if (out && import_node_fs24.default.existsSync(out)) return { available: true, path: out };
52939
54204
  } catch {
52940
54205
  }
52941
54206
  return { available: false };
@@ -53055,9 +54320,9 @@ var CodexAdapter = class {
53055
54320
 
53056
54321
  // src/tools/claude-tui.ts
53057
54322
  var import_node_crypto7 = require("crypto");
53058
- var import_node_fs22 = __toESM(require("fs"), 1);
53059
- var import_node_os9 = __toESM(require("os"), 1);
53060
- var import_node_path21 = __toESM(require("path"), 1);
54323
+ var import_node_fs25 = __toESM(require("fs"), 1);
54324
+ var import_node_os10 = __toESM(require("os"), 1);
54325
+ var import_node_path24 = __toESM(require("path"), 1);
53061
54326
  var import_headless = __toESM(require_xterm_headless(), 1);
53062
54327
 
53063
54328
  // ../node_modules/.pnpm/@xterm+addon-serialize@0.14.0/node_modules/@xterm/addon-serialize/lib/addon-serialize.mjs
@@ -54282,10 +55547,10 @@ function buildTuiSpawnArgs(ctx, isResume = false) {
54282
55547
  }
54283
55548
  function jsonlExistsForCtx(ctx) {
54284
55549
  if (!ctx.toolSessionId) return false;
54285
- const home = import_node_os9.default.homedir();
54286
- const file = import_node_path21.default.join(home, ".claude", "projects", cwdToHashDir(ctx.cwd), `${ctx.toolSessionId}.jsonl`);
55550
+ const home = import_node_os10.default.homedir();
55551
+ const file = import_node_path24.default.join(home, ".claude", "projects", cwdToHashDir(ctx.cwd), `${ctx.toolSessionId}.jsonl`);
54287
55552
  try {
54288
- return import_node_fs22.default.statSync(file).isFile();
55553
+ return import_node_fs25.default.statSync(file).isFile();
54289
55554
  } catch {
54290
55555
  return false;
54291
55556
  }
@@ -54370,7 +55635,7 @@ var PersonaDispatchManager = class {
54370
55635
 
54371
55636
  // src/dispatch/store.ts
54372
55637
  var import_promises = __toESM(require("fs/promises"), 1);
54373
- var import_node_path22 = __toESM(require("path"), 1);
55638
+ var import_node_path25 = __toESM(require("path"), 1);
54374
55639
 
54375
55640
  // src/shift/constants.ts
54376
55641
  var MAX_RUNS_PER_SHIFT = 30;
@@ -54408,7 +55673,7 @@ function createDispatchStore(deps) {
54408
55673
  flushTimer = null;
54409
55674
  }
54410
55675
  const content = { version: 1, records };
54411
- await import_promises.default.mkdir(import_node_path22.default.dirname(deps.filePath), { recursive: true });
55676
+ await import_promises.default.mkdir(import_node_path25.default.dirname(deps.filePath), { recursive: true });
54412
55677
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
54413
55678
  await import_promises.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
54414
55679
  await import_promises.default.rename(tmp, deps.filePath);
@@ -54477,7 +55742,7 @@ function createDispatchStore(deps) {
54477
55742
 
54478
55743
  // src/channel/json-store.ts
54479
55744
  var import_promises2 = __toESM(require("fs/promises"), 1);
54480
- var import_node_path23 = __toESM(require("path"), 1);
55745
+ var import_node_path26 = __toESM(require("path"), 1);
54481
55746
  function createJsonStore(deps) {
54482
55747
  let items = [];
54483
55748
  let flushTimer = null;
@@ -54488,7 +55753,7 @@ function createJsonStore(deps) {
54488
55753
  flushTimer = null;
54489
55754
  }
54490
55755
  const content = { version: version2, [deps.key]: items };
54491
- await import_promises2.default.mkdir(import_node_path23.default.dirname(deps.filePath), { recursive: true });
55756
+ await import_promises2.default.mkdir(import_node_path26.default.dirname(deps.filePath), { recursive: true });
54492
55757
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
54493
55758
  await import_promises2.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
54494
55759
  await import_promises2.default.rename(tmp, deps.filePath);
@@ -54780,10 +56045,10 @@ function createTopicStore(deps) {
54780
56045
  }
54781
56046
 
54782
56047
  // src/dispatch/mcp-config.ts
54783
- var import_node_fs23 = __toESM(require("fs"), 1);
54784
- var import_node_path24 = __toESM(require("path"), 1);
56048
+ var import_node_fs26 = __toESM(require("fs"), 1);
56049
+ var import_node_path27 = __toESM(require("path"), 1);
54785
56050
  function dispatchMcpConfigPath(dataDir) {
54786
- return import_node_path24.default.join(dataDir, "dispatch.mcp.json");
56051
+ return import_node_path27.default.join(dataDir, "dispatch.mcp.json");
54787
56052
  }
54788
56053
  function writeDispatchMcpConfig(args) {
54789
56054
  const cfgPath = dispatchMcpConfigPath(args.dataDir);
@@ -54802,16 +56067,16 @@ function writeDispatchMcpConfig(args) {
54802
56067
  }
54803
56068
  }
54804
56069
  };
54805
- import_node_fs23.default.mkdirSync(args.dataDir, { recursive: true });
54806
- import_node_fs23.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
56070
+ import_node_fs26.default.mkdirSync(args.dataDir, { recursive: true });
56071
+ import_node_fs26.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
54807
56072
  return cfgPath;
54808
56073
  }
54809
56074
 
54810
56075
  // src/ticket/mcp-config.ts
54811
- var import_node_fs24 = __toESM(require("fs"), 1);
54812
- var import_node_path25 = __toESM(require("path"), 1);
56076
+ var import_node_fs27 = __toESM(require("fs"), 1);
56077
+ var import_node_path28 = __toESM(require("path"), 1);
54813
56078
  function ticketMcpConfigPath(dataDir) {
54814
- return import_node_path25.default.join(dataDir, "ticket.mcp.json");
56079
+ return import_node_path28.default.join(dataDir, "ticket.mcp.json");
54815
56080
  }
54816
56081
  function writeTicketMcpConfig(args) {
54817
56082
  const cfgPath = ticketMcpConfigPath(args.dataDir);
@@ -54833,16 +56098,16 @@ function writeTicketMcpConfig(args) {
54833
56098
  }
54834
56099
  }
54835
56100
  };
54836
- import_node_fs24.default.mkdirSync(args.dataDir, { recursive: true });
54837
- import_node_fs24.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
56101
+ import_node_fs27.default.mkdirSync(args.dataDir, { recursive: true });
56102
+ import_node_fs27.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
54838
56103
  return cfgPath;
54839
56104
  }
54840
56105
 
54841
56106
  // src/shift/mcp-config.ts
54842
- var import_node_fs25 = __toESM(require("fs"), 1);
54843
- var import_node_path26 = __toESM(require("path"), 1);
56107
+ var import_node_fs28 = __toESM(require("fs"), 1);
56108
+ var import_node_path29 = __toESM(require("path"), 1);
54844
56109
  function shiftMcpConfigPath(dataDir) {
54845
- return import_node_path26.default.join(dataDir, "shift.mcp.json");
56110
+ return import_node_path29.default.join(dataDir, "shift.mcp.json");
54846
56111
  }
54847
56112
  async function writeShiftMcpConfig(args) {
54848
56113
  const cfgPath = shiftMcpConfigPath(args.dataDir);
@@ -54861,16 +56126,16 @@ async function writeShiftMcpConfig(args) {
54861
56126
  }
54862
56127
  }
54863
56128
  };
54864
- await import_node_fs25.default.promises.mkdir(args.dataDir, { recursive: true });
54865
- await import_node_fs25.default.promises.writeFile(cfgPath, JSON.stringify(content, null, 2), "utf8");
56129
+ await import_node_fs28.default.promises.mkdir(args.dataDir, { recursive: true });
56130
+ await import_node_fs28.default.promises.writeFile(cfgPath, JSON.stringify(content, null, 2), "utf8");
54866
56131
  return cfgPath;
54867
56132
  }
54868
56133
 
54869
56134
  // src/inbox/mcp-config.ts
54870
- var import_node_fs26 = __toESM(require("fs"), 1);
54871
- var import_node_path27 = __toESM(require("path"), 1);
56135
+ var import_node_fs29 = __toESM(require("fs"), 1);
56136
+ var import_node_path30 = __toESM(require("path"), 1);
54872
56137
  function inboxMcpConfigPath(dataDir) {
54873
- return import_node_path27.default.join(dataDir, "inbox.mcp.json");
56138
+ return import_node_path30.default.join(dataDir, "inbox.mcp.json");
54874
56139
  }
54875
56140
  async function writeInboxMcpConfig(args) {
54876
56141
  const cfgPath = inboxMcpConfigPath(args.dataDir);
@@ -54889,16 +56154,16 @@ async function writeInboxMcpConfig(args) {
54889
56154
  }
54890
56155
  }
54891
56156
  };
54892
- await import_node_fs26.default.promises.mkdir(args.dataDir, { recursive: true });
54893
- await import_node_fs26.default.promises.writeFile(cfgPath, JSON.stringify(content, null, 2), "utf8");
56157
+ await import_node_fs29.default.promises.mkdir(args.dataDir, { recursive: true });
56158
+ await import_node_fs29.default.promises.writeFile(cfgPath, JSON.stringify(content, null, 2), "utf8");
54894
56159
  return cfgPath;
54895
56160
  }
54896
56161
 
54897
56162
  // src/peer-ops/mcp-config.ts
54898
- var import_node_fs27 = __toESM(require("fs"), 1);
54899
- var import_node_path28 = __toESM(require("path"), 1);
56163
+ var import_node_fs30 = __toESM(require("fs"), 1);
56164
+ var import_node_path31 = __toESM(require("path"), 1);
54900
56165
  function peerOpsMcpConfigPath(dataDir) {
54901
- return import_node_path28.default.join(dataDir, "peer-ops.mcp.json");
56166
+ return import_node_path31.default.join(dataDir, "peer-ops.mcp.json");
54902
56167
  }
54903
56168
  function writePeerOpsMcpConfig(args) {
54904
56169
  const cfgPath = peerOpsMcpConfigPath(args.dataDir);
@@ -54915,16 +56180,16 @@ function writePeerOpsMcpConfig(args) {
54915
56180
  }
54916
56181
  }
54917
56182
  };
54918
- import_node_fs27.default.mkdirSync(args.dataDir, { recursive: true });
54919
- import_node_fs27.default.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
56183
+ import_node_fs30.default.mkdirSync(args.dataDir, { recursive: true });
56184
+ import_node_fs30.default.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
54920
56185
  return cfgPath;
54921
56186
  }
54922
56187
 
54923
56188
  // src/rpc-tool/mcp-config.ts
54924
- var import_node_fs28 = __toESM(require("fs"), 1);
54925
- var import_node_path29 = __toESM(require("path"), 1);
56189
+ var import_node_fs31 = __toESM(require("fs"), 1);
56190
+ var import_node_path32 = __toESM(require("path"), 1);
54926
56191
  function rpcToolMcpConfigPath(dataDir) {
54927
- return import_node_path29.default.join(dataDir, "rpc-tool.mcp.json");
56192
+ return import_node_path32.default.join(dataDir, "rpc-tool.mcp.json");
54928
56193
  }
54929
56194
  function writeRpcToolMcpConfig(args) {
54930
56195
  const cfgPath = rpcToolMcpConfigPath(args.dataDir);
@@ -54943,29 +56208,29 @@ function writeRpcToolMcpConfig(args) {
54943
56208
  }
54944
56209
  }
54945
56210
  };
54946
- import_node_fs28.default.mkdirSync(args.dataDir, { recursive: true });
54947
- import_node_fs28.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
56211
+ import_node_fs31.default.mkdirSync(args.dataDir, { recursive: true });
56212
+ import_node_fs31.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
54948
56213
  return cfgPath;
54949
56214
  }
54950
56215
 
54951
56216
  // src/deploy/secret-table.ts
54952
- var import_node_fs29 = __toESM(require("fs"), 1);
54953
- var import_node_path30 = __toESM(require("path"), 1);
56217
+ var import_node_fs32 = __toESM(require("fs"), 1);
56218
+ var import_node_path33 = __toESM(require("path"), 1);
54954
56219
  var import_node_url5 = require("url");
54955
56220
  var import_meta5 = {};
54956
56221
  var LOCAL_SECRETS_DIRNAME = "secrets";
54957
56222
  function findSecretsBundleRoot(logger) {
54958
56223
  const candidates = [];
54959
56224
  try {
54960
- const here = import_node_path30.default.dirname((0, import_node_url5.fileURLToPath)(import_meta5.url));
54961
- candidates.push(import_node_path30.default.resolve(here, "..", "secrets"));
56225
+ const here = import_node_path33.default.dirname((0, import_node_url5.fileURLToPath)(import_meta5.url));
56226
+ candidates.push(import_node_path33.default.resolve(here, "..", "secrets"));
54962
56227
  } catch {
54963
56228
  }
54964
56229
  const fromArgv = bundleSiblingFromArgv(process.argv[1], "secrets");
54965
56230
  if (fromArgv) candidates.push(fromArgv);
54966
56231
  for (const c2 of candidates) {
54967
56232
  try {
54968
- if (import_node_fs29.default.statSync(c2).isDirectory()) return c2;
56233
+ if (import_node_fs32.default.statSync(c2).isDirectory()) return c2;
54969
56234
  } catch {
54970
56235
  }
54971
56236
  }
@@ -54991,7 +56256,7 @@ function readDir(dir, logger) {
54991
56256
  const out = {};
54992
56257
  let files;
54993
56258
  try {
54994
- files = import_node_fs29.default.readdirSync(dir).filter((f2) => f2.endsWith(".env")).sort();
56259
+ files = import_node_fs32.default.readdirSync(dir).filter((f2) => f2.endsWith(".env")).sort();
54995
56260
  } catch (e) {
54996
56261
  const code = e.code;
54997
56262
  if (code !== "ENOENT") logger?.warn("secrets.dir-unreadable", { dir, code: code ?? "unknown" });
@@ -54999,19 +56264,19 @@ function readDir(dir, logger) {
54999
56264
  }
55000
56265
  for (const f2 of files) {
55001
56266
  try {
55002
- Object.assign(out, parseEnvFile(import_node_fs29.default.readFileSync(import_node_path30.default.join(dir, f2), "utf8")));
56267
+ Object.assign(out, parseEnvFile(import_node_fs32.default.readFileSync(import_node_path33.default.join(dir, f2), "utf8")));
55003
56268
  } catch (e) {
55004
- logger?.warn("secrets.file-unreadable", { file: import_node_path30.default.join(dir, f2), error: e.message });
56269
+ logger?.warn("secrets.file-unreadable", { file: import_node_path33.default.join(dir, f2), error: e.message });
55005
56270
  }
55006
56271
  }
55007
56272
  return out;
55008
56273
  }
55009
56274
  function upsertEnvFileVar(file, key, value) {
55010
56275
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`invalid env name: ${key}`);
55011
- import_node_fs29.default.mkdirSync(import_node_path30.default.dirname(file), { recursive: true, mode: 448 });
56276
+ import_node_fs32.default.mkdirSync(import_node_path33.default.dirname(file), { recursive: true, mode: 448 });
55012
56277
  let lines = [];
55013
56278
  try {
55014
- lines = import_node_fs29.default.readFileSync(file, "utf8").split("\n");
56279
+ lines = import_node_fs32.default.readFileSync(file, "utf8").split("\n");
55015
56280
  if (lines[lines.length - 1] === "") lines.pop();
55016
56281
  } catch {
55017
56282
  }
@@ -55019,12 +56284,12 @@ function upsertEnvFileVar(file, key, value) {
55019
56284
  let replaced = false;
55020
56285
  const out = lines.map((l2) => re2.test(l2) ? (replaced = true, `${key}=${value}`) : l2);
55021
56286
  if (!replaced) out.push(`${key}=${value}`);
55022
- import_node_fs29.default.writeFileSync(file, `${out.join("\n")}
56287
+ import_node_fs32.default.writeFileSync(file, `${out.join("\n")}
55023
56288
  `, { encoding: "utf8", mode: 384 });
55024
56289
  }
55025
56290
  function readEnvFileVar(file, key) {
55026
56291
  try {
55027
- return parseEnvFile(import_node_fs29.default.readFileSync(file, "utf8"))[key];
56292
+ return parseEnvFile(import_node_fs32.default.readFileSync(file, "utf8"))[key];
55028
56293
  } catch {
55029
56294
  return void 0;
55030
56295
  }
@@ -55033,7 +56298,7 @@ function removeEnvFileVar(file, key) {
55033
56298
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`invalid env name: ${key}`);
55034
56299
  let lines;
55035
56300
  try {
55036
- lines = import_node_fs29.default.readFileSync(file, "utf8").split("\n");
56301
+ lines = import_node_fs32.default.readFileSync(file, "utf8").split("\n");
55037
56302
  } catch {
55038
56303
  return;
55039
56304
  }
@@ -55042,10 +56307,10 @@ function removeEnvFileVar(file, key) {
55042
56307
  const out = lines.filter((l2) => !re2.test(l2));
55043
56308
  if (out.length === lines.length) return;
55044
56309
  if (out.every((l2) => l2.trim() === "" || l2.trim().startsWith("#"))) {
55045
- import_node_fs29.default.rmSync(file, { force: true });
56310
+ import_node_fs32.default.rmSync(file, { force: true });
55046
56311
  return;
55047
56312
  }
55048
- import_node_fs29.default.writeFileSync(file, `${out.join("\n")}
56313
+ import_node_fs32.default.writeFileSync(file, `${out.join("\n")}
55049
56314
  `, { encoding: "utf8", mode: 384 });
55050
56315
  }
55051
56316
  function readSecretTable(args) {
@@ -55056,12 +56321,12 @@ function readSecretTable(args) {
55056
56321
  }
55057
56322
 
55058
56323
  // src/dispatch/source-transcript.ts
55059
- var import_node_path31 = __toESM(require("path"), 1);
56324
+ var import_node_path34 = __toESM(require("path"), 1);
55060
56325
  init_claude_history();
55061
56326
  var NO_SOURCE_TRANSCRIPT = "(no transcript yet \u2014 operate from the task description alone)";
55062
56327
  function resolveSourceJsonlPath(sourceFile, home) {
55063
56328
  if (!sourceFile?.toolSessionId) return NO_SOURCE_TRANSCRIPT;
55064
- return import_node_path31.default.join(
56329
+ return import_node_path34.default.join(
55065
56330
  home,
55066
56331
  ".claude",
55067
56332
  "projects",
@@ -55072,7 +56337,7 @@ function resolveSourceJsonlPath(sourceFile, home) {
55072
56337
 
55073
56338
  // src/shift/store.ts
55074
56339
  var import_promises3 = __toESM(require("fs/promises"), 1);
55075
- var import_node_path32 = __toESM(require("path"), 1);
56340
+ var import_node_path35 = __toESM(require("path"), 1);
55076
56341
  var import_node_crypto8 = require("crypto");
55077
56342
 
55078
56343
  // src/shift/schedule.ts
@@ -55147,7 +56412,7 @@ function createShiftStore(deps) {
55147
56412
  flushTimer = null;
55148
56413
  }
55149
56414
  const content = { version: 1, shifts };
55150
- await import_promises3.default.mkdir(import_node_path32.default.dirname(deps.filePath), { recursive: true });
56415
+ await import_promises3.default.mkdir(import_node_path35.default.dirname(deps.filePath), { recursive: true });
55151
56416
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
55152
56417
  await import_promises3.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
55153
56418
  await import_promises3.default.rename(tmp, deps.filePath);
@@ -55601,14 +56866,14 @@ function redactEvidence(evidence) {
55601
56866
 
55602
56867
  // src/deploy/deploy-file.ts
55603
56868
  var import_promises4 = __toESM(require("fs/promises"), 1);
55604
- var import_node_path34 = __toESM(require("path"), 1);
56869
+ var import_node_path37 = __toESM(require("path"), 1);
55605
56870
  var import_node_crypto9 = require("crypto");
55606
56871
  init_zod();
55607
56872
  init_protocol();
55608
56873
 
55609
56874
  // src/deploy/connectors.ts
55610
- var import_node_fs30 = __toESM(require("fs"), 1);
55611
- var import_node_path33 = __toESM(require("path"), 1);
56875
+ var import_node_fs33 = __toESM(require("fs"), 1);
56876
+ var import_node_path36 = __toESM(require("path"), 1);
55612
56877
  init_zod();
55613
56878
  init_protocol();
55614
56879
  var PLACEHOLDER2 = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -55658,11 +56923,11 @@ var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "uvx", "pnpx", "bunx"]);
55658
56923
  var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["node", "bun", "deno", "python", "python3"]);
55659
56924
  var HOST_ARG_PLACEHOLDER = "CLAWD_PERSONA_DIR";
55660
56925
  function scanMcpConnectors(personaDir) {
55661
- const file = import_node_path33.default.join(personaDir, ".mcp.json");
56926
+ const file = import_node_path36.default.join(personaDir, ".mcp.json");
55662
56927
  const out = { readable: true, connectors: [], evidence: [], warnings: [] };
55663
56928
  let parsed;
55664
56929
  try {
55665
- parsed = JSON.parse(import_node_fs30.default.readFileSync(file, "utf8"));
56930
+ parsed = JSON.parse(import_node_fs33.default.readFileSync(file, "utf8"));
55666
56931
  } catch (e) {
55667
56932
  if (e.code === "ENOENT") return out;
55668
56933
  out.readable = false;
@@ -55757,8 +57022,8 @@ function mergeScannedConnectors(existing, scanned) {
55757
57022
  }
55758
57023
 
55759
57024
  // src/deploy/deploy-file.ts
55760
- var DEPLOY_FILE = import_node_path34.default.join(".clawd", "deploy.json");
55761
- var SCAN_FILE = import_node_path34.default.join(".clawd", "deploy-scan.json");
57025
+ var DEPLOY_FILE = import_node_path37.default.join(".clawd", "deploy.json");
57026
+ var SCAN_FILE = import_node_path37.default.join(".clawd", "deploy-scan.json");
55762
57027
  async function readJson(file) {
55763
57028
  let raw;
55764
57029
  try {
@@ -55774,14 +57039,14 @@ async function readJson(file) {
55774
57039
  }
55775
57040
  }
55776
57041
  async function writeJson(file, value) {
55777
- await import_promises4.default.mkdir(import_node_path34.default.dirname(file), { recursive: true });
57042
+ await import_promises4.default.mkdir(import_node_path37.default.dirname(file), { recursive: true });
55778
57043
  const tmp = `${file}.tmp-${process.pid}`;
55779
57044
  await import_promises4.default.writeFile(tmp, `${JSON.stringify(value, null, 2)}
55780
57045
  `);
55781
57046
  await import_promises4.default.rename(tmp, file);
55782
57047
  }
55783
57048
  async function readDeployFile(dir) {
55784
- const raw = await readJson(import_node_path34.default.join(dir, DEPLOY_FILE));
57049
+ const raw = await readJson(import_node_path37.default.join(dir, DEPLOY_FILE));
55785
57050
  if (raw === void 0) return void 0;
55786
57051
  const parsed = DeployFileSchema.safeParse(raw);
55787
57052
  if (!parsed.success) {
@@ -55791,18 +57056,18 @@ async function readDeployFile(dir) {
55791
57056
  return parsed.data;
55792
57057
  }
55793
57058
  async function writeDeployFile(dir, file) {
55794
- await writeJson(import_node_path34.default.join(dir, DEPLOY_FILE), DeployFileSchema.parse(file));
57059
+ await writeJson(import_node_path37.default.join(dir, DEPLOY_FILE), DeployFileSchema.parse(file));
55795
57060
  }
55796
57061
  async function readDeployFileRaw(dir) {
55797
57062
  try {
55798
- return await import_promises4.default.readFile(import_node_path34.default.join(dir, DEPLOY_FILE), "utf8");
57063
+ return await import_promises4.default.readFile(import_node_path37.default.join(dir, DEPLOY_FILE), "utf8");
55799
57064
  } catch (err) {
55800
57065
  if (err.code === "ENOENT") return void 0;
55801
57066
  throw err;
55802
57067
  }
55803
57068
  }
55804
57069
  async function restoreDeployFileRaw(dir, raw) {
55805
- const file = import_node_path34.default.join(dir, DEPLOY_FILE);
57070
+ const file = import_node_path37.default.join(dir, DEPLOY_FILE);
55806
57071
  if (raw === void 0) await import_promises4.default.rm(file, { force: true });
55807
57072
  else await import_promises4.default.writeFile(file, raw);
55808
57073
  }
@@ -55843,7 +57108,7 @@ function normalizeLegacyScan(raw) {
55843
57108
  return fix(raw);
55844
57109
  }
55845
57110
  async function readScanFile(dir) {
55846
- const raw = normalizeLegacyScan(await readJson(import_node_path34.default.join(dir, SCAN_FILE)));
57111
+ const raw = normalizeLegacyScan(await readJson(import_node_path37.default.join(dir, SCAN_FILE)));
55847
57112
  if (raw === void 0) return void 0;
55848
57113
  const badShape = (issues) => {
55849
57114
  const first = issues[0];
@@ -55864,7 +57129,7 @@ async function readScanFile(dir) {
55864
57129
  return parsed.data;
55865
57130
  }
55866
57131
  async function writeScanFile(dir, file) {
55867
- await writeJson(import_node_path34.default.join(dir, SCAN_FILE), DeployScanFileSchema.parse(file));
57132
+ await writeJson(import_node_path37.default.join(dir, SCAN_FILE), DeployScanFileSchema.parse(file));
55868
57133
  }
55869
57134
  function replaceScan(file, entry) {
55870
57135
  const idx = file.scans.findIndex((s2) => s2.id === entry.id);
@@ -55947,9 +57212,9 @@ function requiredEnvNames(manifest) {
55947
57212
 
55948
57213
  // src/deploy/artifact.ts
55949
57214
  var import_node_crypto10 = require("crypto");
55950
- var import_node_fs31 = __toESM(require("fs"), 1);
55951
- var import_node_os10 = __toESM(require("os"), 1);
55952
- var import_node_path35 = __toESM(require("path"), 1);
57215
+ var import_node_fs34 = __toESM(require("fs"), 1);
57216
+ var import_node_os11 = __toESM(require("os"), 1);
57217
+ var import_node_path38 = __toESM(require("path"), 1);
55953
57218
  var import_node_stream3 = require("stream");
55954
57219
  var import_promises5 = require("stream/promises");
55955
57220
  var ARTIFACT_MAX_BYTES = 200 * 1024 * 1024;
@@ -55983,8 +57248,8 @@ function downloadArtifact(exec) {
55983
57248
  if (!res.ok || !res.body) throw new Error(`\u4E0B\u8F7D\u5931\u8D25 HTTP ${res.status}\uFF1A${url}`);
55984
57249
  const declared = Number(res.headers.get("content-length") ?? 0);
55985
57250
  if (declared > ARTIFACT_MAX_BYTES) throw new Error(`\u8D44\u4EA7 ${declared} \u5B57\u8282\uFF0C\u8D85\u8FC7\u4E0A\u9650 ${ARTIFACT_MAX_BYTES}\uFF1A${url}`);
55986
- const dir = await import_node_fs31.default.promises.mkdtemp(import_node_path35.default.join(import_node_os10.default.tmpdir(), "clawd-artifact-"));
55987
- const file = import_node_path35.default.join(dir, "pkg");
57251
+ const dir = await import_node_fs34.default.promises.mkdtemp(import_node_path38.default.join(import_node_os11.default.tmpdir(), "clawd-artifact-"));
57252
+ const file = import_node_path38.default.join(dir, "pkg");
55988
57253
  const hash = (0, import_node_crypto10.createHash)("sha256");
55989
57254
  let size = 0;
55990
57255
  const meter = new import_node_stream3.Transform({
@@ -55996,7 +57261,7 @@ function downloadArtifact(exec) {
55996
57261
  }
55997
57262
  });
55998
57263
  try {
55999
- await (0, import_promises5.pipeline)(import_node_stream3.Readable.fromWeb(res.body), meter, import_node_fs31.default.createWriteStream(file));
57264
+ await (0, import_promises5.pipeline)(import_node_stream3.Readable.fromWeb(res.body), meter, import_node_fs34.default.createWriteStream(file));
56000
57265
  let entries;
56001
57266
  if (listEntries) {
56002
57267
  const r2 = await exec("tar", ["-tzf", file], { timeoutMs: 3e4 });
@@ -56005,7 +57270,7 @@ function downloadArtifact(exec) {
56005
57270
  }
56006
57271
  return { sha256: hash.digest("hex"), size, ...entries ? { entries } : {} };
56007
57272
  } finally {
56008
- await import_node_fs31.default.promises.rm(dir, { recursive: true, force: true });
57273
+ await import_node_fs34.default.promises.rm(dir, { recursive: true, force: true });
56009
57274
  }
56010
57275
  };
56011
57276
  }
@@ -56056,7 +57321,7 @@ init_protocol();
56056
57321
 
56057
57322
  // src/deploy/repo.ts
56058
57323
  var import_promises6 = __toESM(require("fs/promises"), 1);
56059
- var import_node_path36 = __toESM(require("path"), 1);
57324
+ var import_node_path39 = __toESM(require("path"), 1);
56060
57325
  init_protocol();
56061
57326
 
56062
57327
  // ../node_modules/.pnpm/libsodium@0.8.4/node_modules/libsodium/dist/modules-esm/libsodium.mjs
@@ -56076,13 +57341,13 @@ async function A2(A4 = {}) {
56076
57341
  (function() {
56077
57342
  }).bind();
56078
57343
  var E3, i2, D3, F3, o2, w3, h2, S3, y3, k3, c2, M3 = function() {
56079
- }.bind(), U2 = false, G2 = false;
57344
+ }.bind(), U3 = false, G2 = false;
56080
57345
  function J2() {
56081
57346
  var A5 = d2.buffer;
56082
57347
  h2 = new Int8Array(A5), o2 = new Int16Array(A5), g3.HEAPU8 = c2 = new Uint8Array(A5), new Uint16Array(A5), w3 = new Int32Array(A5), k3 = new Uint32Array(A5), S3 = new Float32Array(A5), y3 = new Float64Array(A5);
56083
57348
  }
56084
57349
  function Y2(A5) {
56085
- g3.onAbort?.(A5), M3(A5 = `Aborted(${A5})`), U2 = true, A5 += ". Build with -sASSERTIONS for more info.";
57350
+ g3.onAbort?.(A5), M3(A5 = `Aborted(${A5})`), U3 = true, A5 += ". Build with -sASSERTIONS for more info.";
56086
57351
  var I4 = new WebAssembly.RuntimeError(A5);
56087
57352
  throw D3?.(I4), I4;
56088
57353
  }
@@ -56240,7 +57505,7 @@ async function A2(A4 = {}) {
56240
57505
  })(0, F3, C3)).instance));
56241
57506
  })(), (function() {
56242
57507
  function A5() {
56243
- g3.calledRun = true, U2 || (G2 = true, i2?.(g3), g3.onRuntimeInitialized?.(), (function() {
57508
+ g3.calledRun = true, U3 || (G2 = true, i2?.(g3), g3.onRuntimeInitialized?.(), (function() {
56244
57509
  if (g3.postRun) for ("function" == typeof g3.postRun && (g3.postRun = [g3.postRun]); g3.postRun.length; ) H2(g3.postRun.shift());
56245
57510
  N2(K2);
56246
57511
  })());
@@ -59596,6 +60861,9 @@ function createGithubApi(args) {
59596
60861
  },
59597
60862
  async rerunJob(slug, jobId) {
59598
60863
  await must("POST", `/repos/${slug}/actions/jobs/${jobId}/rerun`, "\u91CD\u8DD1\u8FD9\u4E00\u6BB5");
60864
+ },
60865
+ async dispatchWorkflow(slug, file, ref) {
60866
+ await must("POST", `/repos/${slug}/actions/workflows/${encodeURIComponent(file)}/dispatches`, "\u89E6\u53D1\u6D41\u6C34\u7EBF", { ref });
59599
60867
  }
59600
60868
  };
59601
60869
  }
@@ -59663,7 +60931,10 @@ async function storeGithubAuth(args) {
59663
60931
  init_protocol();
59664
60932
  var CLAWOS_REPO = "ottin4ttc/clawos";
59665
60933
  var CLAWOS_REF = "release";
59666
- var WORKFLOW_PATH = ".github/workflows/clawd-deploy.yml";
60934
+ var DEPLOY_WORKFLOW_FILE = "clawd-deploy.yml";
60935
+ var WORKFLOW_PATH = `.github/workflows/${DEPLOY_WORKFLOW_FILE}`;
60936
+ var UNPUBLISH_WORKFLOW_FILE = "clawd-unpublish.yml";
60937
+ var UNPUBLISH_WORKFLOW_PATH = `.github/workflows/${UNPUBLISH_WORKFLOW_FILE}`;
59667
60938
  var DOCKERFILE_PATH = "Dockerfile";
59668
60939
  var DOCKERIGNORE_PATH = ".dockerignore";
59669
60940
  var ACR_REGISTRY = "registry.cn-hangzhou.aliyuncs.com";
@@ -59757,6 +61028,7 @@ var ACR_LOGIN_STEPS = [
59757
61028
  ];
59758
61029
  var IMAGE_LATEST = '"$REGISTRY/$ACR_NAMESPACE/${{ steps.plan.outputs.persona_id }}:latest"';
59759
61030
  var RESOLVE_LINE = ` IMAGE_REF=$(node clawos/cloud-host/pipeline/acr.mjs resolve --image ${IMAGE_LATEST} --vpc-registry "$VPC_REGISTRY")`;
61031
+ var CONCURRENCY_GROUP = " group: persona-cloud-${{ github.repository }}";
59760
61032
  var L2 = [
59761
61033
  "name: clawd deploy",
59762
61034
  "",
@@ -59777,7 +61049,7 @@ var L2 = [
59777
61049
  "",
59778
61050
  "concurrency:",
59779
61051
  " # \u540C\u4E00\u4E2A\u4ED3\u5E93\u7684\u53D1\u5E03\u6392\u961F\u8DD1\uFF0C\u4E0D\u53D6\u6D88\uFF1A\u4E0A\u4E00\u6B21\u53EF\u80FD\u5DF2\u7ECF\u5728\u767B\u8BB0\u6B63\u5F0F\u4E86",
59780
- " group: persona-cloud-${{ github.repository }}",
61052
+ CONCURRENCY_GROUP,
59781
61053
  "",
59782
61054
  "env:",
59783
61055
  ` REGISTRY: ${ACR_REGISTRY}`,
@@ -59913,6 +61185,44 @@ var L2 = [
59913
61185
  ""
59914
61186
  ];
59915
61187
  var WORKFLOW_TEMPLATE = L2.join("\n");
61188
+ var U2 = [
61189
+ "name: clawd unpublish",
61190
+ "",
61191
+ "# \u7531 clawd \u751F\u6210\u3001\u7531 clawd \u7EF4\u62A4\uFF1A\u6BCF\u6B21\u300C\u53D1\u5E03\u300D\u90FD\u4F1A\u7528\u5F53\u524D\u6A21\u677F\u91CD\u5199\u8FD9\u4EFD\u6587\u4EF6\u518D\u63D0\u4EA4\uFF0C\u624B\u6539\u4F1A\u88AB\u76D6\u6389\u3002",
61192
+ "# \u4E0B\u7EBF\uFF1A\u5728 gateway \u64A4\u6389\u8FD9\u4E2A persona \u7684\u6B63\u5F0F\u767B\u8BB0\u2014\u2014\u522B\u4EBA\u770B\u4E0D\u5230\u3001\u7528\u4E0D\u4E86\uFF1B\u4ED3\u5E93\u548C\u955C\u50CF\u90FD\u4E0D\u52A8\uFF0C\u91CD\u65B0\u53D1\u5E03\u5C31\u56DE\u6765\u3002",
61193
+ "# \u53EA\u7531 clawd \u4E0A\u4E91\u9875\u7684\u300C\u4E0B\u7EBF\u300D\u89E6\u53D1\uFF08workflow_dispatch\uFF09\uFF0C\u63A8\u4EE3\u7801\u4E0D\u8DD1\u3002\u6B63\u5728\u8DD1\u7684\u5BF9\u8BDD\u4E0D\u6253\u65AD\uFF0C\u8DD1\u5230\u7A7A\u95F2\u81EA\u5DF1\u9000\u3002",
61194
+ "#",
61195
+ "# \u8981\u7684\u7EC4\u7EC7\u7EA7 Secrets\uFF1ACLAWOS_READ_TOKEN\uFF08\u8BFB clawos \u4ED3\u5E93\uFF09\u3001CLAWD_GATEWAY_ADMIN_KEY\uFF1B\u7EC4\u7EC7\u7EA7 Variable\uFF1ACLAWD_GATEWAY_URL\u3002",
61196
+ "",
61197
+ "on:",
61198
+ " workflow_dispatch:",
61199
+ "",
61200
+ "concurrency:",
61201
+ " # \u4E0E\u53D1\u5E03\u540C\u4E00\u7EC4\uFF1A\u53D1\u5E03\u5728\u8DD1\u65F6\u70B9\u4E0B\u7EBF\uFF0C\u6392\u5728\u5B83\u540E\u9762\uFF1B\u540E\u70B9\u7684\u9876\u6389\u5148\u70B9\u7684",
61202
+ CONCURRENCY_GROUP,
61203
+ "",
61204
+ "env:",
61205
+ ` CLAWOS_REPO: ${CLAWOS_REPO}`,
61206
+ ` CLAWOS_REF: ${CLAWOS_REF}`,
61207
+ "",
61208
+ "permissions:",
61209
+ " contents: read",
61210
+ "",
61211
+ "jobs:",
61212
+ " unpublish:",
61213
+ " name: unpublish",
61214
+ " runs-on: ubuntu-latest",
61215
+ " timeout-minutes: 10",
61216
+ " steps:",
61217
+ ...CONTRACT_STEPS,
61218
+ " - name: \u64A4\u767B\u8BB0",
61219
+ " env:",
61220
+ " CLAWD_GATEWAY_URL: ${{ vars.CLAWD_GATEWAY_URL }}",
61221
+ " CLAWD_GATEWAY_ADMIN_KEY: ${{ secrets.CLAWD_GATEWAY_ADMIN_KEY }}",
61222
+ " run: node clawos/cloud-host/pipeline/unregister.mjs --persona-id '${{ steps.plan.outputs.persona_id }}'",
61223
+ ""
61224
+ ];
61225
+ var UNPUBLISH_WORKFLOW_TEMPLATE = U2.join("\n");
59916
61226
 
59917
61227
  // src/deploy/exec.ts
59918
61228
  var import_node_child_process9 = require("child_process");
@@ -59953,7 +61263,7 @@ var runCommand = (cmd, args, opts = {}) => new Promise((resolve9) => {
59953
61263
  });
59954
61264
 
59955
61265
  // src/deploy/repo.ts
59956
- var DEPLOY_FILE_REL = DEPLOY_FILE.split(import_node_path36.default.sep).join("/");
61266
+ var DEPLOY_FILE_REL = DEPLOY_FILE.split(import_node_path39.default.sep).join("/");
59957
61267
  var PERSONA_FILE_REL = ".clawd/persona.json";
59958
61268
  var DIRTY_IGNORED = [DEPLOY_FILE_REL, PERSONA_FILE_REL];
59959
61269
  var RepoError = class extends Error {
@@ -59968,6 +61278,7 @@ async function mustGit(exec, dir, args, what, opts) {
59968
61278
  if (r2.code !== 0) throw new RepoError(`${what}\u6CA1\u6210\uFF08git \u9000\u51FA\u7801 ${r2.code}\uFF09\uFF1A${tail(r2.stderr) || tail(r2.stdout)}`);
59969
61279
  return r2.stdout.trim();
59970
61280
  }
61281
+ var cloudPersonaIdOf = (originUrl) => personaIdOf(repoSlug(originUrl).split("/").pop() ?? "");
59971
61282
  var INLINE_HELPER = '!f() { echo username=x-access-token; echo "password=$CLAWD_GITHUB_TOKEN"; }; f';
59972
61283
  async function gitPush(exec, dir, auth) {
59973
61284
  if (auth.source === "file") {
@@ -59992,7 +61303,7 @@ async function samePath(a2, b3) {
59992
61303
  try {
59993
61304
  return await import_promises6.default.realpath(x2);
59994
61305
  } catch {
59995
- return import_node_path36.default.resolve(x2);
61306
+ return import_node_path39.default.resolve(x2);
59996
61307
  }
59997
61308
  };
59998
61309
  return await real(a2) === await real(b3);
@@ -60018,7 +61329,7 @@ async function identityArgs(exec, dir) {
60018
61329
  return ["-c", "user.name=clawd", "-c", "user.email=clawd@clawos.local"];
60019
61330
  }
60020
61331
  async function writeGitignore(dir) {
60021
- const gi = import_node_path36.default.join(dir, ".gitignore");
61332
+ const gi = import_node_path39.default.join(dir, ".gitignore");
60022
61333
  let existing;
60023
61334
  try {
60024
61335
  existing = await import_promises6.default.readFile(gi, "utf8");
@@ -60028,13 +61339,15 @@ async function writeGitignore(dir) {
60028
61339
  await import_promises6.default.writeFile(gi, mergeGitignore(existing));
60029
61340
  }
60030
61341
  async function writeWorkflow(dir) {
60031
- const wf = import_node_path36.default.join(dir, WORKFLOW_PATH);
60032
- await import_promises6.default.mkdir(import_node_path36.default.dirname(wf), { recursive: true });
60033
- await import_promises6.default.writeFile(wf, WORKFLOW_TEMPLATE);
61342
+ for (const [rel, body] of [[WORKFLOW_PATH, WORKFLOW_TEMPLATE], [UNPUBLISH_WORKFLOW_PATH, UNPUBLISH_WORKFLOW_TEMPLATE]]) {
61343
+ const wf = import_node_path39.default.join(dir, rel);
61344
+ await import_promises6.default.mkdir(import_node_path39.default.dirname(wf), { recursive: true });
61345
+ await import_promises6.default.writeFile(wf, body);
61346
+ }
60034
61347
  }
60035
61348
  async function writeDockerfile(dir, personaId2) {
60036
- await import_promises6.default.writeFile(import_node_path36.default.join(dir, DOCKERFILE_PATH), renderDockerfile(personaId2));
60037
- await import_promises6.default.writeFile(import_node_path36.default.join(dir, DOCKERIGNORE_PATH), DOCKERIGNORE_TEMPLATE);
61349
+ await import_promises6.default.writeFile(import_node_path39.default.join(dir, DOCKERFILE_PATH), renderDockerfile(personaId2));
61350
+ await import_promises6.default.writeFile(import_node_path39.default.join(dir, DOCKERIGNORE_PATH), DOCKERIGNORE_TEMPLATE);
60038
61351
  }
60039
61352
  var RepoAlreadyExistsError = class extends RepoError {
60040
61353
  constructor(slug) {
@@ -60071,7 +61384,7 @@ async function ensureRepo(exec, api, args) {
60071
61384
  const existing = await repoInfo(exec, dir);
60072
61385
  if (existing) return { created: false, url: existing.url };
60073
61386
  await writeGitignore(dir);
60074
- await import_promises6.default.rm(import_node_path36.default.join(dir, WORKFLOW_PATH), { force: true });
61387
+ await import_promises6.default.rm(import_node_path39.default.join(dir, WORKFLOW_PATH), { force: true });
60075
61388
  let created;
60076
61389
  try {
60077
61390
  created = await createOrgRepo(exec, api, { dir, org, repoName: personaId2, message: "clawd: \u4E0A\u4E91", auth: args.auth });
@@ -60109,6 +61422,10 @@ async function hasCommit(exec, dir, sha) {
60109
61422
  const r2 = await git(exec, dir, ["cat-file", "-e", `${sha}^{commit}`]);
60110
61423
  return !r2.spawnError && r2.code === 0;
60111
61424
  }
61425
+ async function fileInCommit(exec, dir, sha, rel) {
61426
+ const r2 = await git(exec, dir, ["cat-file", "-e", `${sha}:${rel}`]);
61427
+ return !r2.spawnError && r2.code === 0;
61428
+ }
60112
61429
  async function commitAll(exec, args) {
60113
61430
  await mustGit(exec, args.dir, ["add", "-A"], "\u6682\u5B58\u6587\u4EF6");
60114
61431
  const staged = await git(exec, args.dir, ["diff", "--cached", "--quiet"]);
@@ -60124,7 +61441,7 @@ async function release(exec, api, args) {
60124
61441
  await api.enableActions(repoSlug(info.url));
60125
61442
  await writeGitignore(args.dir);
60126
61443
  await writeWorkflow(args.dir);
60127
- await writeDockerfile(args.dir, personaIdOf(repoSlug(info.url).split("/").pop() ?? ""));
61444
+ await writeDockerfile(args.dir, cloudPersonaIdOf(info.url));
60128
61445
  const identity = await identityArgs(exec, args.dir);
60129
61446
  await mustGit(exec, args.dir, ["add", "-A"], "\u6682\u5B58\u6587\u4EF6");
60130
61447
  const stamp = new Date(args.now()).toISOString();
@@ -60147,6 +61464,7 @@ var SCAN_BASE_LABEL = {
60147
61464
 
60148
61465
  // src/deploy/run-status.ts
60149
61466
  init_protocol();
61467
+ var runsOf = (slug, file, query) => `/repos/${slug}/actions/workflows/${encodeURIComponent(file)}/runs?${query}`;
60150
61468
  var pipelineJobKey = (name) => DEPLOY_STAGE_KEYS.find((k3) => name === k3 || name.startsWith(`${k3} `));
60151
61469
  function stageStatusOf(status, conclusion) {
60152
61470
  if (status === "queued" || status === "requested" || status === "pending") return "pending";
@@ -60188,7 +61506,7 @@ async function runByHead(api, slug, head) {
60188
61506
  return fetchRun(api, slug, `head_sha=${encodeURIComponent(head)}&per_page=1`);
60189
61507
  }
60190
61508
  async function fetchRun(api, slug, query) {
60191
- const listed = await api.getJson(`/repos/${slug}/actions/runs?${query}`);
61509
+ const listed = await api.getJson(runsOf(slug, DEPLOY_WORKFLOW_FILE, query));
60192
61510
  const raw = listed?.workflow_runs?.[0];
60193
61511
  if (!raw || typeof raw.id !== "number" || typeof raw.head_sha !== "string" || typeof raw.html_url !== "string") {
60194
61512
  return void 0;
@@ -60210,6 +61528,21 @@ async function fetchRun(api, slug, query) {
60210
61528
  jobs: DEPLOY_STAGE_KEYS.map((k3) => byKey.get(k3)).filter((j2) => j2 !== void 0)
60211
61529
  };
60212
61530
  }
61531
+ async function latestUnpublish(api, slug) {
61532
+ const listed = await api.getJson(runsOf(slug, UNPUBLISH_WORKFLOW_FILE, "per_page=1"));
61533
+ const runs = listed?.workflow_runs;
61534
+ if (!Array.isArray(runs)) return void 0;
61535
+ const raw = runs[0];
61536
+ if (!raw) return null;
61537
+ if (typeof raw.id !== "number" || typeof raw.html_url !== "string") return void 0;
61538
+ const status = runStatusOf(String(raw.status ?? ""));
61539
+ return {
61540
+ runId: raw.id,
61541
+ status,
61542
+ htmlUrl: raw.html_url,
61543
+ ...status === "completed" ? { conclusion: raw.conclusion === "success" ? "success" : "failure" } : {}
61544
+ };
61545
+ }
60213
61546
  async function rerunJob(api, slug, jobId) {
60214
61547
  await api.rerunJob(slug, jobId);
60215
61548
  }
@@ -60241,6 +61574,8 @@ function reconcileInterruptedScans(deps) {
60241
61574
  // src/handlers/deploy.ts
60242
61575
  var SECRETS_CACHE_MS = 2e4;
60243
61576
  var RUN_CACHE_MS = 4e3;
61577
+ var ONLINE_CACHE_MS = 2e4;
61578
+ var UNPUBLISH_NEEDS_REPUBLISH = "\u8FD9\u4E2A persona \u7684\u6D41\u6C34\u7EBF\u662F\u65E7\u7248\u7684\uFF0C\u5148\u91CD\u65B0\u626B\u63CF\u3001\u53D1\u5E03\u4E00\u6B21\u518D\u4E0B\u7EBF";
60244
61579
  function ensureOwner(ctx) {
60245
61580
  if (!ctx || ctx.principal.kind !== "owner") {
60246
61581
  throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: deploy:* requires owner ctx");
@@ -60301,6 +61636,28 @@ function buildDeployHandlers(deps) {
60301
61636
  runCache.set(key, { at: deps.now(), run });
60302
61637
  return run;
60303
61638
  };
61639
+ const unpublishCache = /* @__PURE__ */ new Map();
61640
+ const cachedUnpublish = async (personaId2, api, slug) => {
61641
+ const hit = unpublishCache.get(personaId2);
61642
+ if (hit && deps.now() - hit.at < RUN_CACHE_MS) return hit.run;
61643
+ const run = await latestUnpublish(api, slug) ?? void 0;
61644
+ unpublishCache.set(personaId2, { at: deps.now(), run });
61645
+ return run;
61646
+ };
61647
+ const onlineCache = /* @__PURE__ */ new Map();
61648
+ const cachedOnline = async (personaId2, run, unpublish) => {
61649
+ const stamp = `${run?.runId ?? "-"}:${run?.status ?? "-"}|${unpublish?.runId ?? "-"}:${unpublish?.status ?? "-"}`;
61650
+ const hit = onlineCache.get(personaId2);
61651
+ if (hit && hit.stamp === stamp && deps.now() - hit.at < ONLINE_CACHE_MS) return hit.online;
61652
+ let online;
61653
+ try {
61654
+ online = await deps.cloudOnline(personaId2);
61655
+ } catch {
61656
+ online = void 0;
61657
+ }
61658
+ onlineCache.set(personaId2, { stamp, at: deps.now(), online });
61659
+ return online;
61660
+ };
60304
61661
  deps.githubIdentity.onChange(() => secretsCache.clear());
60305
61662
  async function requireGithub() {
60306
61663
  const auth = await deps.githubIdentity.refresh();
@@ -60337,6 +61694,7 @@ function buildDeployHandlers(deps) {
60337
61694
  const { scans } = file;
60338
61695
  let secrets;
60339
61696
  let run;
61697
+ let unpublish;
60340
61698
  if (repo2 && api && github.status === "authorized") {
60341
61699
  try {
60342
61700
  secrets = await cachedSecrets(personaId2, dir, api);
@@ -60344,7 +61702,9 @@ function buildDeployHandlers(deps) {
60344
61702
  secrets = void 0;
60345
61703
  }
60346
61704
  run = await cachedRun(personaId2, api, repoSlug(repo2.url), releaseHead);
61705
+ if (releaseHead === void 0) unpublish = await cachedUnpublish(personaId2, api, repoSlug(repo2.url));
60347
61706
  }
61707
+ const online = releaseHead === void 0 && file.releases.length > 0 ? await cachedOnline(personaId2, run, unpublish) : void 0;
60348
61708
  return {
60349
61709
  personaId: personaId2,
60350
61710
  github,
@@ -60353,6 +61713,8 @@ function buildDeployHandlers(deps) {
60353
61713
  releases: file.releases,
60354
61714
  ...secrets ? { secrets } : {},
60355
61715
  ...run ? { run } : {},
61716
+ ...online !== void 0 ? { online } : {},
61717
+ ...unpublish ? { unpublish } : {},
60356
61718
  ...historyError ? { historyError } : {}
60357
61719
  };
60358
61720
  }
@@ -60562,6 +61924,14 @@ function buildDeployHandlers(deps) {
60562
61924
  repo2 = await repoInfo(deps.exec, dir);
60563
61925
  if (!repo2) throw new ClawdError(ERROR_CODES.INTERNAL, "\u4ED3\u5E93\u5EFA\u597D\u4E86\u4F46\u8BFB\u4E0D\u5230 origin");
60564
61926
  }
61927
+ const cloudName = cloudPersonaIdOf(repo2.url);
61928
+ let claim;
61929
+ try {
61930
+ claim = await deps.claimPublisher(cloudName);
61931
+ } catch (err) {
61932
+ throw toClawdError(err);
61933
+ }
61934
+ if (claim === "taken") throw invalid(`\u4E91\u4E0A\u7684 ${cloudName} \u767B\u8BB0\u5728\u522B\u4EBA\u540D\u4E0B\uFF0C\u770B\u677F\u53EA\u5F52\u539F\u53D1\u5E03\u8005\uFF1B\u8981\u4E00\u8D77\u7EF4\u62A4\u627E\u7BA1\u7406\u5458`);
60565
61935
  if (repo2.branch !== "main") throw invalid(`\u5F53\u524D\u5728\u5206\u652F ${repo2.branch}\uFF0C\u4ED3\u5E93\u91CC\u7684\u6D41\u6C34\u7EBF\u53EA\u8BA4 main\uFF1B\u5207\u56DE main \u518D\u626B\u63CF`);
60566
61936
  const live = liveScans.get(personaId2);
60567
61937
  if (live && live.sessionId === void 0) return view(personaId2);
@@ -60657,6 +62027,27 @@ function buildDeployHandlers(deps) {
60657
62027
  }
60658
62028
  return view(personaId2);
60659
62029
  };
62030
+ const unpublishHandler = async (frame, _client, ctx) => {
62031
+ ensureOwner(ctx);
62032
+ const { personaId: personaId2 } = parseArgs2(DeployPersonaArgsSchema, frame, "deploy:unpublish");
62033
+ const dir = mustPersona(personaId2);
62034
+ const repo2 = await repoInfo(deps.exec, dir);
62035
+ if (!repo2) throw invalid("\u8FD9\u4E2A persona \u8FD8\u6CA1\u4E0A\u8FC7\u4E91");
62036
+ const { api } = await requireGithub();
62037
+ const last = (await readHistory(dir)).releases.at(-1);
62038
+ if (!last || !await fileInCommit(deps.exec, dir, last.head, UNPUBLISH_WORKFLOW_PATH)) throw invalid(UNPUBLISH_NEEDS_REPUBLISH);
62039
+ const slug = repoSlug(repo2.url);
62040
+ const before = await latestUnpublish(api, slug);
62041
+ if (before === void 0) throw invalid("\u67E5\u4E0D\u5230\u8FD9\u4E2A\u4ED3\u5E93\u4E0A\u7684\u4E0B\u7EBF\u6D41\u6C34\u7EBF\uFF08GitHub \u6CA1\u54CD\u5E94\uFF09\uFF0C\u7A0D\u540E\u518D\u70B9\u4E00\u6B21");
62042
+ try {
62043
+ await api.dispatchWorkflow(slug, UNPUBLISH_WORKFLOW_FILE, "main");
62044
+ } catch (err) {
62045
+ throw new ClawdError(ERROR_CODES.INTERNAL, err instanceof Error ? err.message : String(err));
62046
+ }
62047
+ unpublishCache.delete(personaId2);
62048
+ onlineCache.delete(personaId2);
62049
+ return { response: { type: "deploy:unpublish", before: before?.runId ?? null } };
62050
+ };
60660
62051
  return {
60661
62052
  "deploy:get": get,
60662
62053
  "deploy:scan": scanHandler,
@@ -60665,7 +62056,8 @@ function buildDeployHandlers(deps) {
60665
62056
  "deploy:reportFinding": reportFinding,
60666
62057
  "deploy:finishScan": finishScan,
60667
62058
  "deploy:release": releaseHandler,
60668
- "deploy:rerunJob": rerunJobHandler
62059
+ "deploy:rerunJob": rerunJobHandler,
62060
+ "deploy:unpublish": unpublishHandler
60669
62061
  };
60670
62062
  }
60671
62063
 
@@ -60717,10 +62109,10 @@ async function probeLocalVersion(exec, pkg) {
60717
62109
 
60718
62110
  // src/tools/shell-path.ts
60719
62111
  var import_node_child_process10 = require("child_process");
60720
- var import_node_os11 = __toESM(require("os"), 1);
62112
+ var import_node_os12 = __toESM(require("os"), 1);
60721
62113
  function refreshPathFromLoginShell() {
60722
62114
  if (process.platform === "win32") return;
60723
- const shell = process.env.SHELL ?? import_node_os11.default.userInfo().shell ?? "/bin/zsh";
62115
+ const shell = process.env.SHELL ?? import_node_os12.default.userInfo().shell ?? "/bin/zsh";
60724
62116
  try {
60725
62117
  const out = (0, import_node_child_process10.execFileSync)(shell, ["-ilc", 'command echo -n "$PATH"'], {
60726
62118
  encoding: "utf8",
@@ -61266,15 +62658,15 @@ async function refreshTunnelUrlOnStartup(deps) {
61266
62658
  }
61267
62659
 
61268
62660
  // src/lark-bot-connector/binding-store.ts
61269
- var import_node_fs32 = __toESM(require("fs"), 1);
61270
- var import_node_path37 = __toESM(require("path"), 1);
62661
+ var import_node_fs35 = __toESM(require("fs"), 1);
62662
+ var import_node_path40 = __toESM(require("path"), 1);
61271
62663
  function createLarkBotBindingStore(personaRoot) {
61272
- const filePath = (personaId2) => import_node_path37.default.join(personaRoot, safeFileName(personaId2), ".clawd", "lark-bot.json");
62664
+ const filePath = (personaId2) => import_node_path40.default.join(personaRoot, safeFileName(personaId2), ".clawd", "lark-bot.json");
61273
62665
  const read = (personaId2) => {
61274
62666
  const file = filePath(personaId2);
61275
- if (!import_node_fs32.default.existsSync(file)) return null;
62667
+ if (!import_node_fs35.default.existsSync(file)) return null;
61276
62668
  try {
61277
- const raw = JSON.parse(import_node_fs32.default.readFileSync(file, "utf8"));
62669
+ const raw = JSON.parse(import_node_fs35.default.readFileSync(file, "utf8"));
61278
62670
  if (typeof raw?.appId !== "string" || raw.appId.length === 0) return null;
61279
62671
  return raw;
61280
62672
  } catch {
@@ -61283,16 +62675,16 @@ function createLarkBotBindingStore(personaRoot) {
61283
62675
  };
61284
62676
  const write = (personaId2, meta) => {
61285
62677
  const file = filePath(personaId2);
61286
- import_node_fs32.default.mkdirSync(import_node_path37.default.dirname(file), { recursive: true });
62678
+ import_node_fs35.default.mkdirSync(import_node_path40.default.dirname(file), { recursive: true });
61287
62679
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
61288
- import_node_fs32.default.writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
61289
- import_node_fs32.default.renameSync(tmp, file);
62680
+ import_node_fs35.default.writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
62681
+ import_node_fs35.default.renameSync(tmp, file);
61290
62682
  };
61291
62683
  return {
61292
62684
  read,
61293
62685
  write,
61294
62686
  remove(personaId2) {
61295
- import_node_fs32.default.rmSync(filePath(personaId2), { force: true });
62687
+ import_node_fs35.default.rmSync(filePath(personaId2), { force: true });
61296
62688
  },
61297
62689
  addRemovedChat(personaId2, chatId) {
61298
62690
  const meta = read(personaId2);
@@ -61444,8 +62836,8 @@ function createLarkBotCloudClient(opts) {
61444
62836
  }
61445
62837
 
61446
62838
  // src/lark-bot-connector/chat-router.ts
61447
- var import_node_fs33 = __toESM(require("fs"), 1);
61448
- var import_node_path38 = __toESM(require("path"), 1);
62839
+ var import_node_fs36 = __toESM(require("fs"), 1);
62840
+ var import_node_path41 = __toESM(require("path"), 1);
61449
62841
  init_protocol();
61450
62842
  var ERROR_REPLY_TEXT = "\u5904\u7406\u51FA\u9519\u4E86\uFF0C\u8BF7\u91CD\u8BD5";
61451
62843
  var LOCAL_TURN_RETRIES = 2;
@@ -61523,9 +62915,9 @@ function createLarkChatRouter(deps) {
61523
62915
  continue;
61524
62916
  }
61525
62917
  const dir = deps.mediaRootFor(env.chatId);
61526
- import_node_fs33.default.mkdirSync(dir, { recursive: true });
61527
- const p3 = import_node_path38.default.join(dir, `${safeMediaName(ownerMessageId)}-${i2}.${ext}`);
61528
- import_node_fs33.default.writeFileSync(p3, r2.data);
62918
+ import_node_fs36.default.mkdirSync(dir, { recursive: true });
62919
+ const p3 = import_node_path41.default.join(dir, `${safeMediaName(ownerMessageId)}-${i2}.${ext}`);
62920
+ import_node_fs36.default.writeFileSync(p3, r2.data);
61529
62921
  lines.push(`[\u56FE\u7247: ${p3}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
61530
62922
  } catch (err) {
61531
62923
  deps.logger?.warn(`larkBot image fetch failed ${key}: ${err.message}`);
@@ -61546,9 +62938,9 @@ function createLarkChatRouter(deps) {
61546
62938
  continue;
61547
62939
  }
61548
62940
  const dir = deps.mediaRootFor(env.chatId);
61549
- import_node_fs33.default.mkdirSync(dir, { recursive: true });
61550
- const p3 = import_node_path38.default.join(dir, `${safeMediaName(ownerMessageId)}-${i2}-${safeAttachmentName(f2.name)}`);
61551
- import_node_fs33.default.writeFileSync(p3, r2.data);
62941
+ import_node_fs36.default.mkdirSync(dir, { recursive: true });
62942
+ const p3 = import_node_path41.default.join(dir, `${safeMediaName(ownerMessageId)}-${i2}-${safeAttachmentName(f2.name)}`);
62943
+ import_node_fs36.default.writeFileSync(p3, r2.data);
61552
62944
  lines.push(`[\u6587\u4EF6: ${p3}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
61553
62945
  } catch (err) {
61554
62946
  deps.logger?.warn(`larkBot file fetch failed ${f2.key}: ${err.message}`);
@@ -62198,952 +63590,182 @@ function buildPersonaDispatchHandlers(deps) {
62198
63590
  "personaDispatch:get": get
62199
63591
  };
62200
63592
  }
62201
-
62202
- // src/dispatch/peer-forward.ts
62203
- init_protocol();
62204
- function wsUrlToHttp(url) {
62205
- if (url.startsWith("wss://")) return "https://" + url.slice("wss://".length);
62206
- if (url.startsWith("ws://")) return "http://" + url.slice("ws://".length);
62207
- return url;
62208
- }
62209
- var DISPATCH_POLL_INTERVAL_MS = 5e3;
62210
- var DISPATCH_POLL_UNREACHABLE_MS = 6e5;
62211
- var DISPATCH_POLL_TOTAL_MS = 864e5;
62212
- var defaultSleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
62213
- async function forwardDispatchToPeer(args) {
62214
- const f2 = args.fetchImpl ?? fetch;
62215
- const sleep2 = args.sleepImpl ?? defaultSleep;
62216
- const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
62217
- const url = `${base}/rpc/personaDispatch:run`;
62218
- let res;
62219
- try {
62220
- res = await f2(url, {
62221
- method: "POST",
62222
- headers: {
62223
- "content-type": "application/json",
62224
- authorization: `Bearer ${args.contact.connectToken}`
62225
- },
62226
- // 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
62227
- body: JSON.stringify({
62228
- targetPersona: args.targetPersona,
62229
- prompt: args.prompt,
62230
- // T-20: 条件展开防 undefined 污染 JSON。远端 daemon handler 会再校验一次 model 合法性。
62231
- ...args.model ? { model: args.model } : {}
62232
- })
62233
- });
62234
- } catch (err) {
62235
- const msg = err instanceof Error ? err.message : String(err);
62236
- return { kind: "failure", reason: `forward to peer failed: ${msg}` };
62237
- }
62238
- let json;
62239
- try {
62240
- json = await res.json();
62241
- } catch {
62242
- return {
62243
- kind: "failure",
62244
- reason: `peer returned non-JSON response (HTTP ${res.status})`
62245
- };
62246
- }
62247
- if (json.ok === false) {
62248
- return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
62249
- }
62250
- if ("outcome" in json.result) return json.result.outcome;
62251
- const dispatchId = json.result.dispatchId;
62252
- let elapsed = 0;
62253
- let unreachableFor = 0;
62254
- while (elapsed < DISPATCH_POLL_TOTAL_MS) {
62255
- await sleep2(DISPATCH_POLL_INTERVAL_MS);
62256
- elapsed += DISPATCH_POLL_INTERVAL_MS;
62257
- const markUnreachable = () => {
62258
- unreachableFor += DISPATCH_POLL_INTERVAL_MS;
62259
- return unreachableFor >= DISPATCH_POLL_UNREACHABLE_MS ? {
62260
- kind: "failure",
62261
- reason: "peer unreachable while polling dispatch outcome (TARGET_UNREACHABLE)"
62262
- } : null;
62263
- };
62264
- let getRes;
62265
- try {
62266
- getRes = await f2(`${base}/rpc/personaDispatch:get`, {
62267
- method: "POST",
62268
- headers: {
62269
- "content-type": "application/json",
62270
- authorization: `Bearer ${args.contact.connectToken}`
62271
- },
62272
- body: JSON.stringify({ dispatchId })
62273
- });
62274
- } catch {
62275
- const dead = markUnreachable();
62276
- if (dead) return dead;
62277
- continue;
62278
- }
62279
- let getJson;
62280
- try {
62281
- getJson = await getRes.json();
62282
- } catch {
62283
- const dead = markUnreachable();
62284
- if (dead) return dead;
62285
- continue;
62286
- }
62287
- if (getJson.ok === false) {
62288
- if (getJson.error === ERROR_CODES.DISPATCH_NOT_FOUND) {
62289
- return { kind: "failure", reason: `peer lost dispatch ${dispatchId} (daemon restarted?)` };
62290
- }
62291
- const dead = markUnreachable();
62292
- if (dead) return dead;
62293
- continue;
62294
- }
62295
- unreachableFor = 0;
62296
- const { status, outcome } = getJson.result;
62297
- if (status === "completed" || status === "failed") {
62298
- return outcome ?? { kind: "failure", reason: "peer returned terminal status without outcome" };
62299
- }
62300
- }
62301
- return { kind: "failure", reason: "dispatch polling exceeded 24h (TIMEOUT)" };
62302
- }
62303
- async function forwardInboxPostToPeer(args) {
62304
- const f2 = args.fetchImpl ?? fetch;
62305
- const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
62306
- const url = `${base}/rpc/inbox:postMessage`;
62307
- let res;
62308
- try {
62309
- res = await f2(url, {
62310
- method: "POST",
62311
- headers: {
62312
- "content-type": "application/json",
62313
- authorization: `Bearer ${args.contact.connectToken}`
62314
- },
62315
- body: JSON.stringify({
62316
- id: args.id,
62317
- text: args.text,
62318
- createdAt: args.createdAt,
62319
- ...args.origin ? { origin: args.origin } : {}
62320
- })
62321
- });
62322
- } catch (err) {
62323
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
62324
- }
62325
- let json = null;
62326
- try {
62327
- json = await res.json();
62328
- } catch {
62329
- return { ok: false, error: `peer non-JSON (HTTP ${res.status})` };
62330
- }
62331
- if (res.status < 200 || res.status >= 300 || json?.ok === false) {
62332
- return { ok: false, error: json?.error ?? `HTTP ${res.status}` };
62333
- }
62334
- return { ok: true };
62335
- }
62336
- async function forwardPeerExecToPeer(args) {
62337
- const f2 = args.fetchImpl ?? fetch;
62338
- const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
62339
- const url = `${base}/rpc/peerExec:run`;
62340
- const tunnelFail = (msg) => ({
62341
- type: "peerExec:run:ok",
62342
- stdout: "",
62343
- stderr: msg,
62344
- exitCode: null,
62345
- timedOut: false,
62346
- stdoutTruncated: false,
62347
- stderrTruncated: false
62348
- });
62349
- let res;
62350
- try {
62351
- res = await f2(url, {
62352
- method: "POST",
62353
- headers: {
62354
- "content-type": "application/json",
62355
- authorization: `Bearer ${args.contact.connectToken}`
62356
- },
62357
- body: JSON.stringify({ command: args.command, ...args.timeoutMs ? { timeoutMs: args.timeoutMs } : {} })
62358
- });
62359
- } catch (err) {
62360
- return tunnelFail(`forward to peer failed: ${err instanceof Error ? err.message : String(err)}`);
62361
- }
62362
- let json;
62363
- try {
62364
- json = await res.json();
62365
- } catch {
62366
- return tunnelFail(`peer returned non-JSON response (HTTP ${res.status})`);
62367
- }
62368
- if (json.ok === false) {
62369
- throw new ClawdError(json.error || ERROR_CODES.INTERNAL, json.message || json.error);
62370
- }
62371
- return json.result;
62372
- }
62373
-
62374
- // src/tools/codex-history.ts
62375
- var import_node_fs35 = __toESM(require("fs"), 1);
62376
- var import_node_path40 = __toESM(require("path"), 1);
62377
-
62378
- // src/tools/transcript-search.ts
62379
- var import_node_fs34 = __toESM(require("fs"), 1);
62380
- var import_node_path39 = __toESM(require("path"), 1);
62381
- var SNIPPET_PAD = 60;
62382
- var INJECTED_ENVELOPE_RE = /^<(system-reminder|command-name|command-message|command-args|local-command-stdout|local-command-stderr|task-notification|user-prompt-submit-hook|environment_context|user_instructions)\b/;
62383
- var CODEX_WRAPPER_RE = /<(related-dir|environment_context|user_instructions)>[\s\S]*?<\/\1>/g;
62384
- function asRecord2(v3) {
62385
- return typeof v3 === "object" && v3 !== null ? v3 : null;
62386
- }
62387
- function textOfBlocks(content, types) {
62388
- if (!Array.isArray(content)) return null;
62389
- const parts = [];
62390
- for (const block of content) {
62391
- const b3 = asRecord2(block);
62392
- if (!b3 || typeof b3.type !== "string" || !types.includes(b3.type)) continue;
62393
- const text = b3.text;
62394
- if (typeof text === "string" && text !== "") parts.push(text);
62395
- }
62396
- return parts.length > 0 ? parts.join("\n") : null;
62397
- }
62398
- function lineToSearchableText(raw) {
62399
- const line = asRecord2(raw);
62400
- if (!line) return null;
62401
- if (line.type === "response_item") {
62402
- const payload = asRecord2(line.payload);
62403
- if (!payload || payload.type !== "message") return null;
62404
- const role = payload.role;
62405
- if (role !== "user" && role !== "assistant") return null;
62406
- const text2 = textOfBlocks(payload.content, ["input_text", "output_text"]);
62407
- if (text2 === null) return null;
62408
- if (role !== "user") return text2;
62409
- const stripped = text2.replace(CODEX_WRAPPER_RE, "").trim();
62410
- if (stripped === "") return null;
62411
- return INJECTED_ENVELOPE_RE.test(stripped) ? null : stripped;
62412
- }
62413
- if (line.type !== "user" && line.type !== "assistant") return null;
62414
- const message = asRecord2(line.message);
62415
- if (!message) return null;
62416
- const content = message.content;
62417
- const text = typeof content === "string" && content !== "" ? content : textOfBlocks(content, ["text"]);
62418
- if (text === null) return null;
62419
- if (line.type === "user" && (line.isMeta === true || line.isSynthetic === true)) return null;
62420
- if (line.type === "user" && INJECTED_ENVELOPE_RE.test(text.trimStart())) return null;
62421
- return text;
62422
- }
62423
- function escapeRegExp(s2) {
62424
- return s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62425
- }
62426
- function makeSnippet(text, at3, needleLength) {
62427
- const from = Math.max(0, at3 - SNIPPET_PAD);
62428
- const to = Math.min(text.length, at3 + needleLength + SNIPPET_PAD);
62429
- const body = text.slice(from, to).replace(/\s+/g, " ").trim();
62430
- return `${from > 0 ? "\u2026 " : ""}${body}${to < text.length ? " \u2026" : ""}`;
62431
- }
62432
- function searchTranscriptFile(file, query) {
62433
- let buf;
62434
- try {
62435
- buf = import_node_fs34.default.readFileSync(file);
62436
- } catch {
62437
- return null;
62438
- }
62439
- const haystack = buf.toString("latin1");
62440
- const needle = Buffer.from(query, "utf8").toString("latin1");
62441
- const re2 = new RegExp(escapeRegExp(needle), "gi");
62442
- let m3;
62443
- while ((m3 = re2.exec(haystack)) !== null) {
62444
- const lineStart = haystack.lastIndexOf("\n", m3.index) + 1;
62445
- let lineEnd = haystack.indexOf("\n", m3.index);
62446
- if (lineEnd < 0) lineEnd = haystack.length;
62447
- re2.lastIndex = lineEnd + 1;
62448
- let parsed;
62449
- try {
62450
- parsed = JSON.parse(Buffer.from(haystack.slice(lineStart, lineEnd), "latin1").toString("utf8"));
62451
- } catch {
62452
- continue;
62453
- }
62454
- const text = lineToSearchableText(parsed);
62455
- if (text === null) continue;
62456
- const at3 = text.toLowerCase().indexOf(query.toLowerCase());
62457
- if (at3 < 0) continue;
62458
- return makeSnippet(text, at3, query.length);
62459
- }
62460
- return null;
62461
- }
62462
- var ROLLOUT_NAME_RE = /-([0-9a-fA-F-]{36})\.jsonl$/;
62463
- function createCodexRolloutLocator(roots) {
62464
- let index = null;
62465
- const walk2 = (dir, into) => {
62466
- let entries;
62467
- try {
62468
- entries = import_node_fs34.default.readdirSync(dir, { withFileTypes: true });
62469
- } catch {
62470
- return;
62471
- }
62472
- for (const entry of entries) {
62473
- const full = import_node_path39.default.join(dir, entry.name);
62474
- if (entry.isDirectory()) {
62475
- walk2(full, into);
62476
- continue;
62477
- }
62478
- const matched = ROLLOUT_NAME_RE.exec(entry.name);
62479
- if (matched) into.set(matched[1].toLowerCase(), full);
62480
- }
62481
- };
62482
- return (toolSessionId) => {
62483
- if (index === null) {
62484
- index = /* @__PURE__ */ new Map();
62485
- for (const root of roots) walk2(root, index);
62486
- }
62487
- return index.get(toolSessionId.toLowerCase()) ?? null;
62488
- };
62489
- }
62490
-
62491
- // src/tools/codex-rollout.ts
62492
- function rec2(v3) {
62493
- return v3 && typeof v3 === "object" && !Array.isArray(v3) ? v3 : void 0;
62494
- }
62495
- function str2(v3) {
62496
- return typeof v3 === "string" ? v3 : void 0;
62497
- }
62498
- function num2(v3) {
62499
- return typeof v3 === "number" && Number.isFinite(v3) ? v3 : void 0;
62500
- }
62501
- function parseLine(line) {
62502
- const t2 = line.trim();
62503
- if (!t2) return void 0;
62504
- try {
62505
- return rec2(JSON.parse(t2));
62506
- } catch {
62507
- return void 0;
62508
- }
62509
- }
62510
- function metaFrom(obj) {
62511
- if (obj.type !== "session_meta") return null;
62512
- const p3 = rec2(obj.payload);
62513
- const threadId = str2(p3?.id) ?? str2(p3?.session_id);
62514
- if (!p3 || !threadId) return null;
62515
- const meta = { threadId };
62516
- const cwd = str2(p3.cwd);
62517
- if (cwd) meta.cwd = cwd;
62518
- const createdAt = str2(p3.timestamp) ?? str2(obj.timestamp);
62519
- if (createdAt) meta.createdAt = createdAt;
62520
- const parent = str2(p3.parent_thread_id);
62521
- if (parent) meta.parentThreadId = parent;
62522
- const cli = str2(p3.cli_version);
62523
- if (cli) meta.cliVersion = cli;
62524
- return meta;
62525
- }
62526
- function parseCodexRolloutMeta(firstLine) {
62527
- const obj = parseLine(firstLine);
62528
- return obj ? metaFrom(obj) : null;
62529
- }
62530
- function firstUserMessageText(text) {
62531
- for (const line of text.split("\n")) {
62532
- const obj = parseLine(line);
62533
- if (!obj || obj.type !== "event_msg") continue;
62534
- const p3 = rec2(obj.payload);
62535
- if (p3?.type === "user_message") return str2(p3.message);
62536
- }
62537
- return void 0;
62538
- }
62539
- function outputText(output) {
62540
- if (typeof output === "string") return output;
62541
- if (Array.isArray(output)) {
62542
- return output.map((part) => typeof part === "string" ? part : str2(rec2(part)?.text) ?? "").join("");
62543
- }
62544
- return "";
62545
- }
62546
- function functionCallInput(name, rawArgs) {
62547
- let args = rawArgs;
62548
- if (typeof rawArgs === "string") {
62549
- try {
62550
- args = JSON.parse(rawArgs);
62551
- } catch {
62552
- args = { arguments: rawArgs };
62553
- }
62554
- }
62555
- const a2 = rec2(args) ?? {};
62556
- if (name === "exec_command" || name === "shell" || name === "container.exec") {
62557
- const cmd = a2.cmd ?? a2.command;
62558
- const command = Array.isArray(cmd) ? cmd.map(String).join(" ") : str2(cmd) ?? "";
62559
- return { tool: "commandExecution", input: { command } };
62560
- }
62561
- return { tool: name, input: args };
62562
- }
62563
- function parseCodexRollout(text) {
62564
- const out = { meta: null, messages: [], subagents: [], turns: [] };
62565
- const collabCallIds = /* @__PURE__ */ new Set();
62566
- for (const line of text.split("\n")) {
62567
- const obj = parseLine(line);
62568
- if (!obj) continue;
62569
- const ts = str2(obj.timestamp);
62570
- const p3 = rec2(obj.payload);
62571
- if (!p3) continue;
62572
- if (obj.type === "session_meta") {
62573
- if (!out.meta) out.meta = metaFrom(obj);
62574
- continue;
62575
- }
62576
- if (obj.type === "event_msg") {
62577
- switch (p3.type) {
62578
- case "user_message": {
62579
- const t2 = str2(p3.message);
62580
- if (t2) out.messages.push({ kind: "user-text", text: t2, raw: obj, ...ts ? { ts } : {} });
62581
- break;
62582
- }
62583
- case "agent_message": {
62584
- const t2 = str2(p3.message);
62585
- if (t2) out.messages.push({ kind: "assistant-text", text: t2, raw: obj, ...ts ? { ts } : {} });
62586
- break;
62587
- }
62588
- case "sub_agent_activity": {
62589
- if (p3.kind !== "started") break;
62590
- const toolUseId = str2(p3.event_id);
62591
- const agentId = str2(p3.agent_thread_id);
62592
- const agentPath = str2(p3.agent_path);
62593
- if (!toolUseId || !agentId || !agentPath) break;
62594
- const segments = agentPath.split("/").filter(Boolean);
62595
- const description = segments[segments.length - 1] ?? agentId;
62596
- out.subagents.push({ toolUseId, agentId, agentPath, description });
62597
- out.messages.push({
62598
- kind: "tool_call",
62599
- tool: "Agent",
62600
- toolUseId,
62601
- input: { agentId, description, agentPath },
62602
- raw: obj,
62603
- ...ts ? { ts } : {}
62604
- });
62605
- break;
62606
- }
62607
- case "task_complete":
62608
- case "turn_aborted": {
62609
- const durationMs = num2(p3.duration_ms);
62610
- const turnId = str2(p3.turn_id);
62611
- const reason = str2(p3.reason);
62612
- out.turns.push({
62613
- status: p3.type === "task_complete" ? "completed" : "interrupted",
62614
- ...turnId ? { turnId } : {},
62615
- ...durationMs !== void 0 ? { durationMs } : {},
62616
- ...reason ? { reason } : {}
62617
- });
62618
- out.messages.push({
62619
- kind: "turn-end",
62620
- raw: obj,
62621
- ...ts ? { ts } : {},
62622
- ...durationMs !== void 0 ? { durationMs } : {}
62623
- });
62624
- break;
62625
- }
62626
- default:
62627
- break;
62628
- }
62629
- continue;
62630
- }
62631
- if (obj.type === "response_item") {
62632
- switch (p3.type) {
62633
- case "function_call": {
62634
- const callId = str2(p3.call_id);
62635
- const name = str2(p3.name);
62636
- if (!callId || !name) break;
62637
- if (p3.namespace === "collaboration") {
62638
- collabCallIds.add(callId);
62639
- break;
62640
- }
62641
- const { tool, input } = functionCallInput(name, p3.arguments);
62642
- out.messages.push({ kind: "tool_call", tool, toolUseId: callId, input, raw: obj, ...ts ? { ts } : {} });
62643
- break;
62644
- }
62645
- case "custom_tool_call": {
62646
- const callId = str2(p3.call_id);
62647
- const name = str2(p3.name);
62648
- if (!callId || !name) break;
62649
- out.messages.push({
62650
- kind: "tool_call",
62651
- tool: name,
62652
- toolUseId: callId,
62653
- input: { input: str2(p3.input) ?? "" },
62654
- raw: obj,
62655
- ...ts ? { ts } : {}
62656
- });
62657
- break;
62658
- }
62659
- case "function_call_output":
62660
- case "custom_tool_call_output": {
62661
- const callId = str2(p3.call_id);
62662
- if (!callId || collabCallIds.has(callId)) break;
62663
- out.messages.push({
62664
- kind: "tool_result",
62665
- toolUseId: callId,
62666
- output: outputText(p3.output),
62667
- raw: obj,
62668
- ...ts ? { ts } : {}
62669
- });
62670
- break;
62671
- }
62672
- default:
62673
- break;
62674
- }
62675
- continue;
62676
- }
62677
- }
62678
- return out;
62679
- }
62680
- function subagentTerminalResult(started, child) {
62681
- const first = child.turns[0];
62682
- if (!first) return void 0;
62683
- if (first.status === "completed") return { kind: "tool_result", toolUseId: started.toolUseId };
62684
- return { kind: "tool_result", toolUseId: started.toolUseId, error: first.reason ?? "Codex subagent interrupted" };
62685
- }
62686
-
62687
- // src/tools/codex-history.ts
62688
- var CODEX_LIST_SESSIONS_LIMIT = 100;
62689
- var CODEX_LIST_SESSIONS_SCAN_CAP = 4e3;
62690
- var PEEK_BYTES = 8 * 1024;
62691
- var TITLE_BYTES = 256 * 1024;
62692
- function readHead(file, bytes) {
62693
- let fd;
62694
- try {
62695
- fd = import_node_fs35.default.openSync(file, "r");
62696
- } catch {
62697
- return null;
62698
- }
62699
- try {
62700
- const buf = Buffer.alloc(bytes);
62701
- const n2 = import_node_fs35.default.readSync(fd, buf, 0, bytes, 0);
62702
- return buf.subarray(0, n2).toString("utf8");
62703
- } catch {
62704
- return null;
62705
- } finally {
62706
- import_node_fs35.default.closeSync(fd);
62707
- }
62708
- }
62709
- function peekSessionCwd(head) {
62710
- if (!head.includes('"session_meta"')) return null;
62711
- const m3 = /"cwd":"((?:[^"\\]|\\.)*)"/.exec(head);
62712
- if (!m3) return null;
62713
- try {
62714
- return JSON.parse(`"${m3[1]}"`);
62715
- } catch {
62716
- return null;
62717
- }
63593
+
63594
+ // src/dispatch/peer-forward.ts
63595
+ init_protocol();
63596
+ function wsUrlToHttp(url) {
63597
+ if (url.startsWith("wss://")) return "https://" + url.slice("wss://".length);
63598
+ if (url.startsWith("ws://")) return "http://" + url.slice("ws://".length);
63599
+ return url;
62718
63600
  }
62719
- function readFirstLine(file, max = 512 * 1024) {
62720
- let fd;
63601
+ var DISPATCH_POLL_INTERVAL_MS = 5e3;
63602
+ var DISPATCH_POLL_UNREACHABLE_MS = 6e5;
63603
+ var DISPATCH_POLL_TOTAL_MS = 864e5;
63604
+ var defaultSleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
63605
+ async function forwardDispatchToPeer(args) {
63606
+ const f2 = args.fetchImpl ?? fetch;
63607
+ const sleep2 = args.sleepImpl ?? defaultSleep;
63608
+ const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
63609
+ const url = `${base}/rpc/personaDispatch:run`;
63610
+ let res;
62721
63611
  try {
62722
- fd = import_node_fs35.default.openSync(file, "r");
62723
- } catch {
62724
- return null;
63612
+ res = await f2(url, {
63613
+ method: "POST",
63614
+ headers: {
63615
+ "content-type": "application/json",
63616
+ authorization: `Bearer ${args.contact.connectToken}`
63617
+ },
63618
+ // 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
63619
+ body: JSON.stringify({
63620
+ targetPersona: args.targetPersona,
63621
+ prompt: args.prompt,
63622
+ // T-20: 条件展开防 undefined 污染 JSON。远端 daemon handler 会再校验一次 model 合法性。
63623
+ ...args.model ? { model: args.model } : {}
63624
+ })
63625
+ });
63626
+ } catch (err) {
63627
+ const msg = err instanceof Error ? err.message : String(err);
63628
+ return { kind: "failure", reason: `forward to peer failed: ${msg}` };
62725
63629
  }
63630
+ let json;
62726
63631
  try {
62727
- const chunk = Buffer.alloc(64 * 1024);
62728
- const parts = [];
62729
- let total = 0;
62730
- while (total < max) {
62731
- const n2 = import_node_fs35.default.readSync(fd, chunk, 0, chunk.length, total);
62732
- if (n2 <= 0) break;
62733
- const nl = chunk.subarray(0, n2).indexOf(10);
62734
- if (nl >= 0) {
62735
- parts.push(Buffer.from(chunk.subarray(0, nl)));
62736
- return Buffer.concat(parts).toString("utf8");
62737
- }
62738
- parts.push(Buffer.from(chunk.subarray(0, n2)));
62739
- total += n2;
62740
- }
62741
- return total < max && parts.length > 0 ? Buffer.concat(parts).toString("utf8") : null;
63632
+ json = await res.json();
62742
63633
  } catch {
62743
- return null;
62744
- } finally {
62745
- import_node_fs35.default.closeSync(fd);
63634
+ return {
63635
+ kind: "failure",
63636
+ reason: `peer returned non-JSON response (HTTP ${res.status})`
63637
+ };
62746
63638
  }
62747
- }
62748
- function readThreadNames(sharedHome) {
62749
- const names = /* @__PURE__ */ new Map();
62750
- if (!sharedHome) return names;
62751
- let raw;
62752
- try {
62753
- raw = import_node_fs35.default.readFileSync(import_node_path40.default.join(sharedHome, "session_index.jsonl"), "utf8");
62754
- } catch {
62755
- return names;
63639
+ if (json.ok === false) {
63640
+ return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
62756
63641
  }
62757
- for (const line of raw.split("\n")) {
62758
- if (!line.trim()) continue;
63642
+ if ("outcome" in json.result) return json.result.outcome;
63643
+ const dispatchId = json.result.dispatchId;
63644
+ let elapsed = 0;
63645
+ let unreachableFor = 0;
63646
+ while (elapsed < DISPATCH_POLL_TOTAL_MS) {
63647
+ await sleep2(DISPATCH_POLL_INTERVAL_MS);
63648
+ elapsed += DISPATCH_POLL_INTERVAL_MS;
63649
+ const markUnreachable = () => {
63650
+ unreachableFor += DISPATCH_POLL_INTERVAL_MS;
63651
+ return unreachableFor >= DISPATCH_POLL_UNREACHABLE_MS ? {
63652
+ kind: "failure",
63653
+ reason: "peer unreachable while polling dispatch outcome (TARGET_UNREACHABLE)"
63654
+ } : null;
63655
+ };
63656
+ let getRes;
62759
63657
  try {
62760
- const o2 = JSON.parse(line);
62761
- if (typeof o2.id === "string" && typeof o2.thread_name === "string" && o2.thread_name) {
62762
- names.set(o2.id.toLowerCase(), o2.thread_name);
62763
- }
63658
+ getRes = await f2(`${base}/rpc/personaDispatch:get`, {
63659
+ method: "POST",
63660
+ headers: {
63661
+ "content-type": "application/json",
63662
+ authorization: `Bearer ${args.contact.connectToken}`
63663
+ },
63664
+ body: JSON.stringify({ dispatchId })
63665
+ });
62764
63666
  } catch {
63667
+ const dead = markUnreachable();
63668
+ if (dead) return dead;
63669
+ continue;
62765
63670
  }
62766
- }
62767
- return names;
62768
- }
62769
- function walkRollouts(dir, visit) {
62770
- let entries;
62771
- try {
62772
- entries = import_node_fs35.default.readdirSync(dir, { withFileTypes: true });
62773
- } catch {
62774
- return true;
62775
- }
62776
- entries.sort((a2, b3) => a2.name < b3.name ? 1 : a2.name > b3.name ? -1 : 0);
62777
- for (const entry of entries) {
62778
- const full = import_node_path40.default.join(dir, entry.name);
62779
- if (entry.isDirectory()) {
62780
- if (!walkRollouts(full, visit)) return false;
62781
- } else if (ROLLOUT_NAME_RE.test(entry.name)) {
62782
- if (visit(full) === false) return false;
62783
- }
62784
- }
62785
- return true;
62786
- }
62787
- async function withSubagentResults(parent, parentThreadId, loadChild, logger) {
62788
- const messages = [];
62789
- const started = new Map(parent.subagents.map((s2) => [s2.toolUseId, s2]));
62790
- for (const m3 of parent.messages) {
62791
- messages.push(m3);
62792
- const sub = m3.kind === "tool_call" && m3.toolUseId ? started.get(m3.toolUseId) : void 0;
62793
- if (!sub) continue;
62794
- let text;
63671
+ let getJson;
62795
63672
  try {
62796
- text = await loadChild(sub.agentId);
62797
- } catch (error) {
62798
- logger?.warn("codex subagent rollout unreadable", { childThreadId: sub.agentId, message: error.message });
63673
+ getJson = await getRes.json();
63674
+ } catch {
63675
+ const dead = markUnreachable();
63676
+ if (dead) return dead;
62799
63677
  continue;
62800
63678
  }
62801
- if (text === null) {
62802
- logger?.warn("codex subagent rollout not found", { parentThreadId, childThreadId: sub.agentId });
63679
+ if (getJson.ok === false) {
63680
+ if (getJson.error === ERROR_CODES.DISPATCH_NOT_FOUND) {
63681
+ return { kind: "failure", reason: `peer lost dispatch ${dispatchId} (daemon restarted?)` };
63682
+ }
63683
+ const dead = markUnreachable();
63684
+ if (dead) return dead;
62803
63685
  continue;
62804
63686
  }
62805
- const child = parseCodexRollout(text);
62806
- if (child.meta?.parentThreadId !== parentThreadId) {
62807
- logger?.warn("codex subagent rollout does not belong to parent", {
62808
- parentThreadId,
62809
- childThreadId: sub.agentId,
62810
- actualParent: child.meta?.parentThreadId ?? null
62811
- });
62812
- continue;
63687
+ unreachableFor = 0;
63688
+ const { status, outcome } = getJson.result;
63689
+ if (status === "completed" || status === "failed") {
63690
+ return outcome ?? { kind: "failure", reason: "peer returned terminal status without outcome" };
62813
63691
  }
62814
- const result = subagentTerminalResult(sub, child);
62815
- if (result) messages.push(result);
62816
- }
62817
- return messages;
62818
- }
62819
- function pageMessages(messages, args) {
62820
- const total = messages.length;
62821
- const limit = args.limit ?? total;
62822
- const offset = typeof args.offset === "number" ? Math.min(Math.max(0, args.offset), total) : Math.max(0, total - limit);
62823
- return { messages: messages.slice(offset, offset + limit), total, offset };
62824
- }
62825
- function subagentSummaries(parent) {
62826
- const seen = /* @__PURE__ */ new Set();
62827
- const out = [];
62828
- for (const s2 of parent.subagents) {
62829
- if (seen.has(s2.agentId)) continue;
62830
- seen.add(s2.agentId);
62831
- out.push({ subagentId: s2.agentId, summary: s2.description, messageCount: 0 });
62832
63692
  }
62833
- return out;
63693
+ return { kind: "failure", reason: "dispatch polling exceeded 24h (TIMEOUT)" };
62834
63694
  }
62835
- var CodexHistoryReader = class {
62836
- deps;
62837
- constructor(deps) {
62838
- this.deps = deps;
62839
- }
62840
- // 仍返回 []:projects/list 非 session-scoped、不知 tool,跨-tool 项目浏览器合并排后续。
62841
- // codex 旧会话仍能经 clawd 活跃列表(session:list, tool-agnostic) + read 打开。
62842
- async listProjects() {
62843
- return [];
62844
- }
62845
- async listSessions(args) {
62846
- const want = import_node_path40.default.resolve(args.projectPath);
62847
- const names = readThreadNames(this.deps.sharedHome);
62848
- const found = [];
62849
- let scanned = 0;
62850
- for (const root of this.deps.roots) {
62851
- const goOn = walkRollouts(root, (file) => {
62852
- if (++scanned > CODEX_LIST_SESSIONS_SCAN_CAP) return false;
62853
- const head = readHead(file, PEEK_BYTES);
62854
- const cwd = head === null ? null : peekSessionCwd(head);
62855
- if (!cwd || import_node_path40.default.resolve(cwd) !== want) return;
62856
- const first = readFirstLine(file);
62857
- const meta = first === null ? null : parseCodexRolloutMeta(first);
62858
- if (!meta) return;
62859
- let mtimeMs;
62860
- try {
62861
- mtimeMs = import_node_fs35.default.statSync(file).mtimeMs;
62862
- } catch {
62863
- return;
62864
- }
62865
- found.push({ file, threadId: meta.threadId, mtimeMs });
62866
- });
62867
- if (!goOn) break;
62868
- }
62869
- found.sort((a2, b3) => b3.mtimeMs - a2.mtimeMs);
62870
- return found.slice(0, CODEX_LIST_SESSIONS_LIMIT).map((f2) => {
62871
- let summary = names.get(f2.threadId.toLowerCase());
62872
- if (!summary) {
62873
- const head = readHead(f2.file, TITLE_BYTES);
62874
- summary = head === null ? void 0 : firstUserMessageText(head);
62875
- }
62876
- return {
62877
- toolSessionId: f2.threadId,
62878
- summary: summary || "(\u7A7A\u4F1A\u8BDD)",
62879
- messageCount: 0,
62880
- // 与此前 thread/list 一致:列表不数条数
62881
- updatedAt: new Date(f2.mtimeMs).toISOString()
62882
- };
62883
- });
62884
- }
62885
- async read(args) {
62886
- const locate = createCodexRolloutLocator(this.deps.roots);
62887
- const parent = this.parseThread(locate, args.toolSessionId);
62888
- const messages = await withSubagentResults(
62889
- parent,
62890
- args.toolSessionId,
62891
- async (agentId) => {
62892
- const file = locate(agentId);
62893
- return file ? import_node_fs35.default.readFileSync(file, "utf8") : null;
62894
- },
62895
- this.deps.logger
62896
- );
62897
- return pageMessages(messages, args);
62898
- }
62899
- async listSubagents(args) {
62900
- return subagentSummaries(this.parseThread(createCodexRolloutLocator(this.deps.roots), args.toolSessionId));
62901
- }
62902
- async readSubagent(args) {
62903
- const child = this.parseThread(createCodexRolloutLocator(this.deps.roots), args.subagentId);
62904
- if (child.meta?.threadId !== args.subagentId || child.meta.parentThreadId !== args.toolSessionId) {
62905
- throw new Error(
62906
- `Codex subagent thread ${args.subagentId} does not belong to parent thread ${args.toolSessionId}`
62907
- );
62908
- }
62909
- return { messages: child.messages };
62910
- }
62911
- computeRewindDiff() {
62912
- return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
62913
- }
62914
- listRewindableUserMessageIds() {
62915
- return [];
62916
- }
62917
- parseThread(locate, threadId) {
62918
- const file = locate(threadId);
62919
- if (!file) throw new Error(`Codex rollout not found for thread ${threadId}`);
62920
- return parseCodexRollout(import_node_fs35.default.readFileSync(file, "utf8"));
62921
- }
62922
- };
62923
-
62924
- // src/cloud/gateway-client.ts
62925
- init_protocol();
62926
- init_protocol();
62927
-
62928
- // src/feishu-auth/central-client.ts
62929
- var CentralClientError = class extends Error {
62930
- constructor(code, message, cause) {
62931
- super(message);
62932
- this.code = code;
62933
- this.cause = cause;
62934
- this.name = "CentralClientError";
62935
- }
62936
- code;
62937
- cause;
62938
- };
62939
- async function centralRequest(opts, path102, init) {
62940
- const f2 = opts.fetchImpl ?? globalThis.fetch;
62941
- const url = `${opts.api.replace(/\/+$/, "")}${path102}`;
62942
- const ctrl = new AbortController();
62943
- const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
63695
+ async function forwardInboxPostToPeer(args) {
63696
+ const f2 = args.fetchImpl ?? fetch;
63697
+ const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
63698
+ const url = `${base}/rpc/inbox:postMessage`;
62944
63699
  let res;
62945
63700
  try {
62946
63701
  res = await f2(url, {
62947
- method: init.method,
63702
+ method: "POST",
62948
63703
  headers: {
62949
63704
  "content-type": "application/json",
62950
- ...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
63705
+ authorization: `Bearer ${args.contact.connectToken}`
62951
63706
  },
62952
- ...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
62953
- signal: ctrl.signal
63707
+ body: JSON.stringify({
63708
+ id: args.id,
63709
+ text: args.text,
63710
+ createdAt: args.createdAt,
63711
+ ...args.origin ? { origin: args.origin } : {}
63712
+ })
62954
63713
  });
62955
63714
  } catch (err) {
62956
- clearTimeout(timer);
62957
- throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
62958
- }
62959
- clearTimeout(timer);
62960
- if (res.status === 401) {
62961
- throw new CentralClientError("UNAUTHORIZED", "login pass rejected by center");
62962
- }
62963
- if (!res.ok) {
62964
- let detail = "";
62965
- try {
62966
- const body = await res.json();
62967
- if (typeof body?.message === "string" && body.message) detail = `: ${body.message}`;
62968
- } catch {
62969
- }
62970
- throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}${detail}`);
63715
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
62971
63716
  }
63717
+ let json = null;
62972
63718
  try {
62973
- return await res.json();
62974
- } catch (err) {
62975
- throw new CentralClientError("BAD_RESPONSE", "central server: invalid json response", err);
62976
- }
62977
- }
62978
- async function centralExchange(opts) {
62979
- const body = await centralRequest(opts, "/api/clawd-identity/exchange", {
62980
- method: "POST",
62981
- bearer: opts.loginPass,
62982
- body: { deviceId: opts.deviceId }
62983
- });
62984
- if (typeof body.token !== "string" || body.token.length === 0 || typeof body.ownerId !== "string" || typeof body.provider !== "string" || typeof body.displayName !== "string") {
62985
- throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/ownerId/provider/displayName");
62986
- }
62987
- const deviceUrl = typeof body.deviceUrl === "string" && body.deviceUrl.length > 0 ? body.deviceUrl : null;
62988
- return { token: body.token, ownerId: body.ownerId, provider: body.provider, displayName: body.displayName, deviceUrl };
62989
- }
62990
- async function centralGetPublicKey(opts) {
62991
- const body = await centralRequest(opts, "/api/clawd-identity/public-key", {
62992
- method: "GET"
62993
- });
62994
- if (typeof body.publicKeyPem !== "string" || !body.publicKeyPem.includes("PUBLIC KEY")) {
62995
- throw new CentralClientError("BAD_RESPONSE", "public-key: missing or malformed publicKeyPem");
63719
+ json = await res.json();
63720
+ } catch {
63721
+ return { ok: false, error: `peer non-JSON (HTTP ${res.status})` };
62996
63722
  }
62997
- return { publicKeyPem: body.publicKeyPem };
62998
- }
62999
- async function centralUpsertDevice(opts) {
63000
- const body = await centralRequest(opts, "/api/clawd-identity/devices/self", {
63001
- method: "PUT",
63002
- bearer: opts.loginPass,
63003
- body: {
63004
- ...opts.url ? { url: opts.url } : {},
63005
- ...opts.name ? { name: opts.name } : {}
63006
- }
63007
- });
63008
- if (body.ok !== true) {
63009
- throw new CentralClientError("BAD_RESPONSE", "upsertDevice: server did not ack ok");
63723
+ if (res.status < 200 || res.status >= 300 || json?.ok === false) {
63724
+ return { ok: false, error: json?.error ?? `HTTP ${res.status}` };
63010
63725
  }
63011
63726
  return { ok: true };
63012
63727
  }
63013
- async function centralPrincipalStatus(opts) {
63014
- const query = `provider=${encodeURIComponent(opts.provider)}&ownerId=${encodeURIComponent(opts.ownerId)}`;
63015
- const body = await centralRequest(opts, `/api/clawd-identity/principal-status?${query}`, {
63016
- method: "GET"
63017
- });
63018
- if (typeof body.active !== "boolean") {
63019
- throw new CentralClientError("BAD_RESPONSE", "principal-status: missing active flag");
63020
- }
63021
- return { active: body.active };
63022
- }
63023
- async function centralGetApiKey(opts) {
63024
- const body = await centralRequest(opts, "/api/persona-cloud/api-key", {
63025
- method: "GET",
63026
- bearer: opts.loginPass
63728
+ async function forwardPeerExecToPeer(args) {
63729
+ const f2 = args.fetchImpl ?? fetch;
63730
+ const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
63731
+ const url = `${base}/rpc/peerExec:run`;
63732
+ const tunnelFail = (msg) => ({
63733
+ type: "peerExec:run:ok",
63734
+ stdout: "",
63735
+ stderr: msg,
63736
+ exitCode: null,
63737
+ timedOut: false,
63738
+ stdoutTruncated: false,
63739
+ stderrTruncated: false
63027
63740
  });
63028
- if (body.apiKey !== null && typeof body.apiKey !== "string") {
63029
- throw new CentralClientError("BAD_RESPONSE", "api-key: apiKey must be string or null");
63030
- }
63031
- return {
63032
- apiKey: body.apiKey,
63033
- createdAt: typeof body.createdAt === "string" ? body.createdAt : null,
63034
- // 中心没配就是 null —— 不当成错误,key 本身照样有效
63035
- gatewayUrl: typeof body.gatewayUrl === "string" ? body.gatewayUrl : null
63036
- };
63037
- }
63038
- async function centralGetThread(opts) {
63039
- const f2 = opts.fetchImpl ?? globalThis.fetch;
63040
- const url = `${opts.api.replace(/\/+$/, "")}/api/persona-cloud/threads/${encodeURIComponent(opts.threadId)}`;
63041
- const ctrl = new AbortController();
63042
- const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
63043
63741
  let res;
63044
63742
  try {
63045
- res = await f2(url, { method: "GET", headers: { authorization: `Bearer ${opts.loginPass}` }, signal: ctrl.signal });
63743
+ res = await f2(url, {
63744
+ method: "POST",
63745
+ headers: {
63746
+ "content-type": "application/json",
63747
+ authorization: `Bearer ${args.contact.connectToken}`
63748
+ },
63749
+ body: JSON.stringify({ command: args.command, ...args.timeoutMs ? { timeoutMs: args.timeoutMs } : {} })
63750
+ });
63046
63751
  } catch (err) {
63047
- clearTimeout(timer);
63048
- throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
63049
- }
63050
- clearTimeout(timer);
63051
- if (res.status === 404) return null;
63052
- if (res.status === 401) throw new CentralClientError("UNAUTHORIZED", "login pass rejected by center");
63053
- if (!res.ok) {
63054
- let detail = "";
63055
- try {
63056
- const body = await res.json();
63057
- if (typeof body?.message === "string" && body.message) detail = `: ${body.message}`;
63058
- } catch {
63059
- }
63060
- throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}${detail}`);
63061
- }
63062
- return await res.text();
63063
- }
63064
- async function centralIssueApiKey(opts) {
63065
- const body = await centralRequest(opts, "/api/persona-cloud/api-key", {
63066
- method: "POST",
63067
- bearer: opts.loginPass
63068
- });
63069
- if (typeof body.apiKey !== "string" || body.apiKey.length === 0) {
63070
- throw new CentralClientError("BAD_RESPONSE", "api-key: missing apiKey");
63071
- }
63072
- if (typeof body.createdAt !== "string" || body.createdAt.length === 0) {
63073
- throw new CentralClientError("BAD_RESPONSE", "api-key: missing createdAt");
63074
- }
63075
- return {
63076
- apiKey: body.apiKey,
63077
- createdAt: body.createdAt,
63078
- gatewayUrl: typeof body.gatewayUrl === "string" ? body.gatewayUrl : null
63079
- };
63080
- }
63081
- async function centralRevokeApiKey(opts) {
63082
- const body = await centralRequest(opts, "/api/persona-cloud/api-key", {
63083
- method: "DELETE",
63084
- bearer: opts.loginPass
63085
- });
63086
- if (typeof body.revoked !== "boolean") {
63087
- throw new CentralClientError("BAD_RESPONSE", "api-key: revoked must be boolean");
63752
+ return tunnelFail(`forward to peer failed: ${err instanceof Error ? err.message : String(err)}`);
63088
63753
  }
63089
- return { revoked: body.revoked };
63090
- }
63091
- async function centralListDevices(opts) {
63092
- const body = await centralRequest(opts, "/api/clawd-identity/devices", {
63093
- method: "GET",
63094
- bearer: opts.loginPass
63095
- });
63096
- if (!Array.isArray(body.devices)) {
63097
- throw new CentralClientError("BAD_RESPONSE", "devices: missing devices array");
63754
+ let json;
63755
+ try {
63756
+ json = await res.json();
63757
+ } catch {
63758
+ return tunnelFail(`peer returned non-JSON response (HTTP ${res.status})`);
63098
63759
  }
63099
- const out = [];
63100
- for (const raw of body.devices) {
63101
- const d2 = raw;
63102
- if (typeof d2.deviceId !== "string" || typeof d2.name !== "string" || typeof d2.url !== "string") {
63103
- throw new CentralClientError("BAD_RESPONSE", "devices: malformed device entry");
63104
- }
63105
- out.push({ deviceId: d2.deviceId, name: d2.name, url: d2.url });
63760
+ if (json.ok === false) {
63761
+ throw new ClawdError(json.error || ERROR_CODES.INTERNAL, json.message || json.error);
63106
63762
  }
63107
- return out;
63108
- }
63109
- async function centralConnectorsMine(opts) {
63110
- const body = await centralRequest(opts, "/api/persona-cloud/connectors/mine", {
63111
- method: "POST",
63112
- bearer: opts.loginPass,
63113
- body: { personaId: opts.personaId, connectors: opts.connectors }
63114
- });
63115
- if (!Array.isArray(body.connectors)) throw new CentralClientError("BAD_RESPONSE", "connectors/mine: missing connectors array");
63116
- return { connectors: body.connectors };
63117
- }
63118
- async function centralConnectorFill(opts) {
63119
- const body = await centralRequest(opts, "/api/persona-cloud/connectors/fields", {
63120
- method: "PUT",
63121
- bearer: opts.loginPass,
63122
- body: { connector: opts.connector, values: opts.values }
63123
- });
63124
- if (body.ok !== true) throw new CentralClientError("BAD_RESPONSE", "connectors/fields: server did not ack ok");
63125
- }
63126
- async function centralGithubAuthOauthStart(opts) {
63127
- const body = await centralRequest(opts, "/api/persona-cloud/github/oauth/start", {
63128
- method: "POST",
63129
- bearer: opts.loginPass,
63130
- body: { ...opts.returnTo ? { returnTo: opts.returnTo } : {} }
63131
- });
63132
- if (typeof body.authUrl !== "string" || !body.authUrl) throw new CentralClientError("BAD_RESPONSE", "github/oauth/start: missing authUrl");
63133
- return { authUrl: body.authUrl };
63134
- }
63135
- async function centralGithubAuthToken(opts) {
63136
- const body = await centralRequest(opts, "/api/persona-cloud/github/oauth/token", {
63137
- method: "POST",
63138
- bearer: opts.loginPass,
63139
- body: {}
63140
- });
63141
- if (body.token === null || body.token === void 0) return void 0;
63142
- if (typeof body.token !== "string" || !body.token) throw new CentralClientError("BAD_RESPONSE", "github/oauth/token: malformed token");
63143
- return { token: body.token, login: typeof body.login === "string" ? body.login : "" };
63763
+ return json.result;
63144
63764
  }
63145
63765
 
63146
63766
  // src/cloud/gateway-client.ts
63767
+ init_protocol();
63768
+ init_protocol();
63147
63769
  var CLOUD_WORKSPACE_SLOT = "{{WORKSPACE}}";
63148
63770
  var CLOUD_TURN_HEADER = "x-clawd-turn";
63149
63771
  var CLOUD_TURN_HEADER_START = "start";
@@ -63154,7 +63776,7 @@ var GATEWAY_ROUTES = {
63154
63776
  sessionMirror: (personaId2, sessionId) => `/p/${encodeURIComponent(personaId2)}/session/${encodeURIComponent(sessionId)}/mirror`,
63155
63777
  // 调用方自己那块盘的用量(design 2026-09-19-cloud-volume-usage §3.3):同样按人、不挂 persona
63156
63778
  volume: () => "/me/volume",
63157
- // 云 persona 运行看板的数据(design 2026-09-23-cloud-persona-metrics §3.6):任何有效 key 都能看(C1)
63779
+ // 云 persona 运行看板的数据(design 2026-09-23-cloud-persona-metrics §3.6):只给发布者与管理员(gateway 问中心,design 2026-09-24-cloud-persona-conversations §3.2)
63158
63780
  metrics: (personaId2) => `/metrics/${encodeURIComponent(personaId2)}`
63159
63781
  };
63160
63782
  var METRICS_TIMEOUT_MS = 3e4;
@@ -63205,13 +63827,17 @@ var CloudGatewayClient = class {
63205
63827
  if (!apiKey) throw new CloudApiKeyRequiredError();
63206
63828
  return { apiKey, gatewayUrl };
63207
63829
  }
63208
- async listPersonas() {
63830
+ /**
63831
+ * `healthzTimeoutMs`:只给 /healthz 那一趟定上限,**问到地址之后才开始计**(上云页的线上状态用;design 2026-09-24-deploy-unpublish)。
63832
+ * 中心那一跳(locate)有它自己的上限,不归这里管。不给 = 同以前,不设
63833
+ */
63834
+ async listPersonas(opts = {}) {
63209
63835
  const { gatewayUrl } = await this.locate();
63210
- return this.fetchDeployments(gatewayUrl);
63836
+ return this.fetchDeployments(gatewayUrl, opts.healthzTimeoutMs ? AbortSignal.timeout(opts.healthzTimeoutMs) : void 0);
63211
63837
  }
63212
63838
  /** /healthz 的登记表 → official 行。gatewayUrl 由调用方定:listPersonas 从中心拿,personaConnectors 用 persona 文件里的 */
63213
- async fetchDeployments(gatewayUrl) {
63214
- const res = await this.fetchImpl(gatewayUrl + GATEWAY_ROUTES.healthz());
63839
+ async fetchDeployments(gatewayUrl, signal) {
63840
+ const res = await this.fetchImpl(gatewayUrl + GATEWAY_ROUTES.healthz(), ...signal ? [{ signal }] : []);
63215
63841
  if (!res.ok) throw new Error(`gateway /healthz \u56DE ${res.status}`);
63216
63842
  const body = await res.json();
63217
63843
  const rows = Array.isArray(body.deployments) ? body.deployments : [];
@@ -63510,68 +64136,7 @@ function openHttpLineStream(opts) {
63510
64136
  init_protocol();
63511
64137
 
63512
64138
  // src/tools/codex-cloud-session.ts
63513
- var import_node_fs36 = __toESM(require("fs"), 1);
63514
-
63515
- // src/tools/codex-cloud-history.ts
63516
- function personaIdFromCloudCwd(cwd) {
63517
- const m3 = /\/p\/([^/?#]+)(?:[/?#]|$)/.exec(cwd);
63518
- if (!m3) return void 0;
63519
- try {
63520
- return decodeURIComponent(m3[1]);
63521
- } catch {
63522
- return void 0;
63523
- }
63524
- }
63525
- var CloudCodexHistoryReader = class {
63526
- constructor(client, logger) {
63527
- this.client = client;
63528
- this.logger = logger;
63529
- }
63530
- client;
63531
- logger;
63532
- async listProjects() {
63533
- return [];
63534
- }
63535
- /** 云 persona 没有「关联已有 thread」的动线:会话列表就是本机的会话记录 */
63536
- async listSessions() {
63537
- return [];
63538
- }
63539
- async read(args) {
63540
- const parent = await this.fetchThread(args.toolSessionId);
63541
- const messages = await withSubagentResults(
63542
- parent,
63543
- args.toolSessionId,
63544
- (agentId) => this.client.fetchRollout(agentId),
63545
- this.logger
63546
- );
63547
- return pageMessages(messages, args);
63548
- }
63549
- async listSubagents(args) {
63550
- return subagentSummaries(await this.fetchThread(args.toolSessionId));
63551
- }
63552
- async readSubagent(args) {
63553
- const child = await this.fetchThread(args.subagentId);
63554
- if (child.meta?.threadId !== args.subagentId || child.meta.parentThreadId !== args.toolSessionId) {
63555
- throw new Error(
63556
- `Codex subagent thread ${args.subagentId} does not belong to parent thread ${args.toolSessionId}`
63557
- );
63558
- }
63559
- return { messages: child.messages };
63560
- }
63561
- computeRewindDiff() {
63562
- return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
63563
- }
63564
- listRewindableUserMessageIds() {
63565
- return [];
63566
- }
63567
- async fetchThread(threadId) {
63568
- const text = await this.client.fetchRollout(threadId);
63569
- if (text === null) throw new Error(`Codex rollout not found for thread ${threadId}`);
63570
- return parseCodexRollout(text);
63571
- }
63572
- };
63573
-
63574
- // src/tools/codex-cloud-session.ts
64139
+ var import_node_fs37 = __toESM(require("fs"), 1);
63575
64140
  function cloudThreadParams(ctx, paths) {
63576
64141
  const { model: _model, ...base } = threadStartParams(ctx);
63577
64142
  const developerInstructions = [ctx.extraSystemPrompt, workDirPrompt(paths.workDir, ctx.personaMode ?? "owner")].filter((x2) => Boolean(x2)).join("\n\n");
@@ -63628,7 +64193,7 @@ function cloudImageInput(deps) {
63628
64193
  const mime = lookupMime(p3);
63629
64194
  if (!mime.startsWith("image/")) return null;
63630
64195
  try {
63631
- const bytes = (deps.readFile ?? import_node_fs36.default.readFileSync)(p3);
64196
+ const bytes = (deps.readFile ?? import_node_fs37.default.readFileSync)(p3);
63632
64197
  return { type: "image", url: `data:${mime};base64,${bytes.toString("base64")}` };
63633
64198
  } catch (e) {
63634
64199
  deps.logger?.warn("cloud image attachment unreadable", { path: p3, message: e.message });
@@ -63861,37 +64426,11 @@ var PiRpcClient = class {
63861
64426
  // src/tools/pi-session.ts
63862
64427
  var import_node_child_process11 = require("child_process");
63863
64428
  var import_node_crypto12 = require("crypto");
63864
- var import_node_fs37 = __toESM(require("fs"), 1);
63865
- var import_node_path41 = __toESM(require("path"), 1);
64429
+ var import_node_fs38 = __toESM(require("fs"), 1);
64430
+ var import_node_path42 = __toESM(require("path"), 1);
63866
64431
 
63867
64432
  // src/tools/pi-events.ts
63868
64433
  init_protocol();
63869
-
63870
- // src/tools/pi-json.ts
63871
- function rec3(v3) {
63872
- return typeof v3 === "object" && v3 !== null && !Array.isArray(v3) ? v3 : void 0;
63873
- }
63874
- function str3(v3) {
63875
- return typeof v3 === "string" ? v3 : void 0;
63876
- }
63877
- function num3(v3) {
63878
- return typeof v3 === "number" && Number.isFinite(v3) ? v3 : void 0;
63879
- }
63880
- function arr(v3) {
63881
- return Array.isArray(v3) ? v3 : [];
63882
- }
63883
- function contentText(v3, imagePlaceholder = "") {
63884
- if (typeof v3 === "string") return v3;
63885
- return arr(v3).map((b3) => {
63886
- const r2 = rec3(b3);
63887
- if (!r2) return "";
63888
- if (r2.type === "text") return str3(r2.text) ?? "";
63889
- if (r2.type === "image") return imagePlaceholder;
63890
- return "";
63891
- }).filter(Boolean).join("\n");
63892
- }
63893
-
63894
- // src/tools/pi-events.ts
63895
64434
  function createPiTranslatorState() {
63896
64435
  return { assistantSeq: 0, aborted: false };
63897
64436
  }
@@ -63902,23 +64441,23 @@ function translatePiEvent(ev, st2) {
63902
64441
  return [];
63903
64442
  }
63904
64443
  case "message_start": {
63905
- if (rec3(ev.message)?.role === "assistant") st2.assistantSeq += 1;
64444
+ if (rec2(ev.message)?.role === "assistant") st2.assistantSeq += 1;
63906
64445
  return [];
63907
64446
  }
63908
64447
  case "message_update":
63909
- return messageUpdate(rec3(ev.assistantMessageEvent), st2);
64448
+ return messageUpdate(rec2(ev.assistantMessageEvent), st2);
63910
64449
  case "message_end":
63911
- return messageEnd(rec3(ev.message), st2);
64450
+ return messageEnd(rec2(ev.message), st2);
63912
64451
  case "tool_execution_start": {
63913
- const toolUseId = str3(ev.toolCallId);
63914
- const tool = str3(ev.toolName);
64452
+ const toolUseId = str2(ev.toolCallId);
64453
+ const tool = str2(ev.toolName);
63915
64454
  if (!toolUseId || !tool) return [];
63916
64455
  return [{ kind: "tool_call", toolUseId, tool, toolKind: PI_TOOL_ID, input: ev.args ?? {} }];
63917
64456
  }
63918
64457
  case "tool_execution_end": {
63919
- const toolUseId = str3(ev.toolCallId);
64458
+ const toolUseId = str2(ev.toolCallId);
63920
64459
  if (!toolUseId) return [];
63921
- const text = contentText(rec3(ev.result)?.content);
64460
+ const text = contentText(rec2(ev.result)?.content);
63922
64461
  const out = { kind: "tool_result", toolUseId, output: text };
63923
64462
  if (ev.isError === true) out.error = text || "tool failed";
63924
64463
  return [out];
@@ -63934,9 +64473,9 @@ function translatePiEvent(ev, st2) {
63934
64473
  }
63935
64474
  function messageUpdate(e, st2) {
63936
64475
  if (!e) return [];
63937
- const delta = str3(e.delta);
64476
+ const delta = str2(e.delta);
63938
64477
  if (!delta) return [];
63939
- const partialId = `${st2.assistantSeq}-${num3(e.contentIndex) ?? 0}`;
64478
+ const partialId = `${st2.assistantSeq}-${num2(e.contentIndex) ?? 0}`;
63940
64479
  if (e.type === "text_delta") return [{ kind: "text", text: delta, partialId }];
63941
64480
  if (e.type === "thinking_delta") return [{ kind: "thinking", text: delta, partialId }];
63942
64481
  return [];
@@ -63945,32 +64484,32 @@ function messageEnd(m3, st2) {
63945
64484
  if (!m3 || m3.role !== "assistant") return [];
63946
64485
  const out = [];
63947
64486
  for (const b3 of arr(m3.content)) {
63948
- const block = rec3(b3);
64487
+ const block = rec2(b3);
63949
64488
  if (!block) continue;
63950
64489
  if (block.type === "thinking") {
63951
- const t2 = str3(block.thinking);
64490
+ const t2 = str2(block.thinking);
63952
64491
  if (t2) out.push({ kind: "thinking", text: t2 });
63953
64492
  } else if (block.type === "text") {
63954
- const t2 = str3(block.text);
64493
+ const t2 = str2(block.text);
63955
64494
  if (t2) out.push({ kind: "text", text: t2 });
63956
64495
  }
63957
64496
  }
63958
64497
  const patch = {};
63959
- const model = str3(m3.model);
63960
- const provider = str3(m3.provider);
64498
+ const model = str2(m3.model);
64499
+ const provider = str2(m3.provider);
63961
64500
  if (model) patch.resolvedModel = provider ? `${provider}/${model}` : model;
63962
- const u2 = rec3(m3.usage);
63963
- if (u2 && (num3(u2.totalTokens) ?? 0) > 0) {
64501
+ const u2 = rec2(m3.usage);
64502
+ if (u2 && (num2(u2.totalTokens) ?? 0) > 0) {
63964
64503
  patch.contextUsage = {
63965
- inputTokens: num3(u2.input) ?? 0,
63966
- outputTokens: num3(u2.output) ?? 0,
63967
- ...num3(u2.cacheRead) !== void 0 ? { cacheReadTokens: num3(u2.cacheRead) } : {},
63968
- ...num3(u2.cacheWrite) !== void 0 ? { cacheCreateTokens: num3(u2.cacheWrite) } : {}
64504
+ inputTokens: num2(u2.input) ?? 0,
64505
+ outputTokens: num2(u2.output) ?? 0,
64506
+ ...num2(u2.cacheRead) !== void 0 ? { cacheReadTokens: num2(u2.cacheRead) } : {},
64507
+ ...num2(u2.cacheWrite) !== void 0 ? { cacheCreateTokens: num2(u2.cacheWrite) } : {}
63969
64508
  };
63970
64509
  }
63971
64510
  if (patch.resolvedModel || patch.contextUsage) out.push({ kind: "meta_update", patch });
63972
- const stop = str3(m3.stopReason);
63973
- const err = str3(m3.errorMessage);
64511
+ const stop = str2(m3.stopReason);
64512
+ const err = str2(m3.errorMessage);
63974
64513
  if (stop === "aborted") {
63975
64514
  st2.aborted = true;
63976
64515
  } else if (stop === "error" || err) {
@@ -64038,8 +64577,8 @@ function drivePiSession(opts) {
64038
64577
  opts.onTurnCompleted?.("failure");
64039
64578
  };
64040
64579
  const handleUiRequest = (ev) => {
64041
- const method = str3(ev.method);
64042
- const id = str3(ev.id);
64580
+ const method = str2(ev.method);
64581
+ const id = str2(ev.id);
64043
64582
  logger?.warn("pi extension_ui_request unexpected in clawd session", { method });
64044
64583
  if (id && (method === "select" || method === "confirm" || method === "input" || method === "editor")) {
64045
64584
  client.send({ type: "extension_ui_response", id, cancelled: true });
@@ -64054,7 +64593,7 @@ function drivePiSession(opts) {
64054
64593
  }
64055
64594
  if (ev.type === "agent_settled") busy = false;
64056
64595
  if (ev.type === "response") {
64057
- logger?.warn("pi response without pending id", { command: str3(ev.command), error: str3(ev.error) });
64596
+ logger?.warn("pi response without pending id", { command: str2(ev.command), error: str2(ev.error) });
64058
64597
  return;
64059
64598
  }
64060
64599
  if (ev.type === "extension_ui_request") {
@@ -64076,15 +64615,15 @@ function drivePiSession(opts) {
64076
64615
  });
64077
64616
  const ready = client.request({ type: "get_state" }).then((r2) => {
64078
64617
  if (!r2.success) throw new Error(r2.error ?? "get_state failed");
64079
- const data = rec3(r2.data);
64080
- const sid = str3(data?.sessionId) ?? sessionId;
64618
+ const data = rec2(r2.data);
64619
+ const sid = str2(data?.sessionId) ?? sessionId;
64081
64620
  if (sid !== sessionId) logger?.error("pi sessionId mismatch", { expected: sessionId, actual: sid });
64082
- const model = rec3(data?.model);
64621
+ const model = rec2(data?.model);
64083
64622
  const patch = {};
64084
- const mid = str3(model?.id);
64085
- const prov = str3(model?.provider);
64623
+ const mid = str2(model?.id);
64624
+ const prov = str2(model?.provider);
64086
64625
  if (mid && mid !== "unknown") patch.resolvedModel = prov ? `${prov}/${mid}` : mid;
64087
- const cw = num3(model?.contextWindow);
64626
+ const cw = num2(model?.contextWindow);
64088
64627
  if (cw && cw > 0) patch.contextWindowSize = cw;
64089
64628
  const events = [{ kind: "session_init", toolSessionId: sid }];
64090
64629
  if (patch.resolvedModel || patch.contextWindowSize) events.push({ kind: "meta_update", patch });
@@ -64155,8 +64694,8 @@ ${dirs.map((d2) => `- ${d2}`).join("\n")}`;
64155
64694
  function createPiSession(ctx, sink, deps = {}) {
64156
64695
  const cmd = process.env.PI_BIN ?? "pi";
64157
64696
  const sessionId = ctx.toolSessionId ?? (0, import_node_crypto12.randomUUID)();
64158
- const skillsDir = import_node_path41.default.join(ctx.cwd, ".claude", "skills");
64159
- const exists = deps.skillsDirExists ?? ((d2) => import_node_fs37.default.existsSync(d2));
64697
+ const skillsDir = import_node_path42.default.join(ctx.cwd, ".claude", "skills");
64698
+ const exists = deps.skillsDirExists ?? ((d2) => import_node_fs38.default.existsSync(d2));
64160
64699
  const args = buildPiArgs(ctx, {
64161
64700
  sessionId,
64162
64701
  skillsDir: exists(skillsDir) ? skillsDir : void 0,
@@ -64199,201 +64738,6 @@ function createPiSession(ctx, sink, deps = {}) {
64199
64738
  return driver.session;
64200
64739
  }
64201
64740
 
64202
- // src/tools/pi-history.ts
64203
- var import_node_fs38 = __toESM(require("fs"), 1);
64204
- var import_node_os12 = __toESM(require("os"), 1);
64205
- var import_node_path42 = __toESM(require("path"), 1);
64206
- var PI_LIST_SESSIONS_LIMIT = 100;
64207
- var PI_LIST_SESSIONS_SCAN_CAP = 4e3;
64208
- function piAgentDir(env = process.env) {
64209
- return env.PI_CODING_AGENT_DIR || import_node_path42.default.join(import_node_os12.default.homedir(), ".pi", "agent");
64210
- }
64211
- function piSessionsRoot(env = process.env) {
64212
- return import_node_path42.default.join(piAgentDir(env), "sessions");
64213
- }
64214
- function encodePiCwd(cwd) {
64215
- return `--${cwd.replace(/^[\\/]+/, "").replace(/[\\/:]/g, "-")}--`;
64216
- }
64217
- function piProjectSessionDir(sessionsRoot, cwd) {
64218
- return import_node_path42.default.join(sessionsRoot, encodePiCwd(cwd));
64219
- }
64220
- function findInDir(dir, toolSessionId) {
64221
- let names;
64222
- try {
64223
- names = import_node_fs38.default.readdirSync(dir);
64224
- } catch {
64225
- return null;
64226
- }
64227
- const suffix = `_${toolSessionId}.jsonl`;
64228
- const hit = names.filter((n2) => n2.endsWith(suffix)).sort().at(-1);
64229
- return hit ? import_node_path42.default.join(dir, hit) : null;
64230
- }
64231
- function locatePiSessionFile(sessionsRoot, cwd, toolSessionId) {
64232
- const direct = findInDir(piProjectSessionDir(sessionsRoot, cwd), toolSessionId);
64233
- if (direct) return direct;
64234
- let dirs;
64235
- try {
64236
- dirs = import_node_fs38.default.readdirSync(sessionsRoot, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) => d2.name);
64237
- } catch {
64238
- return null;
64239
- }
64240
- for (const d2 of dirs) {
64241
- const hit = findInDir(import_node_path42.default.join(sessionsRoot, d2), toolSessionId);
64242
- if (hit) return hit;
64243
- }
64244
- return null;
64245
- }
64246
- function parseLine2(line) {
64247
- const t2 = line.trim();
64248
- if (!t2) return null;
64249
- try {
64250
- return rec3(JSON.parse(t2)) ?? null;
64251
- } catch {
64252
- return null;
64253
- }
64254
- }
64255
- function parsePiSession(text) {
64256
- let meta = null;
64257
- const entries = /* @__PURE__ */ new Map();
64258
- let last = null;
64259
- for (const line of text.split("\n")) {
64260
- const o2 = parseLine2(line);
64261
- if (!o2) continue;
64262
- if (o2.type === "session") {
64263
- if (!meta) {
64264
- const timestamp = str3(o2.timestamp);
64265
- meta = { id: str3(o2.id) ?? "", cwd: str3(o2.cwd) ?? "", ...timestamp ? { timestamp } : {} };
64266
- }
64267
- continue;
64268
- }
64269
- const id = str3(o2.id);
64270
- if (!id) continue;
64271
- entries.set(id, o2);
64272
- last = o2;
64273
- }
64274
- const chain = [];
64275
- const seen = /* @__PURE__ */ new Set();
64276
- let cur = last;
64277
- while (cur) {
64278
- const id = str3(cur.id);
64279
- if (seen.has(id)) break;
64280
- seen.add(id);
64281
- chain.push(cur);
64282
- const pid = str3(cur.parentId);
64283
- cur = pid ? entries.get(pid) ?? null : null;
64284
- }
64285
- chain.reverse();
64286
- const messages = [];
64287
- for (const e of chain) {
64288
- if (e.type !== "message") continue;
64289
- const m3 = rec3(e.message);
64290
- if (!m3) continue;
64291
- const ts = str3(e.timestamp);
64292
- const base = { raw: e, uuid: str3(e.id), ...ts ? { ts } : {} };
64293
- switch (m3.role) {
64294
- case "user": {
64295
- const t2 = contentText(m3.content, "[image]");
64296
- if (t2) messages.push({ kind: "user-text", text: t2, ...base });
64297
- break;
64298
- }
64299
- case "assistant": {
64300
- for (const b3 of arr(m3.content)) {
64301
- const block = rec3(b3);
64302
- if (!block) continue;
64303
- if (block.type === "text") {
64304
- const t2 = str3(block.text);
64305
- if (t2) messages.push({ kind: "assistant-text", text: t2, ...base });
64306
- } else if (block.type === "toolCall") {
64307
- const toolUseId = str3(block.id);
64308
- const tool = str3(block.name);
64309
- if (toolUseId && tool) messages.push({ kind: "tool_call", tool, toolUseId, input: block.arguments ?? {}, ...base });
64310
- }
64311
- }
64312
- if (str3(m3.stopReason) !== "toolUse") messages.push({ kind: "turn-end", ...base });
64313
- break;
64314
- }
64315
- case "toolResult": {
64316
- const toolUseId = str3(m3.toolCallId);
64317
- if (!toolUseId) break;
64318
- const t2 = contentText(m3.content);
64319
- const msg = { kind: "tool_result", toolUseId, output: t2, ...base };
64320
- if (m3.isError === true) msg.error = t2 || "tool failed";
64321
- messages.push(msg);
64322
- break;
64323
- }
64324
- default:
64325
- break;
64326
- }
64327
- }
64328
- return { meta, messages };
64329
- }
64330
- var PiHistoryReader = class {
64331
- constructor(deps) {
64332
- this.deps = deps;
64333
- }
64334
- deps;
64335
- // projects/list 非 session-scoped、不知 tool(同 codex 的取舍)
64336
- async listProjects() {
64337
- return [];
64338
- }
64339
- async listSessions(args) {
64340
- const want = import_node_path42.default.resolve(args.projectPath);
64341
- const dir = piProjectSessionDir(this.deps.sessionsRoot, want);
64342
- let names;
64343
- try {
64344
- names = import_node_fs38.default.readdirSync(dir).filter((n2) => n2.endsWith(".jsonl"));
64345
- } catch {
64346
- return [];
64347
- }
64348
- const found = [];
64349
- let scanned = 0;
64350
- for (const n2 of names) {
64351
- if (++scanned > PI_LIST_SESSIONS_SCAN_CAP) break;
64352
- const file = import_node_path42.default.join(dir, n2);
64353
- const head = parseLine2(readFirstLine(file) ?? "");
64354
- if (!head || head.type !== "session") continue;
64355
- const cwd = str3(head.cwd);
64356
- const id = str3(head.id);
64357
- if (!cwd || !id || import_node_path42.default.resolve(cwd) !== want) continue;
64358
- let mtimeMs;
64359
- try {
64360
- mtimeMs = import_node_fs38.default.statSync(file).mtimeMs;
64361
- } catch {
64362
- continue;
64363
- }
64364
- found.push({ file, id, mtimeMs });
64365
- }
64366
- found.sort((a2, b3) => b3.mtimeMs - a2.mtimeMs);
64367
- return found.slice(0, PI_LIST_SESSIONS_LIMIT).map((f2) => {
64368
- const parsed = parsePiSession(import_node_fs38.default.readFileSync(f2.file, "utf8"));
64369
- const firstUser = parsed.messages.find((m3) => m3.kind === "user-text");
64370
- return {
64371
- toolSessionId: f2.id,
64372
- summary: firstUser?.text?.slice(0, 80) || "(\u7A7A\u4F1A\u8BDD)",
64373
- messageCount: parsed.messages.length,
64374
- updatedAt: new Date(f2.mtimeMs).toISOString()
64375
- };
64376
- });
64377
- }
64378
- async read(args) {
64379
- const file = locatePiSessionFile(this.deps.sessionsRoot, args.cwd, args.toolSessionId);
64380
- if (!file) throw new Error(`pi session not found for ${args.toolSessionId}`);
64381
- return pageMessages(parsePiSession(import_node_fs38.default.readFileSync(file, "utf8")).messages, args);
64382
- }
64383
- async listSubagents(_args) {
64384
- return [];
64385
- }
64386
- async readSubagent(args) {
64387
- throw new Error(`pi has no subagent threads (${args.subagentId})`);
64388
- }
64389
- computeRewindDiff() {
64390
- return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
64391
- }
64392
- listRewindableUserMessageIds() {
64393
- return [];
64394
- }
64395
- };
64396
-
64397
64741
  // src/tools/pi.ts
64398
64742
  var PI_DEFAULT_CONTEXT_WINDOW = 128e3;
64399
64743
  var PI_THINKING_EFFORTS = [
@@ -64422,20 +64766,20 @@ async function probePi(env = process.env) {
64422
64766
  return { available: false };
64423
64767
  }
64424
64768
  function mapPiModels(raw) {
64425
- const models = arr(rec3(raw)?.models).map(rec3).filter((m3) => m3 !== void 0);
64426
- const providers = new Set(models.map((m3) => str3(m3.provider)).filter(Boolean));
64769
+ const models = arr(rec2(raw)?.models).map(rec2).filter((m3) => m3 !== void 0);
64770
+ const providers = new Set(models.map((m3) => str2(m3.provider)).filter(Boolean));
64427
64771
  const out = [];
64428
64772
  for (const m3 of models) {
64429
- const id = str3(m3.id);
64430
- const provider = str3(m3.provider);
64773
+ const id = str2(m3.id);
64774
+ const provider = str2(m3.provider);
64431
64775
  if (!id || !provider) continue;
64432
- const cw = num3(m3.contextWindow);
64776
+ const cw = num2(m3.contextWindow);
64433
64777
  const levels = arr(m3.thinkingLevels).filter((v3) => typeof v3 === "string");
64434
64778
  const efforts = levels.length ? levels.map((v3) => PI_THINKING_EFFORTS.find((e) => e.value === v3) ?? { value: v3, label: v3 }) : m3.reasoning === true ? PI_THINKING_EFFORTS : [PI_THINKING_EFFORTS[0]];
64435
64779
  out.push({
64436
64780
  id: `${provider}/${id}`,
64437
64781
  // 与 --model 同一串
64438
- label: str3(m3.name) || id,
64782
+ label: str2(m3.name) || id,
64439
64783
  contextWindowSize: cw && cw > 0 ? cw : PI_DEFAULT_CONTEXT_WINDOW,
64440
64784
  efforts,
64441
64785
  // 分组按 provider;只有一个 provider 时不分组(一个横跨全表的标题没信息量,同 codex)
@@ -64501,18 +64845,18 @@ async function queryPiModels(deps = {}) {
64501
64845
  try {
64502
64846
  const r2 = await race(client.request({ type: "get_available_models" }));
64503
64847
  if (!r2.success) throw new Error(r2.error ?? "get_available_models failed");
64504
- const models = arr(rec3(r2.data)?.models).map(rec3).filter((m3) => m3 !== void 0);
64848
+ const models = arr(rec2(r2.data)?.models).map(rec2).filter((m3) => m3 !== void 0);
64505
64849
  const enriched = [];
64506
64850
  for (const m3 of models) {
64507
- const provider = str3(m3.provider);
64508
- const id = str3(m3.id);
64851
+ const provider = str2(m3.provider);
64852
+ const id = str2(m3.id);
64509
64853
  let thinkingLevels;
64510
64854
  if (provider && id && !timedOut) {
64511
64855
  try {
64512
64856
  const set = await race(client.request({ type: "set_model", provider, modelId: id }));
64513
64857
  if (set.success) {
64514
64858
  const lv = await race(client.request({ type: "get_available_thinking_levels" }));
64515
- const levels = arr(rec3(lv.data)?.levels).filter((v3) => typeof v3 === "string");
64859
+ const levels = arr(rec2(lv.data)?.levels).filter((v3) => typeof v3 === "string");
64516
64860
  if (lv.success && levels.length > 0) thinkingLevels = levels;
64517
64861
  }
64518
64862
  } catch {
@@ -64634,42 +64978,6 @@ function createCloudPiSession(ctx, sink, deps) {
64634
64978
  return driver.session;
64635
64979
  }
64636
64980
 
64637
- // src/tools/pi-cloud-history.ts
64638
- var CloudPiHistoryReader = class {
64639
- constructor(client, logger) {
64640
- this.client = client;
64641
- this.logger = logger;
64642
- }
64643
- client;
64644
- logger;
64645
- async listProjects() {
64646
- return [];
64647
- }
64648
- /** 云 persona 没有「关联已有会话」的动线:会话列表就是本机的会话记录 */
64649
- async listSessions() {
64650
- return [];
64651
- }
64652
- async read(args) {
64653
- const text = await this.client.fetchRollout(args.toolSessionId);
64654
- if (text === null) throw new Error(`pi session not found for ${args.toolSessionId}`);
64655
- const parsed = parsePiSession(text);
64656
- if (!parsed.meta) this.logger?.warn("cloud pi session has no header line", { toolSessionId: args.toolSessionId });
64657
- return pageMessages(parsed.messages, args);
64658
- }
64659
- async listSubagents(_args) {
64660
- return [];
64661
- }
64662
- async readSubagent(args) {
64663
- throw new Error(`pi has no subagent threads (${args.subagentId})`);
64664
- }
64665
- computeRewindDiff() {
64666
- return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
64667
- }
64668
- listRewindableUserMessageIds() {
64669
- return [];
64670
- }
64671
- };
64672
-
64673
64981
  // src/tools/pi-cloud.ts
64674
64982
  var CLOUD_APPROVAL2 = { id: "never", label: "Full Access", description: "\u4E91\u4E0A\u65E0\u4EBA\u503C\u5B88\uFF1A\u4ECE\u4E0D\u8BE2\u95EE\uFF0C\u81EA\u4E3B\u6267\u884C" };
64675
64983
  var CLOUD_PI_CAPABILITIES = {
@@ -71686,6 +71994,7 @@ var METHOD_GRANT_MAP = {
71686
71994
  "deploy:finishScan": ADMIN_ANY,
71687
71995
  "deploy:release": ADMIN_ANY,
71688
71996
  "deploy:rerunJob": ADMIN_ANY,
71997
+ "deploy:unpublish": ADMIN_ANY,
71689
71998
  // cloud:connectors:*(云 persona 卡片上的连接器;design 2026-09-11-mcp-connector):
71690
71999
  // 拿本机登录凭证向中心代查 / 代填使用者本人的凭据,guest 一律够不着
71691
72000
  "cloud:connectors:list": ADMIN_ANY,
@@ -71899,6 +72208,7 @@ var HUMAN_ONLY_METHODS = [
71899
72208
  "deploy:release",
71900
72209
  "deploy:putSecret",
71901
72210
  "deploy:rerunJob",
72211
+ "deploy:unpublish",
71902
72212
  "githubAuth:oauthStart",
71903
72213
  "githubAuth:authorize"
71904
72214
  ];
@@ -71943,7 +72253,7 @@ function computeMethodAccess(args) {
71943
72253
  }
71944
72254
 
71945
72255
  // src/version.ts
71946
- var version = "0.2.581".length > 0 ? "0.2.581" : "dev";
72256
+ var version = "0.2.583".length > 0 ? "0.2.583" : "dev";
71947
72257
 
71948
72258
  // src/cli-probe/probe.ts
71949
72259
  var fs80 = __toESM(require("fs"), 1);
@@ -75624,8 +75934,27 @@ async function startDaemon(config) {
75624
75934
  githubIdentity,
75625
75935
  githubApi: (token) => createGithubApi({ token }),
75626
75936
  org: PERSONA_REPO_ORG,
75937
+ // 发布者认领(design 2026-09-24-cloud-persona-conversations §3.1):身份由中心从登录票里取;409 = 名字是别人的
75938
+ claimPublisher: async (personaId2) => {
75939
+ const loginPass = ownerIdentityStore.read()?.loginPass;
75940
+ if (!loginPass) throw new Error("\u8FD8\u6CA1\u767B\u5F55 clawd\uFF1A\u5148\u5728\u8BBE\u7F6E\u91CC\u767B\u5F55\u98DE\u4E66\uFF0C\u518D\u53D1\u5E03");
75941
+ try {
75942
+ await centralClaimPublisher({ api: config.clawosApi ?? DEFAULT_CLAWOS_API, loginPass, personaId: personaId2 });
75943
+ return "ok";
75944
+ } catch (err) {
75945
+ if (err instanceof CentralClientError && err.status === 409) return "taken";
75946
+ throw new Error(`\u5411\u4E2D\u5FC3\u767B\u8BB0\u53D1\u5E03\u8005\u5931\u8D25\uFF1A${err.message}`);
75947
+ }
75948
+ },
75627
75949
  now: () => Date.now(),
75628
75950
  warn: (msg, meta) => logger.warn(msg, meta),
75951
+ // 在不在线上 = gateway 正式登记表里有没有它(design 2026-09-24-deploy-unpublish §3.3):跟 persona 列表的云卡片同一个来源。
75952
+ // 拉不到(没登录飞书 / gateway 不通 / healthz 5s 没回)→ undefined,页面不给下线按钮也不撤「线上」标。
75953
+ // 失败上界:中心那一跳自带 15s,healthz 这一趟 5s——deploy:get 要保证页面打得开,gateway 接了连接不回时不能挂到 undici 默认的 300s
75954
+ cloudOnline: (personaId2) => cloudGateway.listPersonas({ healthzTimeoutMs: 5e3 }).then(
75955
+ (list) => list.some((p3) => p3.personaId === personaId2),
75956
+ () => void 0
75957
+ ),
75629
75958
  startScan: (personaId2, scanBase, scanId) => {
75630
75959
  const baseLines = scanBase.base ? [
75631
75960
  `\u672C\u6B21\u662F**${SCAN_BASE_LABEL[scanBase.kind]}**\uFF1A\u53EA\u770B\u8DDF\u7EBF\u4E0A\u6B63\u8DD1\u90A3\u7248\u7684\u5DEE\u522B\uFF0C`,
@@ -75757,13 +76086,19 @@ async function startDaemon(config) {
75757
76086
  const viewerAssetLoader = tryLoadViewerAssets(logger);
75758
76087
  const shareUiAssetLoader = tryLoadShareUi(logger);
75759
76088
  const dashboardPages = loadDashboardPages(logger);
76089
+ const dashboardApi = () => config.clawosApi ?? DEFAULT_CLAWOS_API;
75760
76090
  const dashboardRoutes = createDashboardRoutes({
75761
76091
  getSecret: () => authFile.token,
75762
76092
  loadPageHtml: (page) => dashboardPages.get(page) ?? null,
75763
- gateway: {
75764
- personas: async () => (await cloudGateway.listPersonas()).map((p3) => p3.personaId),
75765
- metrics: (q3) => cloudGateway.fetchMetrics(q3)
75766
- }
76093
+ data: createDashboardData({
76094
+ loginPass: () => ownerIdentityStore.read()?.loginPass ?? null,
76095
+ registeredPersonas: async () => (await cloudGateway.listPersonas()).map((p3) => p3.personaId),
76096
+ visiblePersonas: (loginPass) => centralVisiblePersonas({ api: dashboardApi(), loginPass }),
76097
+ metrics: (q3) => cloudGateway.fetchMetrics(q3),
76098
+ threads: (loginPass, q3) => centralPersonaThreads({ api: dashboardApi(), loginPass, ...q3 }),
76099
+ thread: (loginPass, q3) => centralPersonaThread({ api: dashboardApi(), loginPass, ...q3 }),
76100
+ logger
76101
+ })
75767
76102
  });
75768
76103
  const httpRouter = createHttpRouter({
75769
76104
  authResolver,