@clawos-dev/clawd 0.2.582 → 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
@@ -7691,7 +7691,7 @@ var init_dashboard_schemas = __esm({
7691
7691
  "../protocol/src/dashboard-schemas.ts"() {
7692
7692
  "use strict";
7693
7693
  init_zod();
7694
- DASHBOARD_PAGES = ["cloud-persona-metrics"];
7694
+ DASHBOARD_PAGES = ["cloud-persona-metrics", "cloud-persona-conversations"];
7695
7695
  DashboardPageSchema = external_exports.enum(DASHBOARD_PAGES);
7696
7696
  DASHBOARD_LINK_TTL_SECONDS = 24 * 3600;
7697
7697
  DashboardOpenUrlArgs = external_exports.object({ page: DashboardPageSchema }).strict();
@@ -15911,13 +15911,13 @@ var init_claude_history = __esm({
15911
15911
  function probeViaWhich() {
15912
15912
  try {
15913
15913
  const out = (0, import_node_child_process2.execFileSync)("which", ["claude"], { encoding: "utf8" }).trim();
15914
- if (out && import_node_fs17.default.existsSync(out)) return out;
15914
+ if (out && import_node_fs20.default.existsSync(out)) return out;
15915
15915
  } catch {
15916
15916
  }
15917
15917
  return null;
15918
15918
  }
15919
15919
  async function probeClaude(env = process.env) {
15920
- 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)) {
15921
15921
  return { available: true, path: env.CLAUDE_BIN };
15922
15922
  }
15923
15923
  const w3 = probeViaWhich();
@@ -16383,13 +16383,13 @@ function encodeClaudeStdin(text) {
16383
16383
  };
16384
16384
  return JSON.stringify(frame) + "\n";
16385
16385
  }
16386
- 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;
16387
16387
  var init_claude = __esm({
16388
16388
  "src/tools/claude.ts"() {
16389
16389
  "use strict";
16390
16390
  import_node_child_process = require("child_process");
16391
16391
  import_node_child_process2 = require("child_process");
16392
- import_node_fs17 = __toESM(require("fs"), 1);
16392
+ import_node_fs20 = __toESM(require("fs"), 1);
16393
16393
  init_protocol();
16394
16394
  init_claude_history();
16395
16395
  init_tool_result_extra();
@@ -51423,19 +51423,37 @@ function verifyDashboardLink(secret, page, eRaw, s2, now = Date.now) {
51423
51423
  }
51424
51424
 
51425
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" };
51426
51441
  var NO_STORE = { "cache-control": "no-store" };
51427
51442
  function send(res, status, contentType, body) {
51428
51443
  res.writeHead(status, { "content-type": contentType, ...NO_STORE });
51429
51444
  res.end(body);
51430
51445
  }
51431
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);
51432
51448
  var PERSONA_RE = /^[a-z0-9-]+$/;
51449
+ var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
51433
51450
  function createDashboardRoutes(deps) {
51434
51451
  const now = deps.now ?? Date.now;
51435
51452
  return async (req, res, url) => {
51436
- const m3 = /^\/dashboard\/([^/]+)(?:\/(data|personas))?$/.exec(url.pathname);
51453
+ const m3 = /^\/dashboard\/([^/]+)(?:\/([a-z]+))?$/.exec(url.pathname);
51437
51454
  const page = m3 && DASHBOARD_PAGES.includes(m3[1]) ? m3[1] : null;
51438
- 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)) {
51439
51457
  sendJson(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
51440
51458
  return true;
51441
51459
  }
@@ -51445,8 +51463,7 @@ function createDashboardRoutes(deps) {
51445
51463
  return true;
51446
51464
  }
51447
51465
  const v3 = verifyDashboardLink(secret, page, url.searchParams.get("e"), url.searchParams.get("s"), now);
51448
- const sub = m3[2];
51449
- if (!sub) {
51466
+ if (sub === void 0) {
51450
51467
  if (!v3.ok && v3.code !== "EXPIRED") {
51451
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");
51452
51469
  return true;
@@ -51463,296 +51480,1523 @@ function createDashboardRoutes(deps) {
51463
51480
  sendJson(res, 403, v3.code === "EXPIRED" ? { code: "LINK_EXPIRED", message: "\u94FE\u63A5\u5DF2\u8FC7\u671F" } : { code: "LINK_INVALID", message: "\u94FE\u63A5\u65E0\u6548" });
51464
51481
  return true;
51465
51482
  }
51483
+ const q3 = url.searchParams;
51466
51484
  try {
51467
51485
  if (sub === "personas") {
51468
- sendJson(res, 200, { personas: await deps.gateway.personas() });
51486
+ sendJson(res, 200, { personas: await deps.data.personas() });
51469
51487
  return true;
51470
51488
  }
51471
- const personaId2 = url.searchParams.get("persona") ?? "";
51489
+ const personaId2 = q3.get("persona") ?? "";
51472
51490
  if (!PERSONA_RE.test(personaId2)) {
51473
51491
  sendJson(res, 400, { code: "INVALID_PARAM", message: "persona \u8981\u662F\u4E91 persona \u540D\uFF08[a-z0-9-]\uFF09" });
51474
51492
  return true;
51475
51493
  }
51476
- const r2 = await deps.gateway.metrics({ personaId: personaId2, window: url.searchParams.get("window"), entry: url.searchParams.get("entry") });
51477
- 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 }));
51478
51514
  } catch (e) {
51479
- 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 });
51480
51520
  }
51481
51521
  return true;
51482
51522
  };
51483
51523
  }
51484
51524
 
51485
- // src/share-ui/load.ts
51486
- var import_node_fs15 = __toESM(require("fs"), 1);
51487
- var import_node_path14 = __toESM(require("path"), 1);
51488
- var import_node_url4 = require("url");
51489
-
51490
- // src/share-ui/asset-loader.ts
51491
- var import_node_fs14 = __toESM(require("fs"), 1);
51492
- var import_node_path13 = __toESM(require("path"), 1);
51493
- function createShareUiAssetLoader(opts) {
51494
- let indexHtml;
51495
- try {
51496
- indexHtml = import_node_fs14.default.readFileSync(opts.indexHtmlPath, "utf8");
51497
- } catch (err) {
51498
- throw new Error(`share-ui index.html not found at ${opts.indexHtmlPath}: ${err.message}`);
51499
- }
51500
- const assetsDir = import_node_path13.default.resolve(opts.assetsDir);
51501
- return {
51502
- renderIndexHtml() {
51503
- return indexHtml;
51504
- },
51505
- resolveAssetPath(rel) {
51506
- const resolved = import_node_path13.default.resolve(assetsDir, rel);
51507
- if (resolved !== assetsDir && !resolved.startsWith(assetsDir + import_node_path13.default.sep)) return null;
51508
- try {
51509
- if (import_node_fs14.default.statSync(resolved).isFile()) return resolved;
51510
- } catch {
51511
- }
51512
- return null;
51513
- }
51514
- };
51525
+ // src/tools/codex-rollout.ts
51526
+ function rec(v3) {
51527
+ return v3 && typeof v3 === "object" && !Array.isArray(v3) ? v3 : void 0;
51515
51528
  }
51516
-
51517
- // src/share-ui/load.ts
51518
- var import_meta4 = {};
51519
- function bundleDirFromArgv(argv1) {
51520
- if (!argv1) return null;
51521
- 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;
51522
51538
  try {
51523
- real = import_node_fs15.default.realpathSync(argv1);
51539
+ return rec(JSON.parse(t2));
51524
51540
  } catch {
51541
+ return void 0;
51525
51542
  }
51526
- return import_node_path14.default.dirname(real);
51527
51543
  }
51528
- function tryLoadShareUi(logger) {
51529
- const candidates = [];
51530
- try {
51531
- const here = import_node_path14.default.dirname((0, import_node_url4.fileURLToPath)(import_meta4.url));
51532
- candidates.push(here);
51533
- candidates.push(import_node_path14.default.resolve(here, ".."));
51534
- candidates.push(import_node_path14.default.resolve(here, "..", "..", "dist"));
51535
- } 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);
51536
51570
  }
51537
- const argvDir = bundleDirFromArgv(process.argv[1]);
51538
- if (argvDir) candidates.push(argvDir);
51539
- for (const root of candidates) {
51540
- const shareUiDir = import_node_path14.default.join(root, "share-ui");
51541
- const indexHtmlPath = import_node_path14.default.join(shareUiDir, "guest.html");
51542
- 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") {
51543
51583
  try {
51544
- if (import_node_fs15.default.statSync(indexHtmlPath).isFile() && import_node_fs15.default.statSync(assetsDir).isDirectory()) {
51545
- logger?.info("share-ui.assets-root.resolved", { root: shareUiDir });
51546
- return createShareUiAssetLoader({ indexHtmlPath, assetsDir });
51547
- }
51584
+ args = JSON.parse(rawArgs);
51548
51585
  } catch {
51586
+ args = { arguments: rawArgs };
51549
51587
  }
51550
51588
  }
51551
- logger?.warn("share-ui.assets-not-found", {
51552
- candidates,
51553
- hint: "run `pnpm -F @clawos/clawd-ui build && pnpm -F @clawos-dev/clawd build`"
51554
- });
51555
- 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" };
51556
51719
  }
51557
51720
 
51558
- // src/visitor/visitor-token.ts
51559
- var import_node_crypto6 = __toESM(require("crypto"), 1);
51560
-
51561
- // ../protocol/src/index.ts
51562
- 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);
51563
51724
 
51564
- // src/visitor/visitor-token.ts
51565
- var DOMAIN_PREFIX3 = "clawd-visitor-v1|";
51566
- function hmac(secret, body) {
51567
- 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;
51568
51733
  }
51569
- function signVisitorToken(secret, payload) {
51570
- const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
51571
- 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;
51572
51744
  }
51573
- function verifyVisitorToken(secret, token, now) {
51574
- const dot = token.indexOf(".");
51575
- if (dot <= 0) return { ok: false, reason: "BAD_FORMAT" };
51576
- const body = token.slice(0, dot);
51577
- const sig = token.slice(dot + 1);
51578
- const expected = hmac(secret, body);
51579
- if (sig.length !== expected.length) return { ok: false, reason: "BAD_SIGNATURE" };
51580
- if (!import_node_crypto6.default.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
51581
- 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;
51582
51759
  }
51583
- 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;
51584
51781
  try {
51585
- const parsed = VisitorTokenPayloadSchema.safeParse(
51586
- JSON.parse(Buffer.from(body, "base64url").toString("utf8"))
51587
- );
51588
- if (!parsed.success) return { ok: false, reason: "BAD_FORMAT" };
51589
- payload = parsed.data;
51782
+ buf = import_node_fs14.default.readFileSync(file);
51590
51783
  } catch {
51591
- return { ok: false, reason: "BAD_FORMAT" };
51784
+ return null;
51592
51785
  }
51593
- if (now >= payload.exp) return { ok: false, reason: "EXPIRED" };
51594
- return { ok: true, payload };
51595
- }
51596
-
51597
- // src/visitor/visitor-login.ts
51598
- function buildVisitorLogin(deps) {
51599
- return async function exchangeVisitorToken(args) {
51600
- 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;
51601
51796
  try {
51602
- id = await deps.fetchUserInfo(args.ttcToken);
51797
+ parsed = JSON.parse(Buffer.from(haystack.slice(lineStart, lineEnd), "latin1").toString("utf8"));
51603
51798
  } catch {
51604
- return { ok: false, reason: "BAD_TTC_TOKEN" };
51799
+ continue;
51605
51800
  }
51606
- deps.visitorStore.upsert({
51607
- visitorId: id.ownerId,
51608
- displayName: id.displayName,
51609
- provider: id.provider,
51610
- ...id.avatarUrl ? { avatarUrl: id.avatarUrl } : {},
51611
- now: deps.now()
51612
- });
51613
- const iat = Math.floor(deps.now() / 1e3);
51614
- const visitorToken = signVisitorToken(deps.getSecret(), {
51615
- v: 1,
51616
- visitorId: id.ownerId,
51617
- displayName: id.displayName,
51618
- provider: id.provider,
51619
- iat,
51620
- exp: iat + deps.ttlSeconds
51621
- });
51622
- return { ok: true, visitorToken };
51623
- };
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;
51624
51808
  }
51625
-
51626
- // src/visitor/visitor-store.ts
51627
- var import_node_fs16 = __toESM(require("fs"), 1);
51628
- var import_node_path15 = __toESM(require("path"), 1);
51629
- function createVisitorStore(opts) {
51630
- const file = import_node_path15.default.join(opts.dir, "visitors.json");
51631
- 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;
51632
51814
  try {
51633
- return JSON.parse(import_node_fs16.default.readFileSync(file, "utf8"));
51815
+ entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
51634
51816
  } catch {
51635
- return [];
51817
+ return;
51636
51818
  }
51637
- };
51638
- const write = (rows) => {
51639
- import_node_fs16.default.mkdirSync(opts.dir, { recursive: true });
51640
- import_node_fs16.default.writeFileSync(file, JSON.stringify(rows, null, 2));
51641
- };
51642
- return {
51643
- upsert(r2) {
51644
- const rows = read();
51645
- const ex = rows.find((x2) => x2.visitorId === r2.visitorId);
51646
- if (ex) {
51647
- ex.displayName = r2.displayName;
51648
- ex.provider = r2.provider;
51649
- ex.lastSeen = r2.now;
51650
- if (r2.avatarUrl) ex.avatarUrl = r2.avatarUrl;
51651
- } else {
51652
- rows.push({
51653
- visitorId: r2.visitorId,
51654
- displayName: r2.displayName,
51655
- provider: r2.provider,
51656
- ...r2.avatarUrl ? { avatarUrl: r2.avatarUrl } : {},
51657
- firstSeen: r2.now,
51658
- lastSeen: r2.now
51659
- });
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;
51660
51824
  }
51661
- write(rows);
51662
- },
51663
- list() {
51664
- return read();
51825
+ const matched = ROLLOUT_NAME_RE.exec(entry.name);
51826
+ if (matched) into.set(matched[1].toLowerCase(), full);
51665
51827
  }
51666
51828
  };
51667
- }
51668
-
51669
- // src/index.ts
51670
- init_claude();
51671
-
51672
- // src/tools/codex.ts
51673
- var import_node_child_process7 = require("child_process");
51674
- var import_node_fs21 = __toESM(require("fs"), 1);
51675
-
51676
- // src/tools/codex-session.ts
51677
- var import_node_child_process4 = require("child_process");
51678
- var import_node_path19 = __toESM(require("path"), 1);
51679
-
51680
- // src/tools/codex-app-server-client.ts
51681
- var import_node_readline = require("readline");
51682
- function stdioLineStream(proc) {
51683
- const rl = (0, import_node_readline.createInterface)({ input: proc.stdout, crlfDelay: Infinity });
51684
- const lineCbs = [];
51685
- const closeCbs = [];
51686
- rl.on("line", (l2) => {
51687
- for (const cb of lineCbs) cb(l2);
51688
- });
51689
- rl.on("close", () => {
51690
- for (const cb of closeCbs) cb();
51691
- });
51692
- return {
51693
- write: (line) => {
51694
- proc.stdin.write(line + "\n");
51695
- },
51696
- onLine: (cb) => {
51697
- lineCbs.push(cb);
51698
- },
51699
- onClose: (cb) => {
51700
- closeCbs.push(cb);
51701
- },
51702
- close: () => {
51703
- proc.stdin?.end();
51829
+ return (toolSessionId) => {
51830
+ if (index === null) {
51831
+ index = /* @__PURE__ */ new Map();
51832
+ for (const root of roots) walk2(root, index);
51704
51833
  }
51834
+ return index.get(toolSessionId.toLowerCase()) ?? null;
51705
51835
  };
51706
51836
  }
51707
- var CodexAppServerClient = class {
51708
- constructor(stream, opts = {}) {
51709
- this.stream = stream;
51710
- this.opts = opts;
51711
- stream.onLine((l2) => this.onLine(l2));
51712
- stream.onClose(() => {
51713
- for (const p3 of this.pending.values()) p3.reject(new Error("app-server connection closed"));
51714
- this.pending.clear();
51715
- this.opts.onClose?.();
51716
- });
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;
51717
51849
  }
51718
- stream;
51719
- opts;
51720
- nextId = 1;
51721
- pending = /* @__PURE__ */ new Map();
51722
- request(method, params) {
51723
- const id = this.nextId++;
51724
- return new Promise((resolve9, reject) => {
51725
- this.pending.set(id, { resolve: resolve9, reject });
51726
- this.write({ jsonrpc: "2.0", method, id, params });
51727
- });
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);
51728
51858
  }
51729
- notify(method, params) {
51730
- 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;
51731
51868
  }
51732
- // server-request 的响应也必须带 jsonrpc:'2.0',否则 codex app-server 忽略它 → 审批/问答永远卡住。
51733
- respond(id, result) {
51734
- 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;
51735
51876
  }
51736
- onLine(line) {
51737
- const t2 = line.trim();
51738
- if (!t2) return;
51739
- let m3;
51740
- try {
51741
- m3 = JSON.parse(t2);
51742
- } catch {
51743
- return;
51744
- }
51745
- if (!isRecord(m3)) return;
51746
- const hasId = typeof m3.id === "number", hasMethod = typeof m3.method === "string";
51747
- if (hasId && !hasMethod) {
51748
- const p3 = this.pending.get(m3.id);
51749
- if (!p3) return;
51750
- this.pending.delete(m3.id);
51751
- if (m3.error) {
51752
- const e = isRecord(m3.error) ? m3.error : {};
51753
- p3.reject(new Error(typeof e.message === "string" ? e.message : "app-server error"));
51754
- } else p3.resolve(m3.result);
51755
- 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;
52994
+ this.pending.delete(m3.id);
52995
+ if (m3.error) {
52996
+ const e = isRecord(m3.error) ? m3.error : {};
52997
+ p3.reject(new Error(typeof e.message === "string" ? e.message : "app-server error"));
52998
+ } else p3.resolve(m3.result);
52999
+ return;
51756
53000
  }
51757
53001
  if (hasId && hasMethod) {
51758
53002
  this.opts.onServerRequest?.(m3.id, m3.method, m3.params);
@@ -51770,49 +53014,49 @@ function isRecord(v3) {
51770
53014
 
51771
53015
  // src/tools/codex-app-server-events.ts
51772
53016
  function translateNotification(method, params) {
51773
- const p3 = rec(params);
53017
+ const p3 = rec3(params);
51774
53018
  switch (method) {
51775
53019
  case "thread/started": {
51776
- const id = str(rec(p3?.thread)?.id);
53020
+ const id = str3(rec3(p3?.thread)?.id);
51777
53021
  return id ? [{ kind: "session_init", toolSessionId: id }] : [];
51778
53022
  }
51779
53023
  case "item/agentMessage/delta": {
51780
- const text = str(p3?.delta);
51781
- const id = str(p3?.itemId);
53024
+ const text = str3(p3?.delta);
53025
+ const id = str3(p3?.itemId);
51782
53026
  return text ? [{ kind: "text", text, ...id ? { partialId: id } : {} }] : [];
51783
53027
  }
51784
53028
  case "item/reasoning/textDelta":
51785
53029
  case "item/reasoning/summaryTextDelta": {
51786
- const text = str(p3?.delta);
51787
- const id = str(p3?.itemId);
53030
+ const text = str3(p3?.delta);
53031
+ const id = str3(p3?.itemId);
51788
53032
  return text ? [{ kind: "thinking", text, ...id ? { partialId: id } : {} }] : [];
51789
53033
  }
51790
53034
  case "item/commandExecution/outputDelta":
51791
53035
  return [];
51792
53036
  // 命令输出增量 v1 不逐字(完成时整段给)
51793
53037
  case "item/started":
51794
- return itemStarted(rec(p3?.item));
53038
+ return itemStarted(rec3(p3?.item));
51795
53039
  case "item/completed":
51796
- return itemCompleted(rec(p3?.item));
53040
+ return itemCompleted(rec3(p3?.item));
51797
53041
  case "thread/tokenUsage/updated":
51798
- return tokenUsage(rec(rec(p3?.tokenUsage)));
53042
+ return tokenUsage(rec3(rec3(p3?.tokenUsage)));
51799
53043
  // codex 自动生成的 thread 标题(实测 0.145:payload = { threadId, threadName })
51800
53044
  case "thread/name/updated": {
51801
- const name = str(p3?.threadName);
53045
+ const name = str3(p3?.threadName);
51802
53046
  return name ? [{ kind: "meta_update", patch: { aiLabel: name } }] : [];
51803
53047
  }
51804
53048
  case "turn/completed":
51805
- return turnCompleted(rec(p3?.turn));
53049
+ return turnCompleted(rec3(p3?.turn));
51806
53050
  default:
51807
53051
  return [];
51808
53052
  }
51809
53053
  }
51810
53054
  function itemStarted(item) {
51811
53055
  if (!item) return [];
51812
- const id = str(item.id);
53056
+ const id = str3(item.id);
51813
53057
  if (!id) return [];
51814
53058
  if (item.type === "commandExecution") {
51815
- 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) ?? "" } }];
51816
53060
  }
51817
53061
  if (item.type === "fileChange") {
51818
53062
  return [{ kind: "tool_call", toolUseId: id, tool: "fileChange", toolKind: "codex", input: { changes: Array.isArray(item.changes) ? item.changes : [] } }];
@@ -51826,28 +53070,28 @@ function itemCompleted(item) {
51826
53070
  return [];
51827
53071
  // 用户输入回显, clawd 自己 synthesize user_text
51828
53072
  case "agentMessage": {
51829
- const text = str(item.text);
53073
+ const text = str3(item.text);
51830
53074
  return text ? [{ kind: "text", text }] : [];
51831
53075
  }
51832
53076
  case "reasoning": {
51833
- const text = str(item.text);
53077
+ const text = str3(item.text);
51834
53078
  return text ? [{ kind: "thinking", text }] : [];
51835
53079
  }
51836
53080
  case "commandExecution": {
51837
- const id = str(item.id);
53081
+ const id = str3(item.id);
51838
53082
  if (!id) return [];
51839
- const exit = num(item.exitCode);
51840
- 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) ?? "" };
51841
53085
  if (exit !== void 0 && exit !== 0) ev.error = `exit ${exit}`;
51842
53086
  return [ev];
51843
53087
  }
51844
53088
  case "fileChange": {
51845
- const id = str(item.id);
53089
+ const id = str3(item.id);
51846
53090
  if (!id) return [];
51847
53091
  const changes = Array.isArray(item.changes) ? item.changes : [];
51848
- 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");
51849
53093
  const ev = { kind: "tool_result", toolUseId: id, output };
51850
- const status = str(item.status);
53094
+ const status = str3(item.status);
51851
53095
  if (status === "failed" || status === "declined") ev.error = `patch ${status}`;
51852
53096
  return [ev];
51853
53097
  }
@@ -51857,26 +53101,26 @@ function itemCompleted(item) {
51857
53101
  }
51858
53102
  function tokenUsage(usage) {
51859
53103
  if (!usage) return [];
51860
- const last = rec(usage.last);
53104
+ const last = rec3(usage.last);
51861
53105
  if (!last) return [];
51862
- const i2 = num(last.inputTokens), o2 = num(last.outputTokens);
53106
+ const i2 = num3(last.inputTokens), o2 = num3(last.outputTokens);
51863
53107
  const patch = {};
51864
53108
  if (i2 !== void 0 && o2 !== void 0) patch.contextUsage = { inputTokens: i2, outputTokens: o2 };
51865
- const w3 = num(usage.modelContextWindow);
53109
+ const w3 = num3(usage.modelContextWindow);
51866
53110
  if (w3 !== void 0) patch.contextWindowSize = w3;
51867
53111
  return Object.keys(patch).length ? [{ kind: "meta_update", patch }] : [];
51868
53112
  }
51869
53113
  function turnCompleted(turn) {
51870
- 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" }];
51871
53115
  return [{ kind: "turn_end" }];
51872
53116
  }
51873
- function rec(v3) {
53117
+ function rec3(v3) {
51874
53118
  return typeof v3 === "object" && v3 !== null ? v3 : void 0;
51875
53119
  }
51876
- function str(v3) {
53120
+ function str3(v3) {
51877
53121
  return typeof v3 === "string" ? v3 : void 0;
51878
53122
  }
51879
- function num(v3) {
53123
+ function num3(v3) {
51880
53124
  return typeof v3 === "number" ? v3 : void 0;
51881
53125
  }
51882
53126
 
@@ -52340,17 +53584,17 @@ function stringValue2(value) {
52340
53584
  }
52341
53585
 
52342
53586
  // src/tools/mcp-server-defs.ts
52343
- var import_node_fs18 = __toESM(require("fs"), 1);
52344
- 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);
52345
53589
  var HTTP_TYPES = /* @__PURE__ */ new Set(["http", "sse", "streamable_http", "streamable-http"]);
52346
53590
  function loadMcpServerDefs(args) {
52347
53591
  const out = [];
52348
53592
  const seen = /* @__PURE__ */ new Set();
52349
- 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")];
52350
53594
  for (const file of sources) {
52351
53595
  let parsed;
52352
53596
  try {
52353
- parsed = JSON.parse(import_node_fs18.default.readFileSync(file, "utf8"));
53597
+ parsed = JSON.parse(import_node_fs21.default.readFileSync(file, "utf8"));
52354
53598
  } catch (e) {
52355
53599
  const code = e.code;
52356
53600
  if (code !== "ENOENT") {
@@ -52414,18 +53658,18 @@ function loadMcpServerDefs(args) {
52414
53658
  }
52415
53659
 
52416
53660
  // src/tools/codex-agents-config.ts
52417
- var import_node_fs19 = __toESM(require("fs"), 1);
52418
- 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);
52419
53663
  var AGENTS_SECTION = /^\[agents\][^\n]*\n([\s\S]*?)(?=^\[|$(?![\s\S]))/m;
52420
53664
  function readKey(section, key) {
52421
53665
  const m3 = new RegExp(`^[ \\t]*${key}[ \\t]*=[ \\t]*(["'])(.*?)\\1`, "m").exec(section);
52422
53666
  return m3?.[2].trim() || void 0;
52423
53667
  }
52424
53668
  function loadCodexAgentsConfig(args) {
52425
- 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");
52426
53670
  let text;
52427
53671
  try {
52428
- text = import_node_fs19.default.readFileSync(file, "utf8");
53672
+ text = import_node_fs22.default.readFileSync(file, "utf8");
52429
53673
  } catch (e) {
52430
53674
  const code = e.code;
52431
53675
  if (code !== "ENOENT") {
@@ -52481,7 +53725,7 @@ function warmCodexFeatureCache() {
52481
53725
  }
52482
53726
 
52483
53727
  // src/attachment/mime.ts
52484
- var import_node_path18 = __toESM(require("path"), 1);
53728
+ var import_node_path21 = __toESM(require("path"), 1);
52485
53729
  var TEXT_PLAIN = "text/plain; charset=utf-8";
52486
53730
  var EXT_TO_NATIVE_MIME = {
52487
53731
  // 图片
@@ -52588,7 +53832,7 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
52588
53832
  ".mk"
52589
53833
  ]);
52590
53834
  function lookupMime(filePathOrName) {
52591
- const ext = import_node_path18.default.extname(filePathOrName).toLowerCase();
53835
+ const ext = import_node_path21.default.extname(filePathOrName).toLowerCase();
52592
53836
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
52593
53837
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
52594
53838
  return "application/octet-stream";
@@ -52637,7 +53881,7 @@ function createCodexSession(ctx, sink, deps = {}) {
52637
53881
  threadParams: threadStartParams(ctx),
52638
53882
  // persona 惯例装在 <cwd>/.claude/skills/,经 extraRoots 注入 discovery
52639
53883
  // (skills:list RPC 侧 codex-skills.ts 同款注入,两处保持一致)。
52640
- skillsExtraRoots: [import_node_path19.default.join(ctx.cwd, ".claude", "skills")],
53884
+ skillsExtraRoots: [import_node_path22.default.join(ctx.cwd, ".claude", "skills")],
52641
53885
  // 附件图片:验签认出本机 absPath → localImage,codex 自己读文件(TUI 拖图 / SDK local_image 同一条路)。
52642
53886
  // 0.145 起 http(s) URL 在 turn/start 入口被拒,不能再按 url 传(codex-app-server-params.ts 头注释)。
52643
53887
  // 与云上 cloudImageInput 同一道 mime 门:非图片(签过的 .md 之类)留文本,别让 codex 读到一半才炸。
@@ -52791,18 +54035,18 @@ function mergeProviderModels(native, provider) {
52791
54035
 
52792
54036
  // src/tools/codex-provider-models.ts
52793
54037
  var import_node_child_process6 = require("child_process");
52794
- var import_node_fs20 = __toESM(require("fs"), 1);
52795
- var import_node_os8 = __toESM(require("os"), 1);
52796
- 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);
52797
54041
  var import_node_util = require("util");
52798
54042
  var PROVIDER_FETCH_TIMEOUT_MS = 3e3;
52799
- function asRecord(v3) {
54043
+ function asRecord2(v3) {
52800
54044
  return v3 && typeof v3 === "object" && !Array.isArray(v3) ? v3 : null;
52801
54045
  }
52802
54046
  function readAuthKey(codexHome) {
52803
54047
  try {
52804
- const raw = JSON.parse(import_node_fs20.default.readFileSync(import_node_path20.default.join(codexHome, "auth.json"), "utf8"));
52805
- 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;
52806
54050
  return typeof key === "string" && key ? key : void 0;
52807
54051
  } catch {
52808
54052
  return void 0;
@@ -52829,19 +54073,19 @@ async function runAuthCommand(name, auth, deps) {
52829
54073
  return void 0;
52830
54074
  }
52831
54075
  async function resolveProviderEndpoint(rawConfig, deps = {}) {
52832
- const config = asRecord(asRecord(rawConfig)?.config);
54076
+ const config = asRecord2(asRecord2(rawConfig)?.config);
52833
54077
  if (!config) return null;
52834
54078
  const name = config.model_provider;
52835
54079
  if (typeof name !== "string" || !name) return null;
52836
- const entry = asRecord(asRecord(config.model_providers)?.[name]);
54080
+ const entry = asRecord2(asRecord2(config.model_providers)?.[name]);
52837
54081
  if (!entry) return null;
52838
54082
  const baseUrl = entry.base_url;
52839
54083
  if (typeof baseUrl !== "string" || !baseUrl) return null;
52840
54084
  const env = deps.env ?? process.env;
52841
- 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");
52842
54086
  const envKey = typeof entry.env_key === "string" && entry.env_key ? entry.env_key : null;
52843
54087
  const inlineToken = typeof entry.experimental_bearer_token === "string" && entry.experimental_bearer_token ? entry.experimental_bearer_token : void 0;
52844
- const auth = asRecord(entry.auth);
54088
+ const auth = asRecord2(entry.auth);
52845
54089
  let token;
52846
54090
  if (envKey) token = env[envKey];
52847
54091
  else if (inlineToken) token = inlineToken;
@@ -52869,11 +54113,11 @@ async function fetchProviderModels(endpoint, deps = {}) {
52869
54113
  try {
52870
54114
  const res = await fetchImpl(url, { headers: endpoint.headers, signal: controller.signal });
52871
54115
  if (!res.ok) throw new Error(`provider model list failed: HTTP ${res.status} from ${url}`);
52872
- const data = asRecord(await res.json())?.data;
54116
+ const data = asRecord2(await res.json())?.data;
52873
54117
  if (!Array.isArray(data)) throw new Error(`provider model list: \`data\` is not an array (${url})`);
52874
54118
  const out = [];
52875
54119
  for (const item of data) {
52876
- const e = asRecord(item);
54120
+ const e = asRecord2(item);
52877
54121
  if (!e || typeof e.id !== "string" || !e.id) continue;
52878
54122
  out.push({ id: e.id, ...typeof e.owned_by === "string" ? { ownedBy: e.owned_by } : {} });
52879
54123
  }
@@ -52953,10 +54197,10 @@ function sanitizeCodexModel(model) {
52953
54197
  return model;
52954
54198
  }
52955
54199
  async function probeCodex(env = process.env) {
52956
- 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 };
52957
54201
  try {
52958
54202
  const out = (0, import_node_child_process7.execFileSync)("which", ["codex"], { encoding: "utf8" }).trim();
52959
- 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 };
52960
54204
  } catch {
52961
54205
  }
52962
54206
  return { available: false };
@@ -53076,9 +54320,9 @@ var CodexAdapter = class {
53076
54320
 
53077
54321
  // src/tools/claude-tui.ts
53078
54322
  var import_node_crypto7 = require("crypto");
53079
- var import_node_fs22 = __toESM(require("fs"), 1);
53080
- var import_node_os9 = __toESM(require("os"), 1);
53081
- 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);
53082
54326
  var import_headless = __toESM(require_xterm_headless(), 1);
53083
54327
 
53084
54328
  // ../node_modules/.pnpm/@xterm+addon-serialize@0.14.0/node_modules/@xterm/addon-serialize/lib/addon-serialize.mjs
@@ -54303,10 +55547,10 @@ function buildTuiSpawnArgs(ctx, isResume = false) {
54303
55547
  }
54304
55548
  function jsonlExistsForCtx(ctx) {
54305
55549
  if (!ctx.toolSessionId) return false;
54306
- const home = import_node_os9.default.homedir();
54307
- 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`);
54308
55552
  try {
54309
- return import_node_fs22.default.statSync(file).isFile();
55553
+ return import_node_fs25.default.statSync(file).isFile();
54310
55554
  } catch {
54311
55555
  return false;
54312
55556
  }
@@ -54391,7 +55635,7 @@ var PersonaDispatchManager = class {
54391
55635
 
54392
55636
  // src/dispatch/store.ts
54393
55637
  var import_promises = __toESM(require("fs/promises"), 1);
54394
- var import_node_path22 = __toESM(require("path"), 1);
55638
+ var import_node_path25 = __toESM(require("path"), 1);
54395
55639
 
54396
55640
  // src/shift/constants.ts
54397
55641
  var MAX_RUNS_PER_SHIFT = 30;
@@ -54429,7 +55673,7 @@ function createDispatchStore(deps) {
54429
55673
  flushTimer = null;
54430
55674
  }
54431
55675
  const content = { version: 1, records };
54432
- 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 });
54433
55677
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
54434
55678
  await import_promises.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
54435
55679
  await import_promises.default.rename(tmp, deps.filePath);
@@ -54498,7 +55742,7 @@ function createDispatchStore(deps) {
54498
55742
 
54499
55743
  // src/channel/json-store.ts
54500
55744
  var import_promises2 = __toESM(require("fs/promises"), 1);
54501
- var import_node_path23 = __toESM(require("path"), 1);
55745
+ var import_node_path26 = __toESM(require("path"), 1);
54502
55746
  function createJsonStore(deps) {
54503
55747
  let items = [];
54504
55748
  let flushTimer = null;
@@ -54509,7 +55753,7 @@ function createJsonStore(deps) {
54509
55753
  flushTimer = null;
54510
55754
  }
54511
55755
  const content = { version: version2, [deps.key]: items };
54512
- 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 });
54513
55757
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
54514
55758
  await import_promises2.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
54515
55759
  await import_promises2.default.rename(tmp, deps.filePath);
@@ -54801,10 +56045,10 @@ function createTopicStore(deps) {
54801
56045
  }
54802
56046
 
54803
56047
  // src/dispatch/mcp-config.ts
54804
- var import_node_fs23 = __toESM(require("fs"), 1);
54805
- 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);
54806
56050
  function dispatchMcpConfigPath(dataDir) {
54807
- return import_node_path24.default.join(dataDir, "dispatch.mcp.json");
56051
+ return import_node_path27.default.join(dataDir, "dispatch.mcp.json");
54808
56052
  }
54809
56053
  function writeDispatchMcpConfig(args) {
54810
56054
  const cfgPath = dispatchMcpConfigPath(args.dataDir);
@@ -54823,16 +56067,16 @@ function writeDispatchMcpConfig(args) {
54823
56067
  }
54824
56068
  }
54825
56069
  };
54826
- import_node_fs23.default.mkdirSync(args.dataDir, { recursive: true });
54827
- 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");
54828
56072
  return cfgPath;
54829
56073
  }
54830
56074
 
54831
56075
  // src/ticket/mcp-config.ts
54832
- var import_node_fs24 = __toESM(require("fs"), 1);
54833
- 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);
54834
56078
  function ticketMcpConfigPath(dataDir) {
54835
- return import_node_path25.default.join(dataDir, "ticket.mcp.json");
56079
+ return import_node_path28.default.join(dataDir, "ticket.mcp.json");
54836
56080
  }
54837
56081
  function writeTicketMcpConfig(args) {
54838
56082
  const cfgPath = ticketMcpConfigPath(args.dataDir);
@@ -54854,16 +56098,16 @@ function writeTicketMcpConfig(args) {
54854
56098
  }
54855
56099
  }
54856
56100
  };
54857
- import_node_fs24.default.mkdirSync(args.dataDir, { recursive: true });
54858
- 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");
54859
56103
  return cfgPath;
54860
56104
  }
54861
56105
 
54862
56106
  // src/shift/mcp-config.ts
54863
- var import_node_fs25 = __toESM(require("fs"), 1);
54864
- 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);
54865
56109
  function shiftMcpConfigPath(dataDir) {
54866
- return import_node_path26.default.join(dataDir, "shift.mcp.json");
56110
+ return import_node_path29.default.join(dataDir, "shift.mcp.json");
54867
56111
  }
54868
56112
  async function writeShiftMcpConfig(args) {
54869
56113
  const cfgPath = shiftMcpConfigPath(args.dataDir);
@@ -54882,16 +56126,16 @@ async function writeShiftMcpConfig(args) {
54882
56126
  }
54883
56127
  }
54884
56128
  };
54885
- await import_node_fs25.default.promises.mkdir(args.dataDir, { recursive: true });
54886
- 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");
54887
56131
  return cfgPath;
54888
56132
  }
54889
56133
 
54890
56134
  // src/inbox/mcp-config.ts
54891
- var import_node_fs26 = __toESM(require("fs"), 1);
54892
- 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);
54893
56137
  function inboxMcpConfigPath(dataDir) {
54894
- return import_node_path27.default.join(dataDir, "inbox.mcp.json");
56138
+ return import_node_path30.default.join(dataDir, "inbox.mcp.json");
54895
56139
  }
54896
56140
  async function writeInboxMcpConfig(args) {
54897
56141
  const cfgPath = inboxMcpConfigPath(args.dataDir);
@@ -54910,16 +56154,16 @@ async function writeInboxMcpConfig(args) {
54910
56154
  }
54911
56155
  }
54912
56156
  };
54913
- await import_node_fs26.default.promises.mkdir(args.dataDir, { recursive: true });
54914
- 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");
54915
56159
  return cfgPath;
54916
56160
  }
54917
56161
 
54918
56162
  // src/peer-ops/mcp-config.ts
54919
- var import_node_fs27 = __toESM(require("fs"), 1);
54920
- 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);
54921
56165
  function peerOpsMcpConfigPath(dataDir) {
54922
- return import_node_path28.default.join(dataDir, "peer-ops.mcp.json");
56166
+ return import_node_path31.default.join(dataDir, "peer-ops.mcp.json");
54923
56167
  }
54924
56168
  function writePeerOpsMcpConfig(args) {
54925
56169
  const cfgPath = peerOpsMcpConfigPath(args.dataDir);
@@ -54936,16 +56180,16 @@ function writePeerOpsMcpConfig(args) {
54936
56180
  }
54937
56181
  }
54938
56182
  };
54939
- import_node_fs27.default.mkdirSync(args.dataDir, { recursive: true });
54940
- 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));
54941
56185
  return cfgPath;
54942
56186
  }
54943
56187
 
54944
56188
  // src/rpc-tool/mcp-config.ts
54945
- var import_node_fs28 = __toESM(require("fs"), 1);
54946
- 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);
54947
56191
  function rpcToolMcpConfigPath(dataDir) {
54948
- return import_node_path29.default.join(dataDir, "rpc-tool.mcp.json");
56192
+ return import_node_path32.default.join(dataDir, "rpc-tool.mcp.json");
54949
56193
  }
54950
56194
  function writeRpcToolMcpConfig(args) {
54951
56195
  const cfgPath = rpcToolMcpConfigPath(args.dataDir);
@@ -54964,29 +56208,29 @@ function writeRpcToolMcpConfig(args) {
54964
56208
  }
54965
56209
  }
54966
56210
  };
54967
- import_node_fs28.default.mkdirSync(args.dataDir, { recursive: true });
54968
- 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");
54969
56213
  return cfgPath;
54970
56214
  }
54971
56215
 
54972
56216
  // src/deploy/secret-table.ts
54973
- var import_node_fs29 = __toESM(require("fs"), 1);
54974
- 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);
54975
56219
  var import_node_url5 = require("url");
54976
56220
  var import_meta5 = {};
54977
56221
  var LOCAL_SECRETS_DIRNAME = "secrets";
54978
56222
  function findSecretsBundleRoot(logger) {
54979
56223
  const candidates = [];
54980
56224
  try {
54981
- const here = import_node_path30.default.dirname((0, import_node_url5.fileURLToPath)(import_meta5.url));
54982
- 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"));
54983
56227
  } catch {
54984
56228
  }
54985
56229
  const fromArgv = bundleSiblingFromArgv(process.argv[1], "secrets");
54986
56230
  if (fromArgv) candidates.push(fromArgv);
54987
56231
  for (const c2 of candidates) {
54988
56232
  try {
54989
- if (import_node_fs29.default.statSync(c2).isDirectory()) return c2;
56233
+ if (import_node_fs32.default.statSync(c2).isDirectory()) return c2;
54990
56234
  } catch {
54991
56235
  }
54992
56236
  }
@@ -55012,7 +56256,7 @@ function readDir(dir, logger) {
55012
56256
  const out = {};
55013
56257
  let files;
55014
56258
  try {
55015
- 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();
55016
56260
  } catch (e) {
55017
56261
  const code = e.code;
55018
56262
  if (code !== "ENOENT") logger?.warn("secrets.dir-unreadable", { dir, code: code ?? "unknown" });
@@ -55020,19 +56264,19 @@ function readDir(dir, logger) {
55020
56264
  }
55021
56265
  for (const f2 of files) {
55022
56266
  try {
55023
- 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")));
55024
56268
  } catch (e) {
55025
- 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 });
55026
56270
  }
55027
56271
  }
55028
56272
  return out;
55029
56273
  }
55030
56274
  function upsertEnvFileVar(file, key, value) {
55031
56275
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`invalid env name: ${key}`);
55032
- 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 });
55033
56277
  let lines = [];
55034
56278
  try {
55035
- lines = import_node_fs29.default.readFileSync(file, "utf8").split("\n");
56279
+ lines = import_node_fs32.default.readFileSync(file, "utf8").split("\n");
55036
56280
  if (lines[lines.length - 1] === "") lines.pop();
55037
56281
  } catch {
55038
56282
  }
@@ -55040,12 +56284,12 @@ function upsertEnvFileVar(file, key, value) {
55040
56284
  let replaced = false;
55041
56285
  const out = lines.map((l2) => re2.test(l2) ? (replaced = true, `${key}=${value}`) : l2);
55042
56286
  if (!replaced) out.push(`${key}=${value}`);
55043
- import_node_fs29.default.writeFileSync(file, `${out.join("\n")}
56287
+ import_node_fs32.default.writeFileSync(file, `${out.join("\n")}
55044
56288
  `, { encoding: "utf8", mode: 384 });
55045
56289
  }
55046
56290
  function readEnvFileVar(file, key) {
55047
56291
  try {
55048
- return parseEnvFile(import_node_fs29.default.readFileSync(file, "utf8"))[key];
56292
+ return parseEnvFile(import_node_fs32.default.readFileSync(file, "utf8"))[key];
55049
56293
  } catch {
55050
56294
  return void 0;
55051
56295
  }
@@ -55054,7 +56298,7 @@ function removeEnvFileVar(file, key) {
55054
56298
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`invalid env name: ${key}`);
55055
56299
  let lines;
55056
56300
  try {
55057
- lines = import_node_fs29.default.readFileSync(file, "utf8").split("\n");
56301
+ lines = import_node_fs32.default.readFileSync(file, "utf8").split("\n");
55058
56302
  } catch {
55059
56303
  return;
55060
56304
  }
@@ -55063,10 +56307,10 @@ function removeEnvFileVar(file, key) {
55063
56307
  const out = lines.filter((l2) => !re2.test(l2));
55064
56308
  if (out.length === lines.length) return;
55065
56309
  if (out.every((l2) => l2.trim() === "" || l2.trim().startsWith("#"))) {
55066
- import_node_fs29.default.rmSync(file, { force: true });
56310
+ import_node_fs32.default.rmSync(file, { force: true });
55067
56311
  return;
55068
56312
  }
55069
- import_node_fs29.default.writeFileSync(file, `${out.join("\n")}
56313
+ import_node_fs32.default.writeFileSync(file, `${out.join("\n")}
55070
56314
  `, { encoding: "utf8", mode: 384 });
55071
56315
  }
55072
56316
  function readSecretTable(args) {
@@ -55077,12 +56321,12 @@ function readSecretTable(args) {
55077
56321
  }
55078
56322
 
55079
56323
  // src/dispatch/source-transcript.ts
55080
- var import_node_path31 = __toESM(require("path"), 1);
56324
+ var import_node_path34 = __toESM(require("path"), 1);
55081
56325
  init_claude_history();
55082
56326
  var NO_SOURCE_TRANSCRIPT = "(no transcript yet \u2014 operate from the task description alone)";
55083
56327
  function resolveSourceJsonlPath(sourceFile, home) {
55084
56328
  if (!sourceFile?.toolSessionId) return NO_SOURCE_TRANSCRIPT;
55085
- return import_node_path31.default.join(
56329
+ return import_node_path34.default.join(
55086
56330
  home,
55087
56331
  ".claude",
55088
56332
  "projects",
@@ -55093,7 +56337,7 @@ function resolveSourceJsonlPath(sourceFile, home) {
55093
56337
 
55094
56338
  // src/shift/store.ts
55095
56339
  var import_promises3 = __toESM(require("fs/promises"), 1);
55096
- var import_node_path32 = __toESM(require("path"), 1);
56340
+ var import_node_path35 = __toESM(require("path"), 1);
55097
56341
  var import_node_crypto8 = require("crypto");
55098
56342
 
55099
56343
  // src/shift/schedule.ts
@@ -55168,7 +56412,7 @@ function createShiftStore(deps) {
55168
56412
  flushTimer = null;
55169
56413
  }
55170
56414
  const content = { version: 1, shifts };
55171
- 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 });
55172
56416
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
55173
56417
  await import_promises3.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
55174
56418
  await import_promises3.default.rename(tmp, deps.filePath);
@@ -55622,14 +56866,14 @@ function redactEvidence(evidence) {
55622
56866
 
55623
56867
  // src/deploy/deploy-file.ts
55624
56868
  var import_promises4 = __toESM(require("fs/promises"), 1);
55625
- var import_node_path34 = __toESM(require("path"), 1);
56869
+ var import_node_path37 = __toESM(require("path"), 1);
55626
56870
  var import_node_crypto9 = require("crypto");
55627
56871
  init_zod();
55628
56872
  init_protocol();
55629
56873
 
55630
56874
  // src/deploy/connectors.ts
55631
- var import_node_fs30 = __toESM(require("fs"), 1);
55632
- 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);
55633
56877
  init_zod();
55634
56878
  init_protocol();
55635
56879
  var PLACEHOLDER2 = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -55679,11 +56923,11 @@ var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "uvx", "pnpx", "bunx"]);
55679
56923
  var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["node", "bun", "deno", "python", "python3"]);
55680
56924
  var HOST_ARG_PLACEHOLDER = "CLAWD_PERSONA_DIR";
55681
56925
  function scanMcpConnectors(personaDir) {
55682
- const file = import_node_path33.default.join(personaDir, ".mcp.json");
56926
+ const file = import_node_path36.default.join(personaDir, ".mcp.json");
55683
56927
  const out = { readable: true, connectors: [], evidence: [], warnings: [] };
55684
56928
  let parsed;
55685
56929
  try {
55686
- parsed = JSON.parse(import_node_fs30.default.readFileSync(file, "utf8"));
56930
+ parsed = JSON.parse(import_node_fs33.default.readFileSync(file, "utf8"));
55687
56931
  } catch (e) {
55688
56932
  if (e.code === "ENOENT") return out;
55689
56933
  out.readable = false;
@@ -55778,8 +57022,8 @@ function mergeScannedConnectors(existing, scanned) {
55778
57022
  }
55779
57023
 
55780
57024
  // src/deploy/deploy-file.ts
55781
- var DEPLOY_FILE = import_node_path34.default.join(".clawd", "deploy.json");
55782
- 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");
55783
57027
  async function readJson(file) {
55784
57028
  let raw;
55785
57029
  try {
@@ -55795,14 +57039,14 @@ async function readJson(file) {
55795
57039
  }
55796
57040
  }
55797
57041
  async function writeJson(file, value) {
55798
- 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 });
55799
57043
  const tmp = `${file}.tmp-${process.pid}`;
55800
57044
  await import_promises4.default.writeFile(tmp, `${JSON.stringify(value, null, 2)}
55801
57045
  `);
55802
57046
  await import_promises4.default.rename(tmp, file);
55803
57047
  }
55804
57048
  async function readDeployFile(dir) {
55805
- 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));
55806
57050
  if (raw === void 0) return void 0;
55807
57051
  const parsed = DeployFileSchema.safeParse(raw);
55808
57052
  if (!parsed.success) {
@@ -55812,18 +57056,18 @@ async function readDeployFile(dir) {
55812
57056
  return parsed.data;
55813
57057
  }
55814
57058
  async function writeDeployFile(dir, file) {
55815
- 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));
55816
57060
  }
55817
57061
  async function readDeployFileRaw(dir) {
55818
57062
  try {
55819
- 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");
55820
57064
  } catch (err) {
55821
57065
  if (err.code === "ENOENT") return void 0;
55822
57066
  throw err;
55823
57067
  }
55824
57068
  }
55825
57069
  async function restoreDeployFileRaw(dir, raw) {
55826
- const file = import_node_path34.default.join(dir, DEPLOY_FILE);
57070
+ const file = import_node_path37.default.join(dir, DEPLOY_FILE);
55827
57071
  if (raw === void 0) await import_promises4.default.rm(file, { force: true });
55828
57072
  else await import_promises4.default.writeFile(file, raw);
55829
57073
  }
@@ -55864,7 +57108,7 @@ function normalizeLegacyScan(raw) {
55864
57108
  return fix(raw);
55865
57109
  }
55866
57110
  async function readScanFile(dir) {
55867
- 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)));
55868
57112
  if (raw === void 0) return void 0;
55869
57113
  const badShape = (issues) => {
55870
57114
  const first = issues[0];
@@ -55885,7 +57129,7 @@ async function readScanFile(dir) {
55885
57129
  return parsed.data;
55886
57130
  }
55887
57131
  async function writeScanFile(dir, file) {
55888
- 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));
55889
57133
  }
55890
57134
  function replaceScan(file, entry) {
55891
57135
  const idx = file.scans.findIndex((s2) => s2.id === entry.id);
@@ -55968,9 +57212,9 @@ function requiredEnvNames(manifest) {
55968
57212
 
55969
57213
  // src/deploy/artifact.ts
55970
57214
  var import_node_crypto10 = require("crypto");
55971
- var import_node_fs31 = __toESM(require("fs"), 1);
55972
- var import_node_os10 = __toESM(require("os"), 1);
55973
- 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);
55974
57218
  var import_node_stream3 = require("stream");
55975
57219
  var import_promises5 = require("stream/promises");
55976
57220
  var ARTIFACT_MAX_BYTES = 200 * 1024 * 1024;
@@ -56004,8 +57248,8 @@ function downloadArtifact(exec) {
56004
57248
  if (!res.ok || !res.body) throw new Error(`\u4E0B\u8F7D\u5931\u8D25 HTTP ${res.status}\uFF1A${url}`);
56005
57249
  const declared = Number(res.headers.get("content-length") ?? 0);
56006
57250
  if (declared > ARTIFACT_MAX_BYTES) throw new Error(`\u8D44\u4EA7 ${declared} \u5B57\u8282\uFF0C\u8D85\u8FC7\u4E0A\u9650 ${ARTIFACT_MAX_BYTES}\uFF1A${url}`);
56007
- const dir = await import_node_fs31.default.promises.mkdtemp(import_node_path35.default.join(import_node_os10.default.tmpdir(), "clawd-artifact-"));
56008
- 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");
56009
57253
  const hash = (0, import_node_crypto10.createHash)("sha256");
56010
57254
  let size = 0;
56011
57255
  const meter = new import_node_stream3.Transform({
@@ -56017,7 +57261,7 @@ function downloadArtifact(exec) {
56017
57261
  }
56018
57262
  });
56019
57263
  try {
56020
- 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));
56021
57265
  let entries;
56022
57266
  if (listEntries) {
56023
57267
  const r2 = await exec("tar", ["-tzf", file], { timeoutMs: 3e4 });
@@ -56026,7 +57270,7 @@ function downloadArtifact(exec) {
56026
57270
  }
56027
57271
  return { sha256: hash.digest("hex"), size, ...entries ? { entries } : {} };
56028
57272
  } finally {
56029
- 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 });
56030
57274
  }
56031
57275
  };
56032
57276
  }
@@ -56077,7 +57321,7 @@ init_protocol();
56077
57321
 
56078
57322
  // src/deploy/repo.ts
56079
57323
  var import_promises6 = __toESM(require("fs/promises"), 1);
56080
- var import_node_path36 = __toESM(require("path"), 1);
57324
+ var import_node_path39 = __toESM(require("path"), 1);
56081
57325
  init_protocol();
56082
57326
 
56083
57327
  // ../node_modules/.pnpm/libsodium@0.8.4/node_modules/libsodium/dist/modules-esm/libsodium.mjs
@@ -60019,7 +61263,7 @@ var runCommand = (cmd, args, opts = {}) => new Promise((resolve9) => {
60019
61263
  });
60020
61264
 
60021
61265
  // src/deploy/repo.ts
60022
- 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("/");
60023
61267
  var PERSONA_FILE_REL = ".clawd/persona.json";
60024
61268
  var DIRTY_IGNORED = [DEPLOY_FILE_REL, PERSONA_FILE_REL];
60025
61269
  var RepoError = class extends Error {
@@ -60034,6 +61278,7 @@ async function mustGit(exec, dir, args, what, opts) {
60034
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)}`);
60035
61279
  return r2.stdout.trim();
60036
61280
  }
61281
+ var cloudPersonaIdOf = (originUrl) => personaIdOf(repoSlug(originUrl).split("/").pop() ?? "");
60037
61282
  var INLINE_HELPER = '!f() { echo username=x-access-token; echo "password=$CLAWD_GITHUB_TOKEN"; }; f';
60038
61283
  async function gitPush(exec, dir, auth) {
60039
61284
  if (auth.source === "file") {
@@ -60058,7 +61303,7 @@ async function samePath(a2, b3) {
60058
61303
  try {
60059
61304
  return await import_promises6.default.realpath(x2);
60060
61305
  } catch {
60061
- return import_node_path36.default.resolve(x2);
61306
+ return import_node_path39.default.resolve(x2);
60062
61307
  }
60063
61308
  };
60064
61309
  return await real(a2) === await real(b3);
@@ -60084,7 +61329,7 @@ async function identityArgs(exec, dir) {
60084
61329
  return ["-c", "user.name=clawd", "-c", "user.email=clawd@clawos.local"];
60085
61330
  }
60086
61331
  async function writeGitignore(dir) {
60087
- const gi = import_node_path36.default.join(dir, ".gitignore");
61332
+ const gi = import_node_path39.default.join(dir, ".gitignore");
60088
61333
  let existing;
60089
61334
  try {
60090
61335
  existing = await import_promises6.default.readFile(gi, "utf8");
@@ -60095,14 +61340,14 @@ async function writeGitignore(dir) {
60095
61340
  }
60096
61341
  async function writeWorkflow(dir) {
60097
61342
  for (const [rel, body] of [[WORKFLOW_PATH, WORKFLOW_TEMPLATE], [UNPUBLISH_WORKFLOW_PATH, UNPUBLISH_WORKFLOW_TEMPLATE]]) {
60098
- const wf = import_node_path36.default.join(dir, rel);
60099
- await import_promises6.default.mkdir(import_node_path36.default.dirname(wf), { recursive: true });
61343
+ const wf = import_node_path39.default.join(dir, rel);
61344
+ await import_promises6.default.mkdir(import_node_path39.default.dirname(wf), { recursive: true });
60100
61345
  await import_promises6.default.writeFile(wf, body);
60101
61346
  }
60102
61347
  }
60103
61348
  async function writeDockerfile(dir, personaId2) {
60104
- await import_promises6.default.writeFile(import_node_path36.default.join(dir, DOCKERFILE_PATH), renderDockerfile(personaId2));
60105
- 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);
60106
61351
  }
60107
61352
  var RepoAlreadyExistsError = class extends RepoError {
60108
61353
  constructor(slug) {
@@ -60139,7 +61384,7 @@ async function ensureRepo(exec, api, args) {
60139
61384
  const existing = await repoInfo(exec, dir);
60140
61385
  if (existing) return { created: false, url: existing.url };
60141
61386
  await writeGitignore(dir);
60142
- 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 });
60143
61388
  let created;
60144
61389
  try {
60145
61390
  created = await createOrgRepo(exec, api, { dir, org, repoName: personaId2, message: "clawd: \u4E0A\u4E91", auth: args.auth });
@@ -60196,7 +61441,7 @@ async function release(exec, api, args) {
60196
61441
  await api.enableActions(repoSlug(info.url));
60197
61442
  await writeGitignore(args.dir);
60198
61443
  await writeWorkflow(args.dir);
60199
- await writeDockerfile(args.dir, personaIdOf(repoSlug(info.url).split("/").pop() ?? ""));
61444
+ await writeDockerfile(args.dir, cloudPersonaIdOf(info.url));
60200
61445
  const identity = await identityArgs(exec, args.dir);
60201
61446
  await mustGit(exec, args.dir, ["add", "-A"], "\u6682\u5B58\u6587\u4EF6");
60202
61447
  const stamp = new Date(args.now()).toISOString();
@@ -60679,6 +61924,14 @@ function buildDeployHandlers(deps) {
60679
61924
  repo2 = await repoInfo(deps.exec, dir);
60680
61925
  if (!repo2) throw new ClawdError(ERROR_CODES.INTERNAL, "\u4ED3\u5E93\u5EFA\u597D\u4E86\u4F46\u8BFB\u4E0D\u5230 origin");
60681
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`);
60682
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`);
60683
61936
  const live = liveScans.get(personaId2);
60684
61937
  if (live && live.sessionId === void 0) return view(personaId2);
@@ -60856,10 +62109,10 @@ async function probeLocalVersion(exec, pkg) {
60856
62109
 
60857
62110
  // src/tools/shell-path.ts
60858
62111
  var import_node_child_process10 = require("child_process");
60859
- var import_node_os11 = __toESM(require("os"), 1);
62112
+ var import_node_os12 = __toESM(require("os"), 1);
60860
62113
  function refreshPathFromLoginShell() {
60861
62114
  if (process.platform === "win32") return;
60862
- 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";
60863
62116
  try {
60864
62117
  const out = (0, import_node_child_process10.execFileSync)(shell, ["-ilc", 'command echo -n "$PATH"'], {
60865
62118
  encoding: "utf8",
@@ -61405,15 +62658,15 @@ async function refreshTunnelUrlOnStartup(deps) {
61405
62658
  }
61406
62659
 
61407
62660
  // src/lark-bot-connector/binding-store.ts
61408
- var import_node_fs32 = __toESM(require("fs"), 1);
61409
- 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);
61410
62663
  function createLarkBotBindingStore(personaRoot) {
61411
- 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");
61412
62665
  const read = (personaId2) => {
61413
62666
  const file = filePath(personaId2);
61414
- if (!import_node_fs32.default.existsSync(file)) return null;
62667
+ if (!import_node_fs35.default.existsSync(file)) return null;
61415
62668
  try {
61416
- const raw = JSON.parse(import_node_fs32.default.readFileSync(file, "utf8"));
62669
+ const raw = JSON.parse(import_node_fs35.default.readFileSync(file, "utf8"));
61417
62670
  if (typeof raw?.appId !== "string" || raw.appId.length === 0) return null;
61418
62671
  return raw;
61419
62672
  } catch {
@@ -61422,16 +62675,16 @@ function createLarkBotBindingStore(personaRoot) {
61422
62675
  };
61423
62676
  const write = (personaId2, meta) => {
61424
62677
  const file = filePath(personaId2);
61425
- 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 });
61426
62679
  const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
61427
- import_node_fs32.default.writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
61428
- 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);
61429
62682
  };
61430
62683
  return {
61431
62684
  read,
61432
62685
  write,
61433
62686
  remove(personaId2) {
61434
- import_node_fs32.default.rmSync(filePath(personaId2), { force: true });
62687
+ import_node_fs35.default.rmSync(filePath(personaId2), { force: true });
61435
62688
  },
61436
62689
  addRemovedChat(personaId2, chatId) {
61437
62690
  const meta = read(personaId2);
@@ -61583,8 +62836,8 @@ function createLarkBotCloudClient(opts) {
61583
62836
  }
61584
62837
 
61585
62838
  // src/lark-bot-connector/chat-router.ts
61586
- var import_node_fs33 = __toESM(require("fs"), 1);
61587
- 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);
61588
62841
  init_protocol();
61589
62842
  var ERROR_REPLY_TEXT = "\u5904\u7406\u51FA\u9519\u4E86\uFF0C\u8BF7\u91CD\u8BD5";
61590
62843
  var LOCAL_TURN_RETRIES = 2;
@@ -61662,9 +62915,9 @@ function createLarkChatRouter(deps) {
61662
62915
  continue;
61663
62916
  }
61664
62917
  const dir = deps.mediaRootFor(env.chatId);
61665
- import_node_fs33.default.mkdirSync(dir, { recursive: true });
61666
- const p3 = import_node_path38.default.join(dir, `${safeMediaName(ownerMessageId)}-${i2}.${ext}`);
61667
- 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);
61668
62921
  lines.push(`[\u56FE\u7247: ${p3}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
61669
62922
  } catch (err) {
61670
62923
  deps.logger?.warn(`larkBot image fetch failed ${key}: ${err.message}`);
@@ -61685,9 +62938,9 @@ function createLarkChatRouter(deps) {
61685
62938
  continue;
61686
62939
  }
61687
62940
  const dir = deps.mediaRootFor(env.chatId);
61688
- import_node_fs33.default.mkdirSync(dir, { recursive: true });
61689
- const p3 = import_node_path38.default.join(dir, `${safeMediaName(ownerMessageId)}-${i2}-${safeAttachmentName(f2.name)}`);
61690
- 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);
61691
62944
  lines.push(`[\u6587\u4EF6: ${p3}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
61692
62945
  } catch (err) {
61693
62946
  deps.logger?.warn(`larkBot file fetch failed ${f2.key}: ${err.message}`);
@@ -62335,954 +63588,184 @@ function buildPersonaDispatchHandlers(deps) {
62335
63588
  "personaDispatch:complete": complete,
62336
63589
  "personaDispatch:list": list,
62337
63590
  "personaDispatch:get": get
62338
- };
62339
- }
62340
-
62341
- // src/dispatch/peer-forward.ts
62342
- init_protocol();
62343
- function wsUrlToHttp(url) {
62344
- if (url.startsWith("wss://")) return "https://" + url.slice("wss://".length);
62345
- if (url.startsWith("ws://")) return "http://" + url.slice("ws://".length);
62346
- return url;
62347
- }
62348
- var DISPATCH_POLL_INTERVAL_MS = 5e3;
62349
- var DISPATCH_POLL_UNREACHABLE_MS = 6e5;
62350
- var DISPATCH_POLL_TOTAL_MS = 864e5;
62351
- var defaultSleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
62352
- async function forwardDispatchToPeer(args) {
62353
- const f2 = args.fetchImpl ?? fetch;
62354
- const sleep2 = args.sleepImpl ?? defaultSleep;
62355
- const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
62356
- const url = `${base}/rpc/personaDispatch:run`;
62357
- let res;
62358
- try {
62359
- res = await f2(url, {
62360
- method: "POST",
62361
- headers: {
62362
- "content-type": "application/json",
62363
- authorization: `Bearer ${args.contact.connectToken}`
62364
- },
62365
- // 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
62366
- body: JSON.stringify({
62367
- targetPersona: args.targetPersona,
62368
- prompt: args.prompt,
62369
- // T-20: 条件展开防 undefined 污染 JSON。远端 daemon handler 会再校验一次 model 合法性。
62370
- ...args.model ? { model: args.model } : {}
62371
- })
62372
- });
62373
- } catch (err) {
62374
- const msg = err instanceof Error ? err.message : String(err);
62375
- return { kind: "failure", reason: `forward to peer failed: ${msg}` };
62376
- }
62377
- let json;
62378
- try {
62379
- json = await res.json();
62380
- } catch {
62381
- return {
62382
- kind: "failure",
62383
- reason: `peer returned non-JSON response (HTTP ${res.status})`
62384
- };
62385
- }
62386
- if (json.ok === false) {
62387
- return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
62388
- }
62389
- if ("outcome" in json.result) return json.result.outcome;
62390
- const dispatchId = json.result.dispatchId;
62391
- let elapsed = 0;
62392
- let unreachableFor = 0;
62393
- while (elapsed < DISPATCH_POLL_TOTAL_MS) {
62394
- await sleep2(DISPATCH_POLL_INTERVAL_MS);
62395
- elapsed += DISPATCH_POLL_INTERVAL_MS;
62396
- const markUnreachable = () => {
62397
- unreachableFor += DISPATCH_POLL_INTERVAL_MS;
62398
- return unreachableFor >= DISPATCH_POLL_UNREACHABLE_MS ? {
62399
- kind: "failure",
62400
- reason: "peer unreachable while polling dispatch outcome (TARGET_UNREACHABLE)"
62401
- } : null;
62402
- };
62403
- let getRes;
62404
- try {
62405
- getRes = await f2(`${base}/rpc/personaDispatch:get`, {
62406
- method: "POST",
62407
- headers: {
62408
- "content-type": "application/json",
62409
- authorization: `Bearer ${args.contact.connectToken}`
62410
- },
62411
- body: JSON.stringify({ dispatchId })
62412
- });
62413
- } catch {
62414
- const dead = markUnreachable();
62415
- if (dead) return dead;
62416
- continue;
62417
- }
62418
- let getJson;
62419
- try {
62420
- getJson = await getRes.json();
62421
- } catch {
62422
- const dead = markUnreachable();
62423
- if (dead) return dead;
62424
- continue;
62425
- }
62426
- if (getJson.ok === false) {
62427
- if (getJson.error === ERROR_CODES.DISPATCH_NOT_FOUND) {
62428
- return { kind: "failure", reason: `peer lost dispatch ${dispatchId} (daemon restarted?)` };
62429
- }
62430
- const dead = markUnreachable();
62431
- if (dead) return dead;
62432
- continue;
62433
- }
62434
- unreachableFor = 0;
62435
- const { status, outcome } = getJson.result;
62436
- if (status === "completed" || status === "failed") {
62437
- return outcome ?? { kind: "failure", reason: "peer returned terminal status without outcome" };
62438
- }
62439
- }
62440
- return { kind: "failure", reason: "dispatch polling exceeded 24h (TIMEOUT)" };
62441
- }
62442
- async function forwardInboxPostToPeer(args) {
62443
- const f2 = args.fetchImpl ?? fetch;
62444
- const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
62445
- const url = `${base}/rpc/inbox:postMessage`;
62446
- let res;
62447
- try {
62448
- res = await f2(url, {
62449
- method: "POST",
62450
- headers: {
62451
- "content-type": "application/json",
62452
- authorization: `Bearer ${args.contact.connectToken}`
62453
- },
62454
- body: JSON.stringify({
62455
- id: args.id,
62456
- text: args.text,
62457
- createdAt: args.createdAt,
62458
- ...args.origin ? { origin: args.origin } : {}
62459
- })
62460
- });
62461
- } catch (err) {
62462
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
62463
- }
62464
- let json = null;
62465
- try {
62466
- json = await res.json();
62467
- } catch {
62468
- return { ok: false, error: `peer non-JSON (HTTP ${res.status})` };
62469
- }
62470
- if (res.status < 200 || res.status >= 300 || json?.ok === false) {
62471
- return { ok: false, error: json?.error ?? `HTTP ${res.status}` };
62472
- }
62473
- return { ok: true };
62474
- }
62475
- async function forwardPeerExecToPeer(args) {
62476
- const f2 = args.fetchImpl ?? fetch;
62477
- const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
62478
- const url = `${base}/rpc/peerExec:run`;
62479
- const tunnelFail = (msg) => ({
62480
- type: "peerExec:run:ok",
62481
- stdout: "",
62482
- stderr: msg,
62483
- exitCode: null,
62484
- timedOut: false,
62485
- stdoutTruncated: false,
62486
- stderrTruncated: false
62487
- });
62488
- let res;
62489
- try {
62490
- res = await f2(url, {
62491
- method: "POST",
62492
- headers: {
62493
- "content-type": "application/json",
62494
- authorization: `Bearer ${args.contact.connectToken}`
62495
- },
62496
- body: JSON.stringify({ command: args.command, ...args.timeoutMs ? { timeoutMs: args.timeoutMs } : {} })
62497
- });
62498
- } catch (err) {
62499
- return tunnelFail(`forward to peer failed: ${err instanceof Error ? err.message : String(err)}`);
62500
- }
62501
- let json;
62502
- try {
62503
- json = await res.json();
62504
- } catch {
62505
- return tunnelFail(`peer returned non-JSON response (HTTP ${res.status})`);
62506
- }
62507
- if (json.ok === false) {
62508
- throw new ClawdError(json.error || ERROR_CODES.INTERNAL, json.message || json.error);
62509
- }
62510
- return json.result;
62511
- }
62512
-
62513
- // src/tools/codex-history.ts
62514
- var import_node_fs35 = __toESM(require("fs"), 1);
62515
- var import_node_path40 = __toESM(require("path"), 1);
62516
-
62517
- // src/tools/transcript-search.ts
62518
- var import_node_fs34 = __toESM(require("fs"), 1);
62519
- var import_node_path39 = __toESM(require("path"), 1);
62520
- var SNIPPET_PAD = 60;
62521
- 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/;
62522
- var CODEX_WRAPPER_RE = /<(related-dir|environment_context|user_instructions)>[\s\S]*?<\/\1>/g;
62523
- function asRecord2(v3) {
62524
- return typeof v3 === "object" && v3 !== null ? v3 : null;
62525
- }
62526
- function textOfBlocks(content, types) {
62527
- if (!Array.isArray(content)) return null;
62528
- const parts = [];
62529
- for (const block of content) {
62530
- const b3 = asRecord2(block);
62531
- if (!b3 || typeof b3.type !== "string" || !types.includes(b3.type)) continue;
62532
- const text = b3.text;
62533
- if (typeof text === "string" && text !== "") parts.push(text);
62534
- }
62535
- return parts.length > 0 ? parts.join("\n") : null;
62536
- }
62537
- function lineToSearchableText(raw) {
62538
- const line = asRecord2(raw);
62539
- if (!line) return null;
62540
- if (line.type === "response_item") {
62541
- const payload = asRecord2(line.payload);
62542
- if (!payload || payload.type !== "message") return null;
62543
- const role = payload.role;
62544
- if (role !== "user" && role !== "assistant") return null;
62545
- const text2 = textOfBlocks(payload.content, ["input_text", "output_text"]);
62546
- if (text2 === null) return null;
62547
- if (role !== "user") return text2;
62548
- const stripped = text2.replace(CODEX_WRAPPER_RE, "").trim();
62549
- if (stripped === "") return null;
62550
- return INJECTED_ENVELOPE_RE.test(stripped) ? null : stripped;
62551
- }
62552
- if (line.type !== "user" && line.type !== "assistant") return null;
62553
- const message = asRecord2(line.message);
62554
- if (!message) return null;
62555
- const content = message.content;
62556
- const text = typeof content === "string" && content !== "" ? content : textOfBlocks(content, ["text"]);
62557
- if (text === null) return null;
62558
- if (line.type === "user" && (line.isMeta === true || line.isSynthetic === true)) return null;
62559
- if (line.type === "user" && INJECTED_ENVELOPE_RE.test(text.trimStart())) return null;
62560
- return text;
62561
- }
62562
- function escapeRegExp(s2) {
62563
- return s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
62564
- }
62565
- function makeSnippet(text, at3, needleLength) {
62566
- const from = Math.max(0, at3 - SNIPPET_PAD);
62567
- const to = Math.min(text.length, at3 + needleLength + SNIPPET_PAD);
62568
- const body = text.slice(from, to).replace(/\s+/g, " ").trim();
62569
- return `${from > 0 ? "\u2026 " : ""}${body}${to < text.length ? " \u2026" : ""}`;
62570
- }
62571
- function searchTranscriptFile(file, query) {
62572
- let buf;
62573
- try {
62574
- buf = import_node_fs34.default.readFileSync(file);
62575
- } catch {
62576
- return null;
62577
- }
62578
- const haystack = buf.toString("latin1");
62579
- const needle = Buffer.from(query, "utf8").toString("latin1");
62580
- const re2 = new RegExp(escapeRegExp(needle), "gi");
62581
- let m3;
62582
- while ((m3 = re2.exec(haystack)) !== null) {
62583
- const lineStart = haystack.lastIndexOf("\n", m3.index) + 1;
62584
- let lineEnd = haystack.indexOf("\n", m3.index);
62585
- if (lineEnd < 0) lineEnd = haystack.length;
62586
- re2.lastIndex = lineEnd + 1;
62587
- let parsed;
62588
- try {
62589
- parsed = JSON.parse(Buffer.from(haystack.slice(lineStart, lineEnd), "latin1").toString("utf8"));
62590
- } catch {
62591
- continue;
62592
- }
62593
- const text = lineToSearchableText(parsed);
62594
- if (text === null) continue;
62595
- const at3 = text.toLowerCase().indexOf(query.toLowerCase());
62596
- if (at3 < 0) continue;
62597
- return makeSnippet(text, at3, query.length);
62598
- }
62599
- return null;
62600
- }
62601
- var ROLLOUT_NAME_RE = /-([0-9a-fA-F-]{36})\.jsonl$/;
62602
- function createCodexRolloutLocator(roots) {
62603
- let index = null;
62604
- const walk2 = (dir, into) => {
62605
- let entries;
62606
- try {
62607
- entries = import_node_fs34.default.readdirSync(dir, { withFileTypes: true });
62608
- } catch {
62609
- return;
62610
- }
62611
- for (const entry of entries) {
62612
- const full = import_node_path39.default.join(dir, entry.name);
62613
- if (entry.isDirectory()) {
62614
- walk2(full, into);
62615
- continue;
62616
- }
62617
- const matched = ROLLOUT_NAME_RE.exec(entry.name);
62618
- if (matched) into.set(matched[1].toLowerCase(), full);
62619
- }
62620
- };
62621
- return (toolSessionId) => {
62622
- if (index === null) {
62623
- index = /* @__PURE__ */ new Map();
62624
- for (const root of roots) walk2(root, index);
62625
- }
62626
- return index.get(toolSessionId.toLowerCase()) ?? null;
62627
- };
62628
- }
62629
-
62630
- // src/tools/codex-rollout.ts
62631
- function rec2(v3) {
62632
- return v3 && typeof v3 === "object" && !Array.isArray(v3) ? v3 : void 0;
62633
- }
62634
- function str2(v3) {
62635
- return typeof v3 === "string" ? v3 : void 0;
62636
- }
62637
- function num2(v3) {
62638
- return typeof v3 === "number" && Number.isFinite(v3) ? v3 : void 0;
62639
- }
62640
- function parseLine(line) {
62641
- const t2 = line.trim();
62642
- if (!t2) return void 0;
62643
- try {
62644
- return rec2(JSON.parse(t2));
62645
- } catch {
62646
- return void 0;
62647
- }
62648
- }
62649
- function metaFrom(obj) {
62650
- if (obj.type !== "session_meta") return null;
62651
- const p3 = rec2(obj.payload);
62652
- const threadId = str2(p3?.id) ?? str2(p3?.session_id);
62653
- if (!p3 || !threadId) return null;
62654
- const meta = { threadId };
62655
- const cwd = str2(p3.cwd);
62656
- if (cwd) meta.cwd = cwd;
62657
- const createdAt = str2(p3.timestamp) ?? str2(obj.timestamp);
62658
- if (createdAt) meta.createdAt = createdAt;
62659
- const parent = str2(p3.parent_thread_id);
62660
- if (parent) meta.parentThreadId = parent;
62661
- const cli = str2(p3.cli_version);
62662
- if (cli) meta.cliVersion = cli;
62663
- return meta;
62664
- }
62665
- function parseCodexRolloutMeta(firstLine) {
62666
- const obj = parseLine(firstLine);
62667
- return obj ? metaFrom(obj) : null;
62668
- }
62669
- function firstUserMessageText(text) {
62670
- for (const line of text.split("\n")) {
62671
- const obj = parseLine(line);
62672
- if (!obj || obj.type !== "event_msg") continue;
62673
- const p3 = rec2(obj.payload);
62674
- if (p3?.type === "user_message") return str2(p3.message);
62675
- }
62676
- return void 0;
62677
- }
62678
- function outputText(output) {
62679
- if (typeof output === "string") return output;
62680
- if (Array.isArray(output)) {
62681
- return output.map((part) => typeof part === "string" ? part : str2(rec2(part)?.text) ?? "").join("");
62682
- }
62683
- return "";
62684
- }
62685
- function functionCallInput(name, rawArgs) {
62686
- let args = rawArgs;
62687
- if (typeof rawArgs === "string") {
62688
- try {
62689
- args = JSON.parse(rawArgs);
62690
- } catch {
62691
- args = { arguments: rawArgs };
62692
- }
62693
- }
62694
- const a2 = rec2(args) ?? {};
62695
- if (name === "exec_command" || name === "shell" || name === "container.exec") {
62696
- const cmd = a2.cmd ?? a2.command;
62697
- const command = Array.isArray(cmd) ? cmd.map(String).join(" ") : str2(cmd) ?? "";
62698
- return { tool: "commandExecution", input: { command } };
62699
- }
62700
- return { tool: name, input: args };
62701
- }
62702
- function parseCodexRollout(text) {
62703
- const out = { meta: null, messages: [], subagents: [], turns: [] };
62704
- const collabCallIds = /* @__PURE__ */ new Set();
62705
- for (const line of text.split("\n")) {
62706
- const obj = parseLine(line);
62707
- if (!obj) continue;
62708
- const ts = str2(obj.timestamp);
62709
- const p3 = rec2(obj.payload);
62710
- if (!p3) continue;
62711
- if (obj.type === "session_meta") {
62712
- if (!out.meta) out.meta = metaFrom(obj);
62713
- continue;
62714
- }
62715
- if (obj.type === "event_msg") {
62716
- switch (p3.type) {
62717
- case "user_message": {
62718
- const t2 = str2(p3.message);
62719
- if (t2) out.messages.push({ kind: "user-text", text: t2, raw: obj, ...ts ? { ts } : {} });
62720
- break;
62721
- }
62722
- case "agent_message": {
62723
- const t2 = str2(p3.message);
62724
- if (t2) out.messages.push({ kind: "assistant-text", text: t2, raw: obj, ...ts ? { ts } : {} });
62725
- break;
62726
- }
62727
- case "sub_agent_activity": {
62728
- if (p3.kind !== "started") break;
62729
- const toolUseId = str2(p3.event_id);
62730
- const agentId = str2(p3.agent_thread_id);
62731
- const agentPath = str2(p3.agent_path);
62732
- if (!toolUseId || !agentId || !agentPath) break;
62733
- const segments = agentPath.split("/").filter(Boolean);
62734
- const description = segments[segments.length - 1] ?? agentId;
62735
- out.subagents.push({ toolUseId, agentId, agentPath, description });
62736
- out.messages.push({
62737
- kind: "tool_call",
62738
- tool: "Agent",
62739
- toolUseId,
62740
- input: { agentId, description, agentPath },
62741
- raw: obj,
62742
- ...ts ? { ts } : {}
62743
- });
62744
- break;
62745
- }
62746
- case "task_complete":
62747
- case "turn_aborted": {
62748
- const durationMs = num2(p3.duration_ms);
62749
- const turnId = str2(p3.turn_id);
62750
- const reason = str2(p3.reason);
62751
- out.turns.push({
62752
- status: p3.type === "task_complete" ? "completed" : "interrupted",
62753
- ...turnId ? { turnId } : {},
62754
- ...durationMs !== void 0 ? { durationMs } : {},
62755
- ...reason ? { reason } : {}
62756
- });
62757
- out.messages.push({
62758
- kind: "turn-end",
62759
- raw: obj,
62760
- ...ts ? { ts } : {},
62761
- ...durationMs !== void 0 ? { durationMs } : {}
62762
- });
62763
- break;
62764
- }
62765
- default:
62766
- break;
62767
- }
62768
- continue;
62769
- }
62770
- if (obj.type === "response_item") {
62771
- switch (p3.type) {
62772
- case "function_call": {
62773
- const callId = str2(p3.call_id);
62774
- const name = str2(p3.name);
62775
- if (!callId || !name) break;
62776
- if (p3.namespace === "collaboration") {
62777
- collabCallIds.add(callId);
62778
- break;
62779
- }
62780
- const { tool, input } = functionCallInput(name, p3.arguments);
62781
- out.messages.push({ kind: "tool_call", tool, toolUseId: callId, input, raw: obj, ...ts ? { ts } : {} });
62782
- break;
62783
- }
62784
- case "custom_tool_call": {
62785
- const callId = str2(p3.call_id);
62786
- const name = str2(p3.name);
62787
- if (!callId || !name) break;
62788
- out.messages.push({
62789
- kind: "tool_call",
62790
- tool: name,
62791
- toolUseId: callId,
62792
- input: { input: str2(p3.input) ?? "" },
62793
- raw: obj,
62794
- ...ts ? { ts } : {}
62795
- });
62796
- break;
62797
- }
62798
- case "function_call_output":
62799
- case "custom_tool_call_output": {
62800
- const callId = str2(p3.call_id);
62801
- if (!callId || collabCallIds.has(callId)) break;
62802
- out.messages.push({
62803
- kind: "tool_result",
62804
- toolUseId: callId,
62805
- output: outputText(p3.output),
62806
- raw: obj,
62807
- ...ts ? { ts } : {}
62808
- });
62809
- break;
62810
- }
62811
- default:
62812
- break;
62813
- }
62814
- continue;
62815
- }
62816
- }
62817
- return out;
62818
- }
62819
- function subagentTerminalResult(started, child) {
62820
- const first = child.turns[0];
62821
- if (!first) return void 0;
62822
- if (first.status === "completed") return { kind: "tool_result", toolUseId: started.toolUseId };
62823
- return { kind: "tool_result", toolUseId: started.toolUseId, error: first.reason ?? "Codex subagent interrupted" };
62824
- }
62825
-
62826
- // src/tools/codex-history.ts
62827
- var CODEX_LIST_SESSIONS_LIMIT = 100;
62828
- var CODEX_LIST_SESSIONS_SCAN_CAP = 4e3;
62829
- var PEEK_BYTES = 8 * 1024;
62830
- var TITLE_BYTES = 256 * 1024;
62831
- function readHead(file, bytes) {
62832
- let fd;
62833
- try {
62834
- fd = import_node_fs35.default.openSync(file, "r");
62835
- } catch {
62836
- return null;
62837
- }
62838
- try {
62839
- const buf = Buffer.alloc(bytes);
62840
- const n2 = import_node_fs35.default.readSync(fd, buf, 0, bytes, 0);
62841
- return buf.subarray(0, n2).toString("utf8");
62842
- } catch {
62843
- return null;
62844
- } finally {
62845
- import_node_fs35.default.closeSync(fd);
62846
- }
62847
- }
62848
- function peekSessionCwd(head) {
62849
- if (!head.includes('"session_meta"')) return null;
62850
- const m3 = /"cwd":"((?:[^"\\]|\\.)*)"/.exec(head);
62851
- if (!m3) return null;
62852
- try {
62853
- return JSON.parse(`"${m3[1]}"`);
62854
- } catch {
62855
- return null;
62856
- }
63591
+ };
62857
63592
  }
62858
- function readFirstLine(file, max = 512 * 1024) {
62859
- let fd;
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;
63600
+ }
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;
62860
63611
  try {
62861
- fd = import_node_fs35.default.openSync(file, "r");
62862
- } catch {
62863
- 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}` };
62864
63629
  }
63630
+ let json;
62865
63631
  try {
62866
- const chunk = Buffer.alloc(64 * 1024);
62867
- const parts = [];
62868
- let total = 0;
62869
- while (total < max) {
62870
- const n2 = import_node_fs35.default.readSync(fd, chunk, 0, chunk.length, total);
62871
- if (n2 <= 0) break;
62872
- const nl = chunk.subarray(0, n2).indexOf(10);
62873
- if (nl >= 0) {
62874
- parts.push(Buffer.from(chunk.subarray(0, nl)));
62875
- return Buffer.concat(parts).toString("utf8");
62876
- }
62877
- parts.push(Buffer.from(chunk.subarray(0, n2)));
62878
- total += n2;
62879
- }
62880
- return total < max && parts.length > 0 ? Buffer.concat(parts).toString("utf8") : null;
63632
+ json = await res.json();
62881
63633
  } catch {
62882
- return null;
62883
- } finally {
62884
- import_node_fs35.default.closeSync(fd);
63634
+ return {
63635
+ kind: "failure",
63636
+ reason: `peer returned non-JSON response (HTTP ${res.status})`
63637
+ };
62885
63638
  }
62886
- }
62887
- function readThreadNames(sharedHome) {
62888
- const names = /* @__PURE__ */ new Map();
62889
- if (!sharedHome) return names;
62890
- let raw;
62891
- try {
62892
- raw = import_node_fs35.default.readFileSync(import_node_path40.default.join(sharedHome, "session_index.jsonl"), "utf8");
62893
- } catch {
62894
- return names;
63639
+ if (json.ok === false) {
63640
+ return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
62895
63641
  }
62896
- for (const line of raw.split("\n")) {
62897
- 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;
62898
63657
  try {
62899
- const o2 = JSON.parse(line);
62900
- if (typeof o2.id === "string" && typeof o2.thread_name === "string" && o2.thread_name) {
62901
- names.set(o2.id.toLowerCase(), o2.thread_name);
62902
- }
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
+ });
62903
63666
  } catch {
63667
+ const dead = markUnreachable();
63668
+ if (dead) return dead;
63669
+ continue;
62904
63670
  }
62905
- }
62906
- return names;
62907
- }
62908
- function walkRollouts(dir, visit) {
62909
- let entries;
62910
- try {
62911
- entries = import_node_fs35.default.readdirSync(dir, { withFileTypes: true });
62912
- } catch {
62913
- return true;
62914
- }
62915
- entries.sort((a2, b3) => a2.name < b3.name ? 1 : a2.name > b3.name ? -1 : 0);
62916
- for (const entry of entries) {
62917
- const full = import_node_path40.default.join(dir, entry.name);
62918
- if (entry.isDirectory()) {
62919
- if (!walkRollouts(full, visit)) return false;
62920
- } else if (ROLLOUT_NAME_RE.test(entry.name)) {
62921
- if (visit(full) === false) return false;
62922
- }
62923
- }
62924
- return true;
62925
- }
62926
- async function withSubagentResults(parent, parentThreadId, loadChild, logger) {
62927
- const messages = [];
62928
- const started = new Map(parent.subagents.map((s2) => [s2.toolUseId, s2]));
62929
- for (const m3 of parent.messages) {
62930
- messages.push(m3);
62931
- const sub = m3.kind === "tool_call" && m3.toolUseId ? started.get(m3.toolUseId) : void 0;
62932
- if (!sub) continue;
62933
- let text;
63671
+ let getJson;
62934
63672
  try {
62935
- text = await loadChild(sub.agentId);
62936
- } catch (error) {
62937
- 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;
62938
63677
  continue;
62939
63678
  }
62940
- if (text === null) {
62941
- 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;
62942
63685
  continue;
62943
63686
  }
62944
- const child = parseCodexRollout(text);
62945
- if (child.meta?.parentThreadId !== parentThreadId) {
62946
- logger?.warn("codex subagent rollout does not belong to parent", {
62947
- parentThreadId,
62948
- childThreadId: sub.agentId,
62949
- actualParent: child.meta?.parentThreadId ?? null
62950
- });
62951
- 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" };
62952
63691
  }
62953
- const result = subagentTerminalResult(sub, child);
62954
- if (result) messages.push(result);
62955
- }
62956
- return messages;
62957
- }
62958
- function pageMessages(messages, args) {
62959
- const total = messages.length;
62960
- const limit = args.limit ?? total;
62961
- const offset = typeof args.offset === "number" ? Math.min(Math.max(0, args.offset), total) : Math.max(0, total - limit);
62962
- return { messages: messages.slice(offset, offset + limit), total, offset };
62963
- }
62964
- function subagentSummaries(parent) {
62965
- const seen = /* @__PURE__ */ new Set();
62966
- const out = [];
62967
- for (const s2 of parent.subagents) {
62968
- if (seen.has(s2.agentId)) continue;
62969
- seen.add(s2.agentId);
62970
- out.push({ subagentId: s2.agentId, summary: s2.description, messageCount: 0 });
62971
63692
  }
62972
- return out;
63693
+ return { kind: "failure", reason: "dispatch polling exceeded 24h (TIMEOUT)" };
62973
63694
  }
62974
- var CodexHistoryReader = class {
62975
- deps;
62976
- constructor(deps) {
62977
- this.deps = deps;
62978
- }
62979
- // 仍返回 []:projects/list 非 session-scoped、不知 tool,跨-tool 项目浏览器合并排后续。
62980
- // codex 旧会话仍能经 clawd 活跃列表(session:list, tool-agnostic) + read 打开。
62981
- async listProjects() {
62982
- return [];
62983
- }
62984
- async listSessions(args) {
62985
- const want = import_node_path40.default.resolve(args.projectPath);
62986
- const names = readThreadNames(this.deps.sharedHome);
62987
- const found = [];
62988
- let scanned = 0;
62989
- for (const root of this.deps.roots) {
62990
- const goOn = walkRollouts(root, (file) => {
62991
- if (++scanned > CODEX_LIST_SESSIONS_SCAN_CAP) return false;
62992
- const head = readHead(file, PEEK_BYTES);
62993
- const cwd = head === null ? null : peekSessionCwd(head);
62994
- if (!cwd || import_node_path40.default.resolve(cwd) !== want) return;
62995
- const first = readFirstLine(file);
62996
- const meta = first === null ? null : parseCodexRolloutMeta(first);
62997
- if (!meta) return;
62998
- let mtimeMs;
62999
- try {
63000
- mtimeMs = import_node_fs35.default.statSync(file).mtimeMs;
63001
- } catch {
63002
- return;
63003
- }
63004
- found.push({ file, threadId: meta.threadId, mtimeMs });
63005
- });
63006
- if (!goOn) break;
63007
- }
63008
- found.sort((a2, b3) => b3.mtimeMs - a2.mtimeMs);
63009
- return found.slice(0, CODEX_LIST_SESSIONS_LIMIT).map((f2) => {
63010
- let summary = names.get(f2.threadId.toLowerCase());
63011
- if (!summary) {
63012
- const head = readHead(f2.file, TITLE_BYTES);
63013
- summary = head === null ? void 0 : firstUserMessageText(head);
63014
- }
63015
- return {
63016
- toolSessionId: f2.threadId,
63017
- summary: summary || "(\u7A7A\u4F1A\u8BDD)",
63018
- messageCount: 0,
63019
- // 与此前 thread/list 一致:列表不数条数
63020
- updatedAt: new Date(f2.mtimeMs).toISOString()
63021
- };
63022
- });
63023
- }
63024
- async read(args) {
63025
- const locate = createCodexRolloutLocator(this.deps.roots);
63026
- const parent = this.parseThread(locate, args.toolSessionId);
63027
- const messages = await withSubagentResults(
63028
- parent,
63029
- args.toolSessionId,
63030
- async (agentId) => {
63031
- const file = locate(agentId);
63032
- return file ? import_node_fs35.default.readFileSync(file, "utf8") : null;
63033
- },
63034
- this.deps.logger
63035
- );
63036
- return pageMessages(messages, args);
63037
- }
63038
- async listSubagents(args) {
63039
- return subagentSummaries(this.parseThread(createCodexRolloutLocator(this.deps.roots), args.toolSessionId));
63040
- }
63041
- async readSubagent(args) {
63042
- const child = this.parseThread(createCodexRolloutLocator(this.deps.roots), args.subagentId);
63043
- if (child.meta?.threadId !== args.subagentId || child.meta.parentThreadId !== args.toolSessionId) {
63044
- throw new Error(
63045
- `Codex subagent thread ${args.subagentId} does not belong to parent thread ${args.toolSessionId}`
63046
- );
63047
- }
63048
- return { messages: child.messages };
63049
- }
63050
- computeRewindDiff() {
63051
- return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
63052
- }
63053
- listRewindableUserMessageIds() {
63054
- return [];
63055
- }
63056
- parseThread(locate, threadId) {
63057
- const file = locate(threadId);
63058
- if (!file) throw new Error(`Codex rollout not found for thread ${threadId}`);
63059
- return parseCodexRollout(import_node_fs35.default.readFileSync(file, "utf8"));
63060
- }
63061
- };
63062
-
63063
- // src/cloud/gateway-client.ts
63064
- init_protocol();
63065
- init_protocol();
63066
-
63067
- // src/feishu-auth/central-client.ts
63068
- var CentralClientError = class extends Error {
63069
- constructor(code, message, cause) {
63070
- super(message);
63071
- this.code = code;
63072
- this.cause = cause;
63073
- this.name = "CentralClientError";
63074
- }
63075
- code;
63076
- cause;
63077
- };
63078
- async function centralRequest(opts, path102, init) {
63079
- const f2 = opts.fetchImpl ?? globalThis.fetch;
63080
- const url = `${opts.api.replace(/\/+$/, "")}${path102}`;
63081
- const ctrl = new AbortController();
63082
- 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`;
63083
63699
  let res;
63084
63700
  try {
63085
63701
  res = await f2(url, {
63086
- method: init.method,
63702
+ method: "POST",
63087
63703
  headers: {
63088
63704
  "content-type": "application/json",
63089
- ...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
63705
+ authorization: `Bearer ${args.contact.connectToken}`
63090
63706
  },
63091
- ...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
63092
- 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
+ })
63093
63713
  });
63094
63714
  } catch (err) {
63095
- clearTimeout(timer);
63096
- throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
63097
- }
63098
- clearTimeout(timer);
63099
- if (res.status === 401) {
63100
- throw new CentralClientError("UNAUTHORIZED", "login pass rejected by center");
63101
- }
63102
- if (!res.ok) {
63103
- let detail = "";
63104
- try {
63105
- const body = await res.json();
63106
- if (typeof body?.message === "string" && body.message) detail = `: ${body.message}`;
63107
- } catch {
63108
- }
63109
- throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}${detail}`);
63715
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
63110
63716
  }
63717
+ let json = null;
63111
63718
  try {
63112
- return await res.json();
63113
- } catch (err) {
63114
- throw new CentralClientError("BAD_RESPONSE", "central server: invalid json response", err);
63115
- }
63116
- }
63117
- async function centralExchange(opts) {
63118
- const body = await centralRequest(opts, "/api/clawd-identity/exchange", {
63119
- method: "POST",
63120
- bearer: opts.loginPass,
63121
- body: { deviceId: opts.deviceId }
63122
- });
63123
- if (typeof body.token !== "string" || body.token.length === 0 || typeof body.ownerId !== "string" || typeof body.provider !== "string" || typeof body.displayName !== "string") {
63124
- throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/ownerId/provider/displayName");
63125
- }
63126
- const deviceUrl = typeof body.deviceUrl === "string" && body.deviceUrl.length > 0 ? body.deviceUrl : null;
63127
- return { token: body.token, ownerId: body.ownerId, provider: body.provider, displayName: body.displayName, deviceUrl };
63128
- }
63129
- async function centralGetPublicKey(opts) {
63130
- const body = await centralRequest(opts, "/api/clawd-identity/public-key", {
63131
- method: "GET"
63132
- });
63133
- if (typeof body.publicKeyPem !== "string" || !body.publicKeyPem.includes("PUBLIC KEY")) {
63134
- 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})` };
63135
63722
  }
63136
- return { publicKeyPem: body.publicKeyPem };
63137
- }
63138
- async function centralUpsertDevice(opts) {
63139
- const body = await centralRequest(opts, "/api/clawd-identity/devices/self", {
63140
- method: "PUT",
63141
- bearer: opts.loginPass,
63142
- body: {
63143
- ...opts.url ? { url: opts.url } : {},
63144
- ...opts.name ? { name: opts.name } : {}
63145
- }
63146
- });
63147
- if (body.ok !== true) {
63148
- 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}` };
63149
63725
  }
63150
63726
  return { ok: true };
63151
63727
  }
63152
- async function centralPrincipalStatus(opts) {
63153
- const query = `provider=${encodeURIComponent(opts.provider)}&ownerId=${encodeURIComponent(opts.ownerId)}`;
63154
- const body = await centralRequest(opts, `/api/clawd-identity/principal-status?${query}`, {
63155
- method: "GET"
63156
- });
63157
- if (typeof body.active !== "boolean") {
63158
- throw new CentralClientError("BAD_RESPONSE", "principal-status: missing active flag");
63159
- }
63160
- return { active: body.active };
63161
- }
63162
- async function centralGetApiKey(opts) {
63163
- const body = await centralRequest(opts, "/api/persona-cloud/api-key", {
63164
- method: "GET",
63165
- 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
63166
63740
  });
63167
- if (body.apiKey !== null && typeof body.apiKey !== "string") {
63168
- throw new CentralClientError("BAD_RESPONSE", "api-key: apiKey must be string or null");
63169
- }
63170
- return {
63171
- apiKey: body.apiKey,
63172
- createdAt: typeof body.createdAt === "string" ? body.createdAt : null,
63173
- // 中心没配就是 null —— 不当成错误,key 本身照样有效
63174
- gatewayUrl: typeof body.gatewayUrl === "string" ? body.gatewayUrl : null
63175
- };
63176
- }
63177
- async function centralGetThread(opts) {
63178
- const f2 = opts.fetchImpl ?? globalThis.fetch;
63179
- const url = `${opts.api.replace(/\/+$/, "")}/api/persona-cloud/threads/${encodeURIComponent(opts.threadId)}`;
63180
- const ctrl = new AbortController();
63181
- const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
63182
63741
  let res;
63183
63742
  try {
63184
- 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
+ });
63185
63751
  } catch (err) {
63186
- clearTimeout(timer);
63187
- throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
63188
- }
63189
- clearTimeout(timer);
63190
- if (res.status === 404) return null;
63191
- if (res.status === 401) throw new CentralClientError("UNAUTHORIZED", "login pass rejected by center");
63192
- if (!res.ok) {
63193
- let detail = "";
63194
- try {
63195
- const body = await res.json();
63196
- if (typeof body?.message === "string" && body.message) detail = `: ${body.message}`;
63197
- } catch {
63198
- }
63199
- throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}${detail}`);
63200
- }
63201
- return await res.text();
63202
- }
63203
- async function centralIssueApiKey(opts) {
63204
- const body = await centralRequest(opts, "/api/persona-cloud/api-key", {
63205
- method: "POST",
63206
- bearer: opts.loginPass
63207
- });
63208
- if (typeof body.apiKey !== "string" || body.apiKey.length === 0) {
63209
- throw new CentralClientError("BAD_RESPONSE", "api-key: missing apiKey");
63210
- }
63211
- if (typeof body.createdAt !== "string" || body.createdAt.length === 0) {
63212
- throw new CentralClientError("BAD_RESPONSE", "api-key: missing createdAt");
63213
- }
63214
- return {
63215
- apiKey: body.apiKey,
63216
- createdAt: body.createdAt,
63217
- gatewayUrl: typeof body.gatewayUrl === "string" ? body.gatewayUrl : null
63218
- };
63219
- }
63220
- async function centralRevokeApiKey(opts) {
63221
- const body = await centralRequest(opts, "/api/persona-cloud/api-key", {
63222
- method: "DELETE",
63223
- bearer: opts.loginPass
63224
- });
63225
- if (typeof body.revoked !== "boolean") {
63226
- 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)}`);
63227
63753
  }
63228
- return { revoked: body.revoked };
63229
- }
63230
- async function centralListDevices(opts) {
63231
- const body = await centralRequest(opts, "/api/clawd-identity/devices", {
63232
- method: "GET",
63233
- bearer: opts.loginPass
63234
- });
63235
- if (!Array.isArray(body.devices)) {
63236
- 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})`);
63237
63759
  }
63238
- const out = [];
63239
- for (const raw of body.devices) {
63240
- const d2 = raw;
63241
- if (typeof d2.deviceId !== "string" || typeof d2.name !== "string" || typeof d2.url !== "string") {
63242
- throw new CentralClientError("BAD_RESPONSE", "devices: malformed device entry");
63243
- }
63244
- 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);
63245
63762
  }
63246
- return out;
63247
- }
63248
- async function centralConnectorsMine(opts) {
63249
- const body = await centralRequest(opts, "/api/persona-cloud/connectors/mine", {
63250
- method: "POST",
63251
- bearer: opts.loginPass,
63252
- body: { personaId: opts.personaId, connectors: opts.connectors }
63253
- });
63254
- if (!Array.isArray(body.connectors)) throw new CentralClientError("BAD_RESPONSE", "connectors/mine: missing connectors array");
63255
- return { connectors: body.connectors };
63256
- }
63257
- async function centralConnectorFill(opts) {
63258
- const body = await centralRequest(opts, "/api/persona-cloud/connectors/fields", {
63259
- method: "PUT",
63260
- bearer: opts.loginPass,
63261
- body: { connector: opts.connector, values: opts.values }
63262
- });
63263
- if (body.ok !== true) throw new CentralClientError("BAD_RESPONSE", "connectors/fields: server did not ack ok");
63264
- }
63265
- async function centralGithubAuthOauthStart(opts) {
63266
- const body = await centralRequest(opts, "/api/persona-cloud/github/oauth/start", {
63267
- method: "POST",
63268
- bearer: opts.loginPass,
63269
- body: { ...opts.returnTo ? { returnTo: opts.returnTo } : {} }
63270
- });
63271
- if (typeof body.authUrl !== "string" || !body.authUrl) throw new CentralClientError("BAD_RESPONSE", "github/oauth/start: missing authUrl");
63272
- return { authUrl: body.authUrl };
63273
- }
63274
- async function centralGithubAuthToken(opts) {
63275
- const body = await centralRequest(opts, "/api/persona-cloud/github/oauth/token", {
63276
- method: "POST",
63277
- bearer: opts.loginPass,
63278
- body: {}
63279
- });
63280
- if (body.token === null || body.token === void 0) return void 0;
63281
- if (typeof body.token !== "string" || !body.token) throw new CentralClientError("BAD_RESPONSE", "github/oauth/token: malformed token");
63282
- return { token: body.token, login: typeof body.login === "string" ? body.login : "" };
63763
+ return json.result;
63283
63764
  }
63284
63765
 
63285
63766
  // src/cloud/gateway-client.ts
63767
+ init_protocol();
63768
+ init_protocol();
63286
63769
  var CLOUD_WORKSPACE_SLOT = "{{WORKSPACE}}";
63287
63770
  var CLOUD_TURN_HEADER = "x-clawd-turn";
63288
63771
  var CLOUD_TURN_HEADER_START = "start";
@@ -63293,7 +63776,7 @@ var GATEWAY_ROUTES = {
63293
63776
  sessionMirror: (personaId2, sessionId) => `/p/${encodeURIComponent(personaId2)}/session/${encodeURIComponent(sessionId)}/mirror`,
63294
63777
  // 调用方自己那块盘的用量(design 2026-09-19-cloud-volume-usage §3.3):同样按人、不挂 persona
63295
63778
  volume: () => "/me/volume",
63296
- // 云 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)
63297
63780
  metrics: (personaId2) => `/metrics/${encodeURIComponent(personaId2)}`
63298
63781
  };
63299
63782
  var METRICS_TIMEOUT_MS = 3e4;
@@ -63653,68 +64136,7 @@ function openHttpLineStream(opts) {
63653
64136
  init_protocol();
63654
64137
 
63655
64138
  // src/tools/codex-cloud-session.ts
63656
- var import_node_fs36 = __toESM(require("fs"), 1);
63657
-
63658
- // src/tools/codex-cloud-history.ts
63659
- function personaIdFromCloudCwd(cwd) {
63660
- const m3 = /\/p\/([^/?#]+)(?:[/?#]|$)/.exec(cwd);
63661
- if (!m3) return void 0;
63662
- try {
63663
- return decodeURIComponent(m3[1]);
63664
- } catch {
63665
- return void 0;
63666
- }
63667
- }
63668
- var CloudCodexHistoryReader = class {
63669
- constructor(client, logger) {
63670
- this.client = client;
63671
- this.logger = logger;
63672
- }
63673
- client;
63674
- logger;
63675
- async listProjects() {
63676
- return [];
63677
- }
63678
- /** 云 persona 没有「关联已有 thread」的动线:会话列表就是本机的会话记录 */
63679
- async listSessions() {
63680
- return [];
63681
- }
63682
- async read(args) {
63683
- const parent = await this.fetchThread(args.toolSessionId);
63684
- const messages = await withSubagentResults(
63685
- parent,
63686
- args.toolSessionId,
63687
- (agentId) => this.client.fetchRollout(agentId),
63688
- this.logger
63689
- );
63690
- return pageMessages(messages, args);
63691
- }
63692
- async listSubagents(args) {
63693
- return subagentSummaries(await this.fetchThread(args.toolSessionId));
63694
- }
63695
- async readSubagent(args) {
63696
- const child = await this.fetchThread(args.subagentId);
63697
- if (child.meta?.threadId !== args.subagentId || child.meta.parentThreadId !== args.toolSessionId) {
63698
- throw new Error(
63699
- `Codex subagent thread ${args.subagentId} does not belong to parent thread ${args.toolSessionId}`
63700
- );
63701
- }
63702
- return { messages: child.messages };
63703
- }
63704
- computeRewindDiff() {
63705
- return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
63706
- }
63707
- listRewindableUserMessageIds() {
63708
- return [];
63709
- }
63710
- async fetchThread(threadId) {
63711
- const text = await this.client.fetchRollout(threadId);
63712
- if (text === null) throw new Error(`Codex rollout not found for thread ${threadId}`);
63713
- return parseCodexRollout(text);
63714
- }
63715
- };
63716
-
63717
- // src/tools/codex-cloud-session.ts
64139
+ var import_node_fs37 = __toESM(require("fs"), 1);
63718
64140
  function cloudThreadParams(ctx, paths) {
63719
64141
  const { model: _model, ...base } = threadStartParams(ctx);
63720
64142
  const developerInstructions = [ctx.extraSystemPrompt, workDirPrompt(paths.workDir, ctx.personaMode ?? "owner")].filter((x2) => Boolean(x2)).join("\n\n");
@@ -63771,7 +64193,7 @@ function cloudImageInput(deps) {
63771
64193
  const mime = lookupMime(p3);
63772
64194
  if (!mime.startsWith("image/")) return null;
63773
64195
  try {
63774
- const bytes = (deps.readFile ?? import_node_fs36.default.readFileSync)(p3);
64196
+ const bytes = (deps.readFile ?? import_node_fs37.default.readFileSync)(p3);
63775
64197
  return { type: "image", url: `data:${mime};base64,${bytes.toString("base64")}` };
63776
64198
  } catch (e) {
63777
64199
  deps.logger?.warn("cloud image attachment unreadable", { path: p3, message: e.message });
@@ -64004,37 +64426,11 @@ var PiRpcClient = class {
64004
64426
  // src/tools/pi-session.ts
64005
64427
  var import_node_child_process11 = require("child_process");
64006
64428
  var import_node_crypto12 = require("crypto");
64007
- var import_node_fs37 = __toESM(require("fs"), 1);
64008
- 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);
64009
64431
 
64010
64432
  // src/tools/pi-events.ts
64011
64433
  init_protocol();
64012
-
64013
- // src/tools/pi-json.ts
64014
- function rec3(v3) {
64015
- return typeof v3 === "object" && v3 !== null && !Array.isArray(v3) ? v3 : void 0;
64016
- }
64017
- function str3(v3) {
64018
- return typeof v3 === "string" ? v3 : void 0;
64019
- }
64020
- function num3(v3) {
64021
- return typeof v3 === "number" && Number.isFinite(v3) ? v3 : void 0;
64022
- }
64023
- function arr(v3) {
64024
- return Array.isArray(v3) ? v3 : [];
64025
- }
64026
- function contentText(v3, imagePlaceholder = "") {
64027
- if (typeof v3 === "string") return v3;
64028
- return arr(v3).map((b3) => {
64029
- const r2 = rec3(b3);
64030
- if (!r2) return "";
64031
- if (r2.type === "text") return str3(r2.text) ?? "";
64032
- if (r2.type === "image") return imagePlaceholder;
64033
- return "";
64034
- }).filter(Boolean).join("\n");
64035
- }
64036
-
64037
- // src/tools/pi-events.ts
64038
64434
  function createPiTranslatorState() {
64039
64435
  return { assistantSeq: 0, aborted: false };
64040
64436
  }
@@ -64045,23 +64441,23 @@ function translatePiEvent(ev, st2) {
64045
64441
  return [];
64046
64442
  }
64047
64443
  case "message_start": {
64048
- if (rec3(ev.message)?.role === "assistant") st2.assistantSeq += 1;
64444
+ if (rec2(ev.message)?.role === "assistant") st2.assistantSeq += 1;
64049
64445
  return [];
64050
64446
  }
64051
64447
  case "message_update":
64052
- return messageUpdate(rec3(ev.assistantMessageEvent), st2);
64448
+ return messageUpdate(rec2(ev.assistantMessageEvent), st2);
64053
64449
  case "message_end":
64054
- return messageEnd(rec3(ev.message), st2);
64450
+ return messageEnd(rec2(ev.message), st2);
64055
64451
  case "tool_execution_start": {
64056
- const toolUseId = str3(ev.toolCallId);
64057
- const tool = str3(ev.toolName);
64452
+ const toolUseId = str2(ev.toolCallId);
64453
+ const tool = str2(ev.toolName);
64058
64454
  if (!toolUseId || !tool) return [];
64059
64455
  return [{ kind: "tool_call", toolUseId, tool, toolKind: PI_TOOL_ID, input: ev.args ?? {} }];
64060
64456
  }
64061
64457
  case "tool_execution_end": {
64062
- const toolUseId = str3(ev.toolCallId);
64458
+ const toolUseId = str2(ev.toolCallId);
64063
64459
  if (!toolUseId) return [];
64064
- const text = contentText(rec3(ev.result)?.content);
64460
+ const text = contentText(rec2(ev.result)?.content);
64065
64461
  const out = { kind: "tool_result", toolUseId, output: text };
64066
64462
  if (ev.isError === true) out.error = text || "tool failed";
64067
64463
  return [out];
@@ -64077,9 +64473,9 @@ function translatePiEvent(ev, st2) {
64077
64473
  }
64078
64474
  function messageUpdate(e, st2) {
64079
64475
  if (!e) return [];
64080
- const delta = str3(e.delta);
64476
+ const delta = str2(e.delta);
64081
64477
  if (!delta) return [];
64082
- const partialId = `${st2.assistantSeq}-${num3(e.contentIndex) ?? 0}`;
64478
+ const partialId = `${st2.assistantSeq}-${num2(e.contentIndex) ?? 0}`;
64083
64479
  if (e.type === "text_delta") return [{ kind: "text", text: delta, partialId }];
64084
64480
  if (e.type === "thinking_delta") return [{ kind: "thinking", text: delta, partialId }];
64085
64481
  return [];
@@ -64088,32 +64484,32 @@ function messageEnd(m3, st2) {
64088
64484
  if (!m3 || m3.role !== "assistant") return [];
64089
64485
  const out = [];
64090
64486
  for (const b3 of arr(m3.content)) {
64091
- const block = rec3(b3);
64487
+ const block = rec2(b3);
64092
64488
  if (!block) continue;
64093
64489
  if (block.type === "thinking") {
64094
- const t2 = str3(block.thinking);
64490
+ const t2 = str2(block.thinking);
64095
64491
  if (t2) out.push({ kind: "thinking", text: t2 });
64096
64492
  } else if (block.type === "text") {
64097
- const t2 = str3(block.text);
64493
+ const t2 = str2(block.text);
64098
64494
  if (t2) out.push({ kind: "text", text: t2 });
64099
64495
  }
64100
64496
  }
64101
64497
  const patch = {};
64102
- const model = str3(m3.model);
64103
- const provider = str3(m3.provider);
64498
+ const model = str2(m3.model);
64499
+ const provider = str2(m3.provider);
64104
64500
  if (model) patch.resolvedModel = provider ? `${provider}/${model}` : model;
64105
- const u2 = rec3(m3.usage);
64106
- if (u2 && (num3(u2.totalTokens) ?? 0) > 0) {
64501
+ const u2 = rec2(m3.usage);
64502
+ if (u2 && (num2(u2.totalTokens) ?? 0) > 0) {
64107
64503
  patch.contextUsage = {
64108
- inputTokens: num3(u2.input) ?? 0,
64109
- outputTokens: num3(u2.output) ?? 0,
64110
- ...num3(u2.cacheRead) !== void 0 ? { cacheReadTokens: num3(u2.cacheRead) } : {},
64111
- ...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) } : {}
64112
64508
  };
64113
64509
  }
64114
64510
  if (patch.resolvedModel || patch.contextUsage) out.push({ kind: "meta_update", patch });
64115
- const stop = str3(m3.stopReason);
64116
- const err = str3(m3.errorMessage);
64511
+ const stop = str2(m3.stopReason);
64512
+ const err = str2(m3.errorMessage);
64117
64513
  if (stop === "aborted") {
64118
64514
  st2.aborted = true;
64119
64515
  } else if (stop === "error" || err) {
@@ -64181,8 +64577,8 @@ function drivePiSession(opts) {
64181
64577
  opts.onTurnCompleted?.("failure");
64182
64578
  };
64183
64579
  const handleUiRequest = (ev) => {
64184
- const method = str3(ev.method);
64185
- const id = str3(ev.id);
64580
+ const method = str2(ev.method);
64581
+ const id = str2(ev.id);
64186
64582
  logger?.warn("pi extension_ui_request unexpected in clawd session", { method });
64187
64583
  if (id && (method === "select" || method === "confirm" || method === "input" || method === "editor")) {
64188
64584
  client.send({ type: "extension_ui_response", id, cancelled: true });
@@ -64197,7 +64593,7 @@ function drivePiSession(opts) {
64197
64593
  }
64198
64594
  if (ev.type === "agent_settled") busy = false;
64199
64595
  if (ev.type === "response") {
64200
- 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) });
64201
64597
  return;
64202
64598
  }
64203
64599
  if (ev.type === "extension_ui_request") {
@@ -64219,15 +64615,15 @@ function drivePiSession(opts) {
64219
64615
  });
64220
64616
  const ready = client.request({ type: "get_state" }).then((r2) => {
64221
64617
  if (!r2.success) throw new Error(r2.error ?? "get_state failed");
64222
- const data = rec3(r2.data);
64223
- const sid = str3(data?.sessionId) ?? sessionId;
64618
+ const data = rec2(r2.data);
64619
+ const sid = str2(data?.sessionId) ?? sessionId;
64224
64620
  if (sid !== sessionId) logger?.error("pi sessionId mismatch", { expected: sessionId, actual: sid });
64225
- const model = rec3(data?.model);
64621
+ const model = rec2(data?.model);
64226
64622
  const patch = {};
64227
- const mid = str3(model?.id);
64228
- const prov = str3(model?.provider);
64623
+ const mid = str2(model?.id);
64624
+ const prov = str2(model?.provider);
64229
64625
  if (mid && mid !== "unknown") patch.resolvedModel = prov ? `${prov}/${mid}` : mid;
64230
- const cw = num3(model?.contextWindow);
64626
+ const cw = num2(model?.contextWindow);
64231
64627
  if (cw && cw > 0) patch.contextWindowSize = cw;
64232
64628
  const events = [{ kind: "session_init", toolSessionId: sid }];
64233
64629
  if (patch.resolvedModel || patch.contextWindowSize) events.push({ kind: "meta_update", patch });
@@ -64298,8 +64694,8 @@ ${dirs.map((d2) => `- ${d2}`).join("\n")}`;
64298
64694
  function createPiSession(ctx, sink, deps = {}) {
64299
64695
  const cmd = process.env.PI_BIN ?? "pi";
64300
64696
  const sessionId = ctx.toolSessionId ?? (0, import_node_crypto12.randomUUID)();
64301
- const skillsDir = import_node_path41.default.join(ctx.cwd, ".claude", "skills");
64302
- 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));
64303
64699
  const args = buildPiArgs(ctx, {
64304
64700
  sessionId,
64305
64701
  skillsDir: exists(skillsDir) ? skillsDir : void 0,
@@ -64342,201 +64738,6 @@ function createPiSession(ctx, sink, deps = {}) {
64342
64738
  return driver.session;
64343
64739
  }
64344
64740
 
64345
- // src/tools/pi-history.ts
64346
- var import_node_fs38 = __toESM(require("fs"), 1);
64347
- var import_node_os12 = __toESM(require("os"), 1);
64348
- var import_node_path42 = __toESM(require("path"), 1);
64349
- var PI_LIST_SESSIONS_LIMIT = 100;
64350
- var PI_LIST_SESSIONS_SCAN_CAP = 4e3;
64351
- function piAgentDir(env = process.env) {
64352
- return env.PI_CODING_AGENT_DIR || import_node_path42.default.join(import_node_os12.default.homedir(), ".pi", "agent");
64353
- }
64354
- function piSessionsRoot(env = process.env) {
64355
- return import_node_path42.default.join(piAgentDir(env), "sessions");
64356
- }
64357
- function encodePiCwd(cwd) {
64358
- return `--${cwd.replace(/^[\\/]+/, "").replace(/[\\/:]/g, "-")}--`;
64359
- }
64360
- function piProjectSessionDir(sessionsRoot, cwd) {
64361
- return import_node_path42.default.join(sessionsRoot, encodePiCwd(cwd));
64362
- }
64363
- function findInDir(dir, toolSessionId) {
64364
- let names;
64365
- try {
64366
- names = import_node_fs38.default.readdirSync(dir);
64367
- } catch {
64368
- return null;
64369
- }
64370
- const suffix = `_${toolSessionId}.jsonl`;
64371
- const hit = names.filter((n2) => n2.endsWith(suffix)).sort().at(-1);
64372
- return hit ? import_node_path42.default.join(dir, hit) : null;
64373
- }
64374
- function locatePiSessionFile(sessionsRoot, cwd, toolSessionId) {
64375
- const direct = findInDir(piProjectSessionDir(sessionsRoot, cwd), toolSessionId);
64376
- if (direct) return direct;
64377
- let dirs;
64378
- try {
64379
- dirs = import_node_fs38.default.readdirSync(sessionsRoot, { withFileTypes: true }).filter((d2) => d2.isDirectory()).map((d2) => d2.name);
64380
- } catch {
64381
- return null;
64382
- }
64383
- for (const d2 of dirs) {
64384
- const hit = findInDir(import_node_path42.default.join(sessionsRoot, d2), toolSessionId);
64385
- if (hit) return hit;
64386
- }
64387
- return null;
64388
- }
64389
- function parseLine2(line) {
64390
- const t2 = line.trim();
64391
- if (!t2) return null;
64392
- try {
64393
- return rec3(JSON.parse(t2)) ?? null;
64394
- } catch {
64395
- return null;
64396
- }
64397
- }
64398
- function parsePiSession(text) {
64399
- let meta = null;
64400
- const entries = /* @__PURE__ */ new Map();
64401
- let last = null;
64402
- for (const line of text.split("\n")) {
64403
- const o2 = parseLine2(line);
64404
- if (!o2) continue;
64405
- if (o2.type === "session") {
64406
- if (!meta) {
64407
- const timestamp = str3(o2.timestamp);
64408
- meta = { id: str3(o2.id) ?? "", cwd: str3(o2.cwd) ?? "", ...timestamp ? { timestamp } : {} };
64409
- }
64410
- continue;
64411
- }
64412
- const id = str3(o2.id);
64413
- if (!id) continue;
64414
- entries.set(id, o2);
64415
- last = o2;
64416
- }
64417
- const chain = [];
64418
- const seen = /* @__PURE__ */ new Set();
64419
- let cur = last;
64420
- while (cur) {
64421
- const id = str3(cur.id);
64422
- if (seen.has(id)) break;
64423
- seen.add(id);
64424
- chain.push(cur);
64425
- const pid = str3(cur.parentId);
64426
- cur = pid ? entries.get(pid) ?? null : null;
64427
- }
64428
- chain.reverse();
64429
- const messages = [];
64430
- for (const e of chain) {
64431
- if (e.type !== "message") continue;
64432
- const m3 = rec3(e.message);
64433
- if (!m3) continue;
64434
- const ts = str3(e.timestamp);
64435
- const base = { raw: e, uuid: str3(e.id), ...ts ? { ts } : {} };
64436
- switch (m3.role) {
64437
- case "user": {
64438
- const t2 = contentText(m3.content, "[image]");
64439
- if (t2) messages.push({ kind: "user-text", text: t2, ...base });
64440
- break;
64441
- }
64442
- case "assistant": {
64443
- for (const b3 of arr(m3.content)) {
64444
- const block = rec3(b3);
64445
- if (!block) continue;
64446
- if (block.type === "text") {
64447
- const t2 = str3(block.text);
64448
- if (t2) messages.push({ kind: "assistant-text", text: t2, ...base });
64449
- } else if (block.type === "toolCall") {
64450
- const toolUseId = str3(block.id);
64451
- const tool = str3(block.name);
64452
- if (toolUseId && tool) messages.push({ kind: "tool_call", tool, toolUseId, input: block.arguments ?? {}, ...base });
64453
- }
64454
- }
64455
- if (str3(m3.stopReason) !== "toolUse") messages.push({ kind: "turn-end", ...base });
64456
- break;
64457
- }
64458
- case "toolResult": {
64459
- const toolUseId = str3(m3.toolCallId);
64460
- if (!toolUseId) break;
64461
- const t2 = contentText(m3.content);
64462
- const msg = { kind: "tool_result", toolUseId, output: t2, ...base };
64463
- if (m3.isError === true) msg.error = t2 || "tool failed";
64464
- messages.push(msg);
64465
- break;
64466
- }
64467
- default:
64468
- break;
64469
- }
64470
- }
64471
- return { meta, messages };
64472
- }
64473
- var PiHistoryReader = class {
64474
- constructor(deps) {
64475
- this.deps = deps;
64476
- }
64477
- deps;
64478
- // projects/list 非 session-scoped、不知 tool(同 codex 的取舍)
64479
- async listProjects() {
64480
- return [];
64481
- }
64482
- async listSessions(args) {
64483
- const want = import_node_path42.default.resolve(args.projectPath);
64484
- const dir = piProjectSessionDir(this.deps.sessionsRoot, want);
64485
- let names;
64486
- try {
64487
- names = import_node_fs38.default.readdirSync(dir).filter((n2) => n2.endsWith(".jsonl"));
64488
- } catch {
64489
- return [];
64490
- }
64491
- const found = [];
64492
- let scanned = 0;
64493
- for (const n2 of names) {
64494
- if (++scanned > PI_LIST_SESSIONS_SCAN_CAP) break;
64495
- const file = import_node_path42.default.join(dir, n2);
64496
- const head = parseLine2(readFirstLine(file) ?? "");
64497
- if (!head || head.type !== "session") continue;
64498
- const cwd = str3(head.cwd);
64499
- const id = str3(head.id);
64500
- if (!cwd || !id || import_node_path42.default.resolve(cwd) !== want) continue;
64501
- let mtimeMs;
64502
- try {
64503
- mtimeMs = import_node_fs38.default.statSync(file).mtimeMs;
64504
- } catch {
64505
- continue;
64506
- }
64507
- found.push({ file, id, mtimeMs });
64508
- }
64509
- found.sort((a2, b3) => b3.mtimeMs - a2.mtimeMs);
64510
- return found.slice(0, PI_LIST_SESSIONS_LIMIT).map((f2) => {
64511
- const parsed = parsePiSession(import_node_fs38.default.readFileSync(f2.file, "utf8"));
64512
- const firstUser = parsed.messages.find((m3) => m3.kind === "user-text");
64513
- return {
64514
- toolSessionId: f2.id,
64515
- summary: firstUser?.text?.slice(0, 80) || "(\u7A7A\u4F1A\u8BDD)",
64516
- messageCount: parsed.messages.length,
64517
- updatedAt: new Date(f2.mtimeMs).toISOString()
64518
- };
64519
- });
64520
- }
64521
- async read(args) {
64522
- const file = locatePiSessionFile(this.deps.sessionsRoot, args.cwd, args.toolSessionId);
64523
- if (!file) throw new Error(`pi session not found for ${args.toolSessionId}`);
64524
- return pageMessages(parsePiSession(import_node_fs38.default.readFileSync(file, "utf8")).messages, args);
64525
- }
64526
- async listSubagents(_args) {
64527
- return [];
64528
- }
64529
- async readSubagent(args) {
64530
- throw new Error(`pi has no subagent threads (${args.subagentId})`);
64531
- }
64532
- computeRewindDiff() {
64533
- return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
64534
- }
64535
- listRewindableUserMessageIds() {
64536
- return [];
64537
- }
64538
- };
64539
-
64540
64741
  // src/tools/pi.ts
64541
64742
  var PI_DEFAULT_CONTEXT_WINDOW = 128e3;
64542
64743
  var PI_THINKING_EFFORTS = [
@@ -64565,20 +64766,20 @@ async function probePi(env = process.env) {
64565
64766
  return { available: false };
64566
64767
  }
64567
64768
  function mapPiModels(raw) {
64568
- const models = arr(rec3(raw)?.models).map(rec3).filter((m3) => m3 !== void 0);
64569
- 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));
64570
64771
  const out = [];
64571
64772
  for (const m3 of models) {
64572
- const id = str3(m3.id);
64573
- const provider = str3(m3.provider);
64773
+ const id = str2(m3.id);
64774
+ const provider = str2(m3.provider);
64574
64775
  if (!id || !provider) continue;
64575
- const cw = num3(m3.contextWindow);
64776
+ const cw = num2(m3.contextWindow);
64576
64777
  const levels = arr(m3.thinkingLevels).filter((v3) => typeof v3 === "string");
64577
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]];
64578
64779
  out.push({
64579
64780
  id: `${provider}/${id}`,
64580
64781
  // 与 --model 同一串
64581
- label: str3(m3.name) || id,
64782
+ label: str2(m3.name) || id,
64582
64783
  contextWindowSize: cw && cw > 0 ? cw : PI_DEFAULT_CONTEXT_WINDOW,
64583
64784
  efforts,
64584
64785
  // 分组按 provider;只有一个 provider 时不分组(一个横跨全表的标题没信息量,同 codex)
@@ -64644,18 +64845,18 @@ async function queryPiModels(deps = {}) {
64644
64845
  try {
64645
64846
  const r2 = await race(client.request({ type: "get_available_models" }));
64646
64847
  if (!r2.success) throw new Error(r2.error ?? "get_available_models failed");
64647
- 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);
64648
64849
  const enriched = [];
64649
64850
  for (const m3 of models) {
64650
- const provider = str3(m3.provider);
64651
- const id = str3(m3.id);
64851
+ const provider = str2(m3.provider);
64852
+ const id = str2(m3.id);
64652
64853
  let thinkingLevels;
64653
64854
  if (provider && id && !timedOut) {
64654
64855
  try {
64655
64856
  const set = await race(client.request({ type: "set_model", provider, modelId: id }));
64656
64857
  if (set.success) {
64657
64858
  const lv = await race(client.request({ type: "get_available_thinking_levels" }));
64658
- 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");
64659
64860
  if (lv.success && levels.length > 0) thinkingLevels = levels;
64660
64861
  }
64661
64862
  } catch {
@@ -64777,42 +64978,6 @@ function createCloudPiSession(ctx, sink, deps) {
64777
64978
  return driver.session;
64778
64979
  }
64779
64980
 
64780
- // src/tools/pi-cloud-history.ts
64781
- var CloudPiHistoryReader = class {
64782
- constructor(client, logger) {
64783
- this.client = client;
64784
- this.logger = logger;
64785
- }
64786
- client;
64787
- logger;
64788
- async listProjects() {
64789
- return [];
64790
- }
64791
- /** 云 persona 没有「关联已有会话」的动线:会话列表就是本机的会话记录 */
64792
- async listSessions() {
64793
- return [];
64794
- }
64795
- async read(args) {
64796
- const text = await this.client.fetchRollout(args.toolSessionId);
64797
- if (text === null) throw new Error(`pi session not found for ${args.toolSessionId}`);
64798
- const parsed = parsePiSession(text);
64799
- if (!parsed.meta) this.logger?.warn("cloud pi session has no header line", { toolSessionId: args.toolSessionId });
64800
- return pageMessages(parsed.messages, args);
64801
- }
64802
- async listSubagents(_args) {
64803
- return [];
64804
- }
64805
- async readSubagent(args) {
64806
- throw new Error(`pi has no subagent threads (${args.subagentId})`);
64807
- }
64808
- computeRewindDiff() {
64809
- return { canRewind: false, files: [], totalInsertions: 0, totalDeletions: 0 };
64810
- }
64811
- listRewindableUserMessageIds() {
64812
- return [];
64813
- }
64814
- };
64815
-
64816
64981
  // src/tools/pi-cloud.ts
64817
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" };
64818
64983
  var CLOUD_PI_CAPABILITIES = {
@@ -72088,7 +72253,7 @@ function computeMethodAccess(args) {
72088
72253
  }
72089
72254
 
72090
72255
  // src/version.ts
72091
- var version = "0.2.582".length > 0 ? "0.2.582" : "dev";
72256
+ var version = "0.2.583".length > 0 ? "0.2.583" : "dev";
72092
72257
 
72093
72258
  // src/cli-probe/probe.ts
72094
72259
  var fs80 = __toESM(require("fs"), 1);
@@ -75769,6 +75934,18 @@ async function startDaemon(config) {
75769
75934
  githubIdentity,
75770
75935
  githubApi: (token) => createGithubApi({ token }),
75771
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
+ },
75772
75949
  now: () => Date.now(),
75773
75950
  warn: (msg, meta) => logger.warn(msg, meta),
75774
75951
  // 在不在线上 = gateway 正式登记表里有没有它(design 2026-09-24-deploy-unpublish §3.3):跟 persona 列表的云卡片同一个来源。
@@ -75909,13 +76086,19 @@ async function startDaemon(config) {
75909
76086
  const viewerAssetLoader = tryLoadViewerAssets(logger);
75910
76087
  const shareUiAssetLoader = tryLoadShareUi(logger);
75911
76088
  const dashboardPages = loadDashboardPages(logger);
76089
+ const dashboardApi = () => config.clawosApi ?? DEFAULT_CLAWOS_API;
75912
76090
  const dashboardRoutes = createDashboardRoutes({
75913
76091
  getSecret: () => authFile.token,
75914
76092
  loadPageHtml: (page) => dashboardPages.get(page) ?? null,
75915
- gateway: {
75916
- personas: async () => (await cloudGateway.listPersonas()).map((p3) => p3.personaId),
75917
- metrics: (q3) => cloudGateway.fetchMetrics(q3)
75918
- }
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
+ })
75919
76102
  });
75920
76103
  const httpRouter = createHttpRouter({
75921
76104
  authResolver,