@clawos-dev/clawd 0.2.582 → 0.2.584

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
 
@@ -52069,6 +53313,23 @@ function stringValue(value) {
52069
53313
  return typeof value === "string" && value.length > 0 ? value : void 0;
52070
53314
  }
52071
53315
 
53316
+ // src/tools/first-byte.ts
53317
+ var VISIBLE = /* @__PURE__ */ new Set(["thinking", "tool_call", "text"]);
53318
+ function createFirstByteClock(now = Date.now) {
53319
+ let at3 = null;
53320
+ return {
53321
+ reset() {
53322
+ at3 = null;
53323
+ },
53324
+ see(events) {
53325
+ if (at3 === null && events.some((e) => VISIBLE.has(e.kind))) at3 = now();
53326
+ },
53327
+ afterFirstByteMs() {
53328
+ return at3 === null ? void 0 : Math.max(0, Math.round(now() - at3));
53329
+ }
53330
+ };
53331
+ }
53332
+
52072
53333
  // src/tools/codex-app-server-params.ts
52073
53334
  function resolveApprovalPolicy(ctx) {
52074
53335
  if (ctx.personaMode === "owner" || ctx.personaMode === "guest") return "never";
@@ -52204,6 +53465,7 @@ function driveCodexThread(opts) {
52204
53465
  let threadId = ctx.toolSessionId;
52205
53466
  let turnId;
52206
53467
  let turnOpen = false;
53468
+ const firstByte = createFirstByteClock(opts.now);
52207
53469
  const approvalIds = /* @__PURE__ */ new Map();
52208
53470
  const subagents = new CodexSubagentTracker();
52209
53471
  const effort = ctx.effort;
@@ -52235,10 +53497,13 @@ function driveCodexThread(opts) {
52235
53497
  ...subagents.handleParentNotification(method, params),
52236
53498
  ...translateNotification(method, params)
52237
53499
  ] : [];
52238
- if (events.length) sink.pushEvents(events);
53500
+ if (events.length) {
53501
+ if (turnOpen) firstByte.see(events);
53502
+ sink.pushEvents(events);
53503
+ }
52239
53504
  if (route === "foreground" && method === "turn/completed") {
52240
53505
  turnOpen = false;
52241
- opts.onTurnCompleted?.(turnOutcomeOf(params));
53506
+ opts.onTurnCompleted?.(turnOutcomeOf(params), firstByte.afterFirstByteMs());
52242
53507
  }
52243
53508
  },
52244
53509
  onServerRequest: (id, method, params) => {
@@ -52287,6 +53552,7 @@ function driveCodexThread(opts) {
52287
53552
  const opened = !turnOpen;
52288
53553
  if (opened) {
52289
53554
  turnOpen = true;
53555
+ firstByte.reset();
52290
53556
  opts.onTurnStarting?.();
52291
53557
  }
52292
53558
  void client.request("turn/start", {
@@ -52303,7 +53569,7 @@ function driveCodexThread(opts) {
52303
53569
  ]);
52304
53570
  if (!opened) return;
52305
53571
  turnOpen = false;
52306
- opts.onTurnCompleted?.("failure");
53572
+ opts.onTurnCompleted?.("failure", firstByte.afterFirstByteMs());
52307
53573
  });
52308
53574
  }
52309
53575
  });
@@ -52340,17 +53606,17 @@ function stringValue2(value) {
52340
53606
  }
52341
53607
 
52342
53608
  // src/tools/mcp-server-defs.ts
52343
- var import_node_fs18 = __toESM(require("fs"), 1);
52344
- var import_node_path16 = __toESM(require("path"), 1);
53609
+ var import_node_fs21 = __toESM(require("fs"), 1);
53610
+ var import_node_path19 = __toESM(require("path"), 1);
52345
53611
  var HTTP_TYPES = /* @__PURE__ */ new Set(["http", "sse", "streamable_http", "streamable-http"]);
52346
53612
  function loadMcpServerDefs(args) {
52347
53613
  const out = [];
52348
53614
  const seen = /* @__PURE__ */ new Set();
52349
- const sources = [...args.mcpConfigPaths, import_node_path16.default.join(args.cwd, ".mcp.json")];
53615
+ const sources = [...args.mcpConfigPaths, import_node_path19.default.join(args.cwd, ".mcp.json")];
52350
53616
  for (const file of sources) {
52351
53617
  let parsed;
52352
53618
  try {
52353
- parsed = JSON.parse(import_node_fs18.default.readFileSync(file, "utf8"));
53619
+ parsed = JSON.parse(import_node_fs21.default.readFileSync(file, "utf8"));
52354
53620
  } catch (e) {
52355
53621
  const code = e.code;
52356
53622
  if (code !== "ENOENT") {
@@ -52414,18 +53680,18 @@ function loadMcpServerDefs(args) {
52414
53680
  }
52415
53681
 
52416
53682
  // src/tools/codex-agents-config.ts
52417
- var import_node_fs19 = __toESM(require("fs"), 1);
52418
- var import_node_path17 = __toESM(require("path"), 1);
53683
+ var import_node_fs22 = __toESM(require("fs"), 1);
53684
+ var import_node_path20 = __toESM(require("path"), 1);
52419
53685
  var AGENTS_SECTION = /^\[agents\][^\n]*\n([\s\S]*?)(?=^\[|$(?![\s\S]))/m;
52420
53686
  function readKey(section, key) {
52421
53687
  const m3 = new RegExp(`^[ \\t]*${key}[ \\t]*=[ \\t]*(["'])(.*?)\\1`, "m").exec(section);
52422
53688
  return m3?.[2].trim() || void 0;
52423
53689
  }
52424
53690
  function loadCodexAgentsConfig(args) {
52425
- const file = import_node_path17.default.join(args.cwd, ".codex", "config.toml");
53691
+ const file = import_node_path20.default.join(args.cwd, ".codex", "config.toml");
52426
53692
  let text;
52427
53693
  try {
52428
- text = import_node_fs19.default.readFileSync(file, "utf8");
53694
+ text = import_node_fs22.default.readFileSync(file, "utf8");
52429
53695
  } catch (e) {
52430
53696
  const code = e.code;
52431
53697
  if (code !== "ENOENT") {
@@ -52481,7 +53747,7 @@ function warmCodexFeatureCache() {
52481
53747
  }
52482
53748
 
52483
53749
  // src/attachment/mime.ts
52484
- var import_node_path18 = __toESM(require("path"), 1);
53750
+ var import_node_path21 = __toESM(require("path"), 1);
52485
53751
  var TEXT_PLAIN = "text/plain; charset=utf-8";
52486
53752
  var EXT_TO_NATIVE_MIME = {
52487
53753
  // 图片
@@ -52588,7 +53854,7 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
52588
53854
  ".mk"
52589
53855
  ]);
52590
53856
  function lookupMime(filePathOrName) {
52591
- const ext = import_node_path18.default.extname(filePathOrName).toLowerCase();
53857
+ const ext = import_node_path21.default.extname(filePathOrName).toLowerCase();
52592
53858
  if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
52593
53859
  if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
52594
53860
  return "application/octet-stream";
@@ -52637,7 +53903,7 @@ function createCodexSession(ctx, sink, deps = {}) {
52637
53903
  threadParams: threadStartParams(ctx),
52638
53904
  // persona 惯例装在 <cwd>/.claude/skills/,经 extraRoots 注入 discovery
52639
53905
  // (skills:list RPC 侧 codex-skills.ts 同款注入,两处保持一致)。
52640
- skillsExtraRoots: [import_node_path19.default.join(ctx.cwd, ".claude", "skills")],
53906
+ skillsExtraRoots: [import_node_path22.default.join(ctx.cwd, ".claude", "skills")],
52641
53907
  // 附件图片:验签认出本机 absPath → localImage,codex 自己读文件(TUI 拖图 / SDK local_image 同一条路)。
52642
53908
  // 0.145 起 http(s) URL 在 turn/start 入口被拒,不能再按 url 传(codex-app-server-params.ts 头注释)。
52643
53909
  // 与云上 cloudImageInput 同一道 mime 门:非图片(签过的 .md 之类)留文本,别让 codex 读到一半才炸。
@@ -52791,18 +54057,18 @@ function mergeProviderModels(native, provider) {
52791
54057
 
52792
54058
  // src/tools/codex-provider-models.ts
52793
54059
  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);
54060
+ var import_node_fs23 = __toESM(require("fs"), 1);
54061
+ var import_node_os9 = __toESM(require("os"), 1);
54062
+ var import_node_path23 = __toESM(require("path"), 1);
52797
54063
  var import_node_util = require("util");
52798
54064
  var PROVIDER_FETCH_TIMEOUT_MS = 3e3;
52799
- function asRecord(v3) {
54065
+ function asRecord2(v3) {
52800
54066
  return v3 && typeof v3 === "object" && !Array.isArray(v3) ? v3 : null;
52801
54067
  }
52802
54068
  function readAuthKey(codexHome) {
52803
54069
  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;
54070
+ const raw = JSON.parse(import_node_fs23.default.readFileSync(import_node_path23.default.join(codexHome, "auth.json"), "utf8"));
54071
+ const key = asRecord2(raw)?.OPENAI_API_KEY;
52806
54072
  return typeof key === "string" && key ? key : void 0;
52807
54073
  } catch {
52808
54074
  return void 0;
@@ -52829,19 +54095,19 @@ async function runAuthCommand(name, auth, deps) {
52829
54095
  return void 0;
52830
54096
  }
52831
54097
  async function resolveProviderEndpoint(rawConfig, deps = {}) {
52832
- const config = asRecord(asRecord(rawConfig)?.config);
54098
+ const config = asRecord2(asRecord2(rawConfig)?.config);
52833
54099
  if (!config) return null;
52834
54100
  const name = config.model_provider;
52835
54101
  if (typeof name !== "string" || !name) return null;
52836
- const entry = asRecord(asRecord(config.model_providers)?.[name]);
54102
+ const entry = asRecord2(asRecord2(config.model_providers)?.[name]);
52837
54103
  if (!entry) return null;
52838
54104
  const baseUrl = entry.base_url;
52839
54105
  if (typeof baseUrl !== "string" || !baseUrl) return null;
52840
54106
  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");
54107
+ const codexHome = deps.codexHome ?? env.CODEX_HOME ?? import_node_path23.default.join(import_node_os9.default.homedir(), ".codex");
52842
54108
  const envKey = typeof entry.env_key === "string" && entry.env_key ? entry.env_key : null;
52843
54109
  const inlineToken = typeof entry.experimental_bearer_token === "string" && entry.experimental_bearer_token ? entry.experimental_bearer_token : void 0;
52844
- const auth = asRecord(entry.auth);
54110
+ const auth = asRecord2(entry.auth);
52845
54111
  let token;
52846
54112
  if (envKey) token = env[envKey];
52847
54113
  else if (inlineToken) token = inlineToken;
@@ -52869,11 +54135,11 @@ async function fetchProviderModels(endpoint, deps = {}) {
52869
54135
  try {
52870
54136
  const res = await fetchImpl(url, { headers: endpoint.headers, signal: controller.signal });
52871
54137
  if (!res.ok) throw new Error(`provider model list failed: HTTP ${res.status} from ${url}`);
52872
- const data = asRecord(await res.json())?.data;
54138
+ const data = asRecord2(await res.json())?.data;
52873
54139
  if (!Array.isArray(data)) throw new Error(`provider model list: \`data\` is not an array (${url})`);
52874
54140
  const out = [];
52875
54141
  for (const item of data) {
52876
- const e = asRecord(item);
54142
+ const e = asRecord2(item);
52877
54143
  if (!e || typeof e.id !== "string" || !e.id) continue;
52878
54144
  out.push({ id: e.id, ...typeof e.owned_by === "string" ? { ownedBy: e.owned_by } : {} });
52879
54145
  }
@@ -52953,10 +54219,10 @@ function sanitizeCodexModel(model) {
52953
54219
  return model;
52954
54220
  }
52955
54221
  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 };
54222
+ if (env.CODEX_BIN && import_node_fs24.default.existsSync(env.CODEX_BIN)) return { available: true, path: env.CODEX_BIN };
52957
54223
  try {
52958
54224
  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 };
54225
+ if (out && import_node_fs24.default.existsSync(out)) return { available: true, path: out };
52960
54226
  } catch {
52961
54227
  }
52962
54228
  return { available: false };
@@ -53076,9 +54342,9 @@ var CodexAdapter = class {
53076
54342
 
53077
54343
  // src/tools/claude-tui.ts
53078
54344
  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);
54345
+ var import_node_fs25 = __toESM(require("fs"), 1);
54346
+ var import_node_os10 = __toESM(require("os"), 1);
54347
+ var import_node_path24 = __toESM(require("path"), 1);
53082
54348
  var import_headless = __toESM(require_xterm_headless(), 1);
53083
54349
 
53084
54350
  // ../node_modules/.pnpm/@xterm+addon-serialize@0.14.0/node_modules/@xterm/addon-serialize/lib/addon-serialize.mjs
@@ -54303,10 +55569,10 @@ function buildTuiSpawnArgs(ctx, isResume = false) {
54303
55569
  }
54304
55570
  function jsonlExistsForCtx(ctx) {
54305
55571
  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`);
55572
+ const home = import_node_os10.default.homedir();
55573
+ const file = import_node_path24.default.join(home, ".claude", "projects", cwdToHashDir(ctx.cwd), `${ctx.toolSessionId}.jsonl`);
54308
55574
  try {
54309
- return import_node_fs22.default.statSync(file).isFile();
55575
+ return import_node_fs25.default.statSync(file).isFile();
54310
55576
  } catch {
54311
55577
  return false;
54312
55578
  }
@@ -54391,7 +55657,7 @@ var PersonaDispatchManager = class {
54391
55657
 
54392
55658
  // src/dispatch/store.ts
54393
55659
  var import_promises = __toESM(require("fs/promises"), 1);
54394
- var import_node_path22 = __toESM(require("path"), 1);
55660
+ var import_node_path25 = __toESM(require("path"), 1);
54395
55661
 
54396
55662
  // src/shift/constants.ts
54397
55663
  var MAX_RUNS_PER_SHIFT = 30;
@@ -54429,7 +55695,7 @@ function createDispatchStore(deps) {
54429
55695
  flushTimer = null;
54430
55696
  }
54431
55697
  const content = { version: 1, records };
54432
- await import_promises.default.mkdir(import_node_path22.default.dirname(deps.filePath), { recursive: true });
55698
+ await import_promises.default.mkdir(import_node_path25.default.dirname(deps.filePath), { recursive: true });
54433
55699
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
54434
55700
  await import_promises.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
54435
55701
  await import_promises.default.rename(tmp, deps.filePath);
@@ -54498,7 +55764,7 @@ function createDispatchStore(deps) {
54498
55764
 
54499
55765
  // src/channel/json-store.ts
54500
55766
  var import_promises2 = __toESM(require("fs/promises"), 1);
54501
- var import_node_path23 = __toESM(require("path"), 1);
55767
+ var import_node_path26 = __toESM(require("path"), 1);
54502
55768
  function createJsonStore(deps) {
54503
55769
  let items = [];
54504
55770
  let flushTimer = null;
@@ -54509,7 +55775,7 @@ function createJsonStore(deps) {
54509
55775
  flushTimer = null;
54510
55776
  }
54511
55777
  const content = { version: version2, [deps.key]: items };
54512
- await import_promises2.default.mkdir(import_node_path23.default.dirname(deps.filePath), { recursive: true });
55778
+ await import_promises2.default.mkdir(import_node_path26.default.dirname(deps.filePath), { recursive: true });
54513
55779
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
54514
55780
  await import_promises2.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
54515
55781
  await import_promises2.default.rename(tmp, deps.filePath);
@@ -54801,10 +56067,10 @@ function createTopicStore(deps) {
54801
56067
  }
54802
56068
 
54803
56069
  // src/dispatch/mcp-config.ts
54804
- var import_node_fs23 = __toESM(require("fs"), 1);
54805
- var import_node_path24 = __toESM(require("path"), 1);
56070
+ var import_node_fs26 = __toESM(require("fs"), 1);
56071
+ var import_node_path27 = __toESM(require("path"), 1);
54806
56072
  function dispatchMcpConfigPath(dataDir) {
54807
- return import_node_path24.default.join(dataDir, "dispatch.mcp.json");
56073
+ return import_node_path27.default.join(dataDir, "dispatch.mcp.json");
54808
56074
  }
54809
56075
  function writeDispatchMcpConfig(args) {
54810
56076
  const cfgPath = dispatchMcpConfigPath(args.dataDir);
@@ -54823,16 +56089,16 @@ function writeDispatchMcpConfig(args) {
54823
56089
  }
54824
56090
  }
54825
56091
  };
54826
- import_node_fs23.default.mkdirSync(args.dataDir, { recursive: true });
54827
- import_node_fs23.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
56092
+ import_node_fs26.default.mkdirSync(args.dataDir, { recursive: true });
56093
+ import_node_fs26.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
54828
56094
  return cfgPath;
54829
56095
  }
54830
56096
 
54831
56097
  // src/ticket/mcp-config.ts
54832
- var import_node_fs24 = __toESM(require("fs"), 1);
54833
- var import_node_path25 = __toESM(require("path"), 1);
56098
+ var import_node_fs27 = __toESM(require("fs"), 1);
56099
+ var import_node_path28 = __toESM(require("path"), 1);
54834
56100
  function ticketMcpConfigPath(dataDir) {
54835
- return import_node_path25.default.join(dataDir, "ticket.mcp.json");
56101
+ return import_node_path28.default.join(dataDir, "ticket.mcp.json");
54836
56102
  }
54837
56103
  function writeTicketMcpConfig(args) {
54838
56104
  const cfgPath = ticketMcpConfigPath(args.dataDir);
@@ -54854,16 +56120,16 @@ function writeTicketMcpConfig(args) {
54854
56120
  }
54855
56121
  }
54856
56122
  };
54857
- import_node_fs24.default.mkdirSync(args.dataDir, { recursive: true });
54858
- import_node_fs24.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
56123
+ import_node_fs27.default.mkdirSync(args.dataDir, { recursive: true });
56124
+ import_node_fs27.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
54859
56125
  return cfgPath;
54860
56126
  }
54861
56127
 
54862
56128
  // src/shift/mcp-config.ts
54863
- var import_node_fs25 = __toESM(require("fs"), 1);
54864
- var import_node_path26 = __toESM(require("path"), 1);
56129
+ var import_node_fs28 = __toESM(require("fs"), 1);
56130
+ var import_node_path29 = __toESM(require("path"), 1);
54865
56131
  function shiftMcpConfigPath(dataDir) {
54866
- return import_node_path26.default.join(dataDir, "shift.mcp.json");
56132
+ return import_node_path29.default.join(dataDir, "shift.mcp.json");
54867
56133
  }
54868
56134
  async function writeShiftMcpConfig(args) {
54869
56135
  const cfgPath = shiftMcpConfigPath(args.dataDir);
@@ -54882,16 +56148,16 @@ async function writeShiftMcpConfig(args) {
54882
56148
  }
54883
56149
  }
54884
56150
  };
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");
56151
+ await import_node_fs28.default.promises.mkdir(args.dataDir, { recursive: true });
56152
+ await import_node_fs28.default.promises.writeFile(cfgPath, JSON.stringify(content, null, 2), "utf8");
54887
56153
  return cfgPath;
54888
56154
  }
54889
56155
 
54890
56156
  // src/inbox/mcp-config.ts
54891
- var import_node_fs26 = __toESM(require("fs"), 1);
54892
- var import_node_path27 = __toESM(require("path"), 1);
56157
+ var import_node_fs29 = __toESM(require("fs"), 1);
56158
+ var import_node_path30 = __toESM(require("path"), 1);
54893
56159
  function inboxMcpConfigPath(dataDir) {
54894
- return import_node_path27.default.join(dataDir, "inbox.mcp.json");
56160
+ return import_node_path30.default.join(dataDir, "inbox.mcp.json");
54895
56161
  }
54896
56162
  async function writeInboxMcpConfig(args) {
54897
56163
  const cfgPath = inboxMcpConfigPath(args.dataDir);
@@ -54910,16 +56176,16 @@ async function writeInboxMcpConfig(args) {
54910
56176
  }
54911
56177
  }
54912
56178
  };
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");
56179
+ await import_node_fs29.default.promises.mkdir(args.dataDir, { recursive: true });
56180
+ await import_node_fs29.default.promises.writeFile(cfgPath, JSON.stringify(content, null, 2), "utf8");
54915
56181
  return cfgPath;
54916
56182
  }
54917
56183
 
54918
56184
  // src/peer-ops/mcp-config.ts
54919
- var import_node_fs27 = __toESM(require("fs"), 1);
54920
- var import_node_path28 = __toESM(require("path"), 1);
56185
+ var import_node_fs30 = __toESM(require("fs"), 1);
56186
+ var import_node_path31 = __toESM(require("path"), 1);
54921
56187
  function peerOpsMcpConfigPath(dataDir) {
54922
- return import_node_path28.default.join(dataDir, "peer-ops.mcp.json");
56188
+ return import_node_path31.default.join(dataDir, "peer-ops.mcp.json");
54923
56189
  }
54924
56190
  function writePeerOpsMcpConfig(args) {
54925
56191
  const cfgPath = peerOpsMcpConfigPath(args.dataDir);
@@ -54936,16 +56202,16 @@ function writePeerOpsMcpConfig(args) {
54936
56202
  }
54937
56203
  }
54938
56204
  };
54939
- import_node_fs27.default.mkdirSync(args.dataDir, { recursive: true });
54940
- import_node_fs27.default.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
56205
+ import_node_fs30.default.mkdirSync(args.dataDir, { recursive: true });
56206
+ import_node_fs30.default.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
54941
56207
  return cfgPath;
54942
56208
  }
54943
56209
 
54944
56210
  // src/rpc-tool/mcp-config.ts
54945
- var import_node_fs28 = __toESM(require("fs"), 1);
54946
- var import_node_path29 = __toESM(require("path"), 1);
56211
+ var import_node_fs31 = __toESM(require("fs"), 1);
56212
+ var import_node_path32 = __toESM(require("path"), 1);
54947
56213
  function rpcToolMcpConfigPath(dataDir) {
54948
- return import_node_path29.default.join(dataDir, "rpc-tool.mcp.json");
56214
+ return import_node_path32.default.join(dataDir, "rpc-tool.mcp.json");
54949
56215
  }
54950
56216
  function writeRpcToolMcpConfig(args) {
54951
56217
  const cfgPath = rpcToolMcpConfigPath(args.dataDir);
@@ -54964,29 +56230,29 @@ function writeRpcToolMcpConfig(args) {
54964
56230
  }
54965
56231
  }
54966
56232
  };
54967
- import_node_fs28.default.mkdirSync(args.dataDir, { recursive: true });
54968
- import_node_fs28.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
56233
+ import_node_fs31.default.mkdirSync(args.dataDir, { recursive: true });
56234
+ import_node_fs31.default.writeFileSync(cfgPath, JSON.stringify(content, null, 2), "utf8");
54969
56235
  return cfgPath;
54970
56236
  }
54971
56237
 
54972
56238
  // src/deploy/secret-table.ts
54973
- var import_node_fs29 = __toESM(require("fs"), 1);
54974
- var import_node_path30 = __toESM(require("path"), 1);
56239
+ var import_node_fs32 = __toESM(require("fs"), 1);
56240
+ var import_node_path33 = __toESM(require("path"), 1);
54975
56241
  var import_node_url5 = require("url");
54976
56242
  var import_meta5 = {};
54977
56243
  var LOCAL_SECRETS_DIRNAME = "secrets";
54978
56244
  function findSecretsBundleRoot(logger) {
54979
56245
  const candidates = [];
54980
56246
  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"));
56247
+ const here = import_node_path33.default.dirname((0, import_node_url5.fileURLToPath)(import_meta5.url));
56248
+ candidates.push(import_node_path33.default.resolve(here, "..", "secrets"));
54983
56249
  } catch {
54984
56250
  }
54985
56251
  const fromArgv = bundleSiblingFromArgv(process.argv[1], "secrets");
54986
56252
  if (fromArgv) candidates.push(fromArgv);
54987
56253
  for (const c2 of candidates) {
54988
56254
  try {
54989
- if (import_node_fs29.default.statSync(c2).isDirectory()) return c2;
56255
+ if (import_node_fs32.default.statSync(c2).isDirectory()) return c2;
54990
56256
  } catch {
54991
56257
  }
54992
56258
  }
@@ -55012,7 +56278,7 @@ function readDir(dir, logger) {
55012
56278
  const out = {};
55013
56279
  let files;
55014
56280
  try {
55015
- files = import_node_fs29.default.readdirSync(dir).filter((f2) => f2.endsWith(".env")).sort();
56281
+ files = import_node_fs32.default.readdirSync(dir).filter((f2) => f2.endsWith(".env")).sort();
55016
56282
  } catch (e) {
55017
56283
  const code = e.code;
55018
56284
  if (code !== "ENOENT") logger?.warn("secrets.dir-unreadable", { dir, code: code ?? "unknown" });
@@ -55020,19 +56286,19 @@ function readDir(dir, logger) {
55020
56286
  }
55021
56287
  for (const f2 of files) {
55022
56288
  try {
55023
- Object.assign(out, parseEnvFile(import_node_fs29.default.readFileSync(import_node_path30.default.join(dir, f2), "utf8")));
56289
+ Object.assign(out, parseEnvFile(import_node_fs32.default.readFileSync(import_node_path33.default.join(dir, f2), "utf8")));
55024
56290
  } catch (e) {
55025
- logger?.warn("secrets.file-unreadable", { file: import_node_path30.default.join(dir, f2), error: e.message });
56291
+ logger?.warn("secrets.file-unreadable", { file: import_node_path33.default.join(dir, f2), error: e.message });
55026
56292
  }
55027
56293
  }
55028
56294
  return out;
55029
56295
  }
55030
56296
  function upsertEnvFileVar(file, key, value) {
55031
56297
  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 });
56298
+ import_node_fs32.default.mkdirSync(import_node_path33.default.dirname(file), { recursive: true, mode: 448 });
55033
56299
  let lines = [];
55034
56300
  try {
55035
- lines = import_node_fs29.default.readFileSync(file, "utf8").split("\n");
56301
+ lines = import_node_fs32.default.readFileSync(file, "utf8").split("\n");
55036
56302
  if (lines[lines.length - 1] === "") lines.pop();
55037
56303
  } catch {
55038
56304
  }
@@ -55040,12 +56306,12 @@ function upsertEnvFileVar(file, key, value) {
55040
56306
  let replaced = false;
55041
56307
  const out = lines.map((l2) => re2.test(l2) ? (replaced = true, `${key}=${value}`) : l2);
55042
56308
  if (!replaced) out.push(`${key}=${value}`);
55043
- import_node_fs29.default.writeFileSync(file, `${out.join("\n")}
56309
+ import_node_fs32.default.writeFileSync(file, `${out.join("\n")}
55044
56310
  `, { encoding: "utf8", mode: 384 });
55045
56311
  }
55046
56312
  function readEnvFileVar(file, key) {
55047
56313
  try {
55048
- return parseEnvFile(import_node_fs29.default.readFileSync(file, "utf8"))[key];
56314
+ return parseEnvFile(import_node_fs32.default.readFileSync(file, "utf8"))[key];
55049
56315
  } catch {
55050
56316
  return void 0;
55051
56317
  }
@@ -55054,7 +56320,7 @@ function removeEnvFileVar(file, key) {
55054
56320
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`invalid env name: ${key}`);
55055
56321
  let lines;
55056
56322
  try {
55057
- lines = import_node_fs29.default.readFileSync(file, "utf8").split("\n");
56323
+ lines = import_node_fs32.default.readFileSync(file, "utf8").split("\n");
55058
56324
  } catch {
55059
56325
  return;
55060
56326
  }
@@ -55063,10 +56329,10 @@ function removeEnvFileVar(file, key) {
55063
56329
  const out = lines.filter((l2) => !re2.test(l2));
55064
56330
  if (out.length === lines.length) return;
55065
56331
  if (out.every((l2) => l2.trim() === "" || l2.trim().startsWith("#"))) {
55066
- import_node_fs29.default.rmSync(file, { force: true });
56332
+ import_node_fs32.default.rmSync(file, { force: true });
55067
56333
  return;
55068
56334
  }
55069
- import_node_fs29.default.writeFileSync(file, `${out.join("\n")}
56335
+ import_node_fs32.default.writeFileSync(file, `${out.join("\n")}
55070
56336
  `, { encoding: "utf8", mode: 384 });
55071
56337
  }
55072
56338
  function readSecretTable(args) {
@@ -55077,12 +56343,12 @@ function readSecretTable(args) {
55077
56343
  }
55078
56344
 
55079
56345
  // src/dispatch/source-transcript.ts
55080
- var import_node_path31 = __toESM(require("path"), 1);
56346
+ var import_node_path34 = __toESM(require("path"), 1);
55081
56347
  init_claude_history();
55082
56348
  var NO_SOURCE_TRANSCRIPT = "(no transcript yet \u2014 operate from the task description alone)";
55083
56349
  function resolveSourceJsonlPath(sourceFile, home) {
55084
56350
  if (!sourceFile?.toolSessionId) return NO_SOURCE_TRANSCRIPT;
55085
- return import_node_path31.default.join(
56351
+ return import_node_path34.default.join(
55086
56352
  home,
55087
56353
  ".claude",
55088
56354
  "projects",
@@ -55093,7 +56359,7 @@ function resolveSourceJsonlPath(sourceFile, home) {
55093
56359
 
55094
56360
  // src/shift/store.ts
55095
56361
  var import_promises3 = __toESM(require("fs/promises"), 1);
55096
- var import_node_path32 = __toESM(require("path"), 1);
56362
+ var import_node_path35 = __toESM(require("path"), 1);
55097
56363
  var import_node_crypto8 = require("crypto");
55098
56364
 
55099
56365
  // src/shift/schedule.ts
@@ -55168,7 +56434,7 @@ function createShiftStore(deps) {
55168
56434
  flushTimer = null;
55169
56435
  }
55170
56436
  const content = { version: 1, shifts };
55171
- await import_promises3.default.mkdir(import_node_path32.default.dirname(deps.filePath), { recursive: true });
56437
+ await import_promises3.default.mkdir(import_node_path35.default.dirname(deps.filePath), { recursive: true });
55172
56438
  const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
55173
56439
  await import_promises3.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
55174
56440
  await import_promises3.default.rename(tmp, deps.filePath);
@@ -55622,14 +56888,14 @@ function redactEvidence(evidence) {
55622
56888
 
55623
56889
  // src/deploy/deploy-file.ts
55624
56890
  var import_promises4 = __toESM(require("fs/promises"), 1);
55625
- var import_node_path34 = __toESM(require("path"), 1);
56891
+ var import_node_path37 = __toESM(require("path"), 1);
55626
56892
  var import_node_crypto9 = require("crypto");
55627
56893
  init_zod();
55628
56894
  init_protocol();
55629
56895
 
55630
56896
  // src/deploy/connectors.ts
55631
- var import_node_fs30 = __toESM(require("fs"), 1);
55632
- var import_node_path33 = __toESM(require("path"), 1);
56897
+ var import_node_fs33 = __toESM(require("fs"), 1);
56898
+ var import_node_path36 = __toESM(require("path"), 1);
55633
56899
  init_zod();
55634
56900
  init_protocol();
55635
56901
  var PLACEHOLDER2 = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
@@ -55679,11 +56945,11 @@ var PACKAGE_RUNNERS = /* @__PURE__ */ new Set(["npx", "uvx", "pnpx", "bunx"]);
55679
56945
  var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["node", "bun", "deno", "python", "python3"]);
55680
56946
  var HOST_ARG_PLACEHOLDER = "CLAWD_PERSONA_DIR";
55681
56947
  function scanMcpConnectors(personaDir) {
55682
- const file = import_node_path33.default.join(personaDir, ".mcp.json");
56948
+ const file = import_node_path36.default.join(personaDir, ".mcp.json");
55683
56949
  const out = { readable: true, connectors: [], evidence: [], warnings: [] };
55684
56950
  let parsed;
55685
56951
  try {
55686
- parsed = JSON.parse(import_node_fs30.default.readFileSync(file, "utf8"));
56952
+ parsed = JSON.parse(import_node_fs33.default.readFileSync(file, "utf8"));
55687
56953
  } catch (e) {
55688
56954
  if (e.code === "ENOENT") return out;
55689
56955
  out.readable = false;
@@ -55778,8 +57044,8 @@ function mergeScannedConnectors(existing, scanned) {
55778
57044
  }
55779
57045
 
55780
57046
  // 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");
57047
+ var DEPLOY_FILE = import_node_path37.default.join(".clawd", "deploy.json");
57048
+ var SCAN_FILE = import_node_path37.default.join(".clawd", "deploy-scan.json");
55783
57049
  async function readJson(file) {
55784
57050
  let raw;
55785
57051
  try {
@@ -55795,14 +57061,14 @@ async function readJson(file) {
55795
57061
  }
55796
57062
  }
55797
57063
  async function writeJson(file, value) {
55798
- await import_promises4.default.mkdir(import_node_path34.default.dirname(file), { recursive: true });
57064
+ await import_promises4.default.mkdir(import_node_path37.default.dirname(file), { recursive: true });
55799
57065
  const tmp = `${file}.tmp-${process.pid}`;
55800
57066
  await import_promises4.default.writeFile(tmp, `${JSON.stringify(value, null, 2)}
55801
57067
  `);
55802
57068
  await import_promises4.default.rename(tmp, file);
55803
57069
  }
55804
57070
  async function readDeployFile(dir) {
55805
- const raw = await readJson(import_node_path34.default.join(dir, DEPLOY_FILE));
57071
+ const raw = await readJson(import_node_path37.default.join(dir, DEPLOY_FILE));
55806
57072
  if (raw === void 0) return void 0;
55807
57073
  const parsed = DeployFileSchema.safeParse(raw);
55808
57074
  if (!parsed.success) {
@@ -55812,18 +57078,18 @@ async function readDeployFile(dir) {
55812
57078
  return parsed.data;
55813
57079
  }
55814
57080
  async function writeDeployFile(dir, file) {
55815
- await writeJson(import_node_path34.default.join(dir, DEPLOY_FILE), DeployFileSchema.parse(file));
57081
+ await writeJson(import_node_path37.default.join(dir, DEPLOY_FILE), DeployFileSchema.parse(file));
55816
57082
  }
55817
57083
  async function readDeployFileRaw(dir) {
55818
57084
  try {
55819
- return await import_promises4.default.readFile(import_node_path34.default.join(dir, DEPLOY_FILE), "utf8");
57085
+ return await import_promises4.default.readFile(import_node_path37.default.join(dir, DEPLOY_FILE), "utf8");
55820
57086
  } catch (err) {
55821
57087
  if (err.code === "ENOENT") return void 0;
55822
57088
  throw err;
55823
57089
  }
55824
57090
  }
55825
57091
  async function restoreDeployFileRaw(dir, raw) {
55826
- const file = import_node_path34.default.join(dir, DEPLOY_FILE);
57092
+ const file = import_node_path37.default.join(dir, DEPLOY_FILE);
55827
57093
  if (raw === void 0) await import_promises4.default.rm(file, { force: true });
55828
57094
  else await import_promises4.default.writeFile(file, raw);
55829
57095
  }
@@ -55864,7 +57130,7 @@ function normalizeLegacyScan(raw) {
55864
57130
  return fix(raw);
55865
57131
  }
55866
57132
  async function readScanFile(dir) {
55867
- const raw = normalizeLegacyScan(await readJson(import_node_path34.default.join(dir, SCAN_FILE)));
57133
+ const raw = normalizeLegacyScan(await readJson(import_node_path37.default.join(dir, SCAN_FILE)));
55868
57134
  if (raw === void 0) return void 0;
55869
57135
  const badShape = (issues) => {
55870
57136
  const first = issues[0];
@@ -55885,7 +57151,7 @@ async function readScanFile(dir) {
55885
57151
  return parsed.data;
55886
57152
  }
55887
57153
  async function writeScanFile(dir, file) {
55888
- await writeJson(import_node_path34.default.join(dir, SCAN_FILE), DeployScanFileSchema.parse(file));
57154
+ await writeJson(import_node_path37.default.join(dir, SCAN_FILE), DeployScanFileSchema.parse(file));
55889
57155
  }
55890
57156
  function replaceScan(file, entry) {
55891
57157
  const idx = file.scans.findIndex((s2) => s2.id === entry.id);
@@ -55968,9 +57234,9 @@ function requiredEnvNames(manifest) {
55968
57234
 
55969
57235
  // src/deploy/artifact.ts
55970
57236
  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);
57237
+ var import_node_fs34 = __toESM(require("fs"), 1);
57238
+ var import_node_os11 = __toESM(require("os"), 1);
57239
+ var import_node_path38 = __toESM(require("path"), 1);
55974
57240
  var import_node_stream3 = require("stream");
55975
57241
  var import_promises5 = require("stream/promises");
55976
57242
  var ARTIFACT_MAX_BYTES = 200 * 1024 * 1024;
@@ -56004,8 +57270,8 @@ function downloadArtifact(exec) {
56004
57270
  if (!res.ok || !res.body) throw new Error(`\u4E0B\u8F7D\u5931\u8D25 HTTP ${res.status}\uFF1A${url}`);
56005
57271
  const declared = Number(res.headers.get("content-length") ?? 0);
56006
57272
  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");
57273
+ const dir = await import_node_fs34.default.promises.mkdtemp(import_node_path38.default.join(import_node_os11.default.tmpdir(), "clawd-artifact-"));
57274
+ const file = import_node_path38.default.join(dir, "pkg");
56009
57275
  const hash = (0, import_node_crypto10.createHash)("sha256");
56010
57276
  let size = 0;
56011
57277
  const meter = new import_node_stream3.Transform({
@@ -56017,7 +57283,7 @@ function downloadArtifact(exec) {
56017
57283
  }
56018
57284
  });
56019
57285
  try {
56020
- await (0, import_promises5.pipeline)(import_node_stream3.Readable.fromWeb(res.body), meter, import_node_fs31.default.createWriteStream(file));
57286
+ await (0, import_promises5.pipeline)(import_node_stream3.Readable.fromWeb(res.body), meter, import_node_fs34.default.createWriteStream(file));
56021
57287
  let entries;
56022
57288
  if (listEntries) {
56023
57289
  const r2 = await exec("tar", ["-tzf", file], { timeoutMs: 3e4 });
@@ -56026,7 +57292,7 @@ function downloadArtifact(exec) {
56026
57292
  }
56027
57293
  return { sha256: hash.digest("hex"), size, ...entries ? { entries } : {} };
56028
57294
  } finally {
56029
- await import_node_fs31.default.promises.rm(dir, { recursive: true, force: true });
57295
+ await import_node_fs34.default.promises.rm(dir, { recursive: true, force: true });
56030
57296
  }
56031
57297
  };
56032
57298
  }
@@ -56077,7 +57343,7 @@ init_protocol();
56077
57343
 
56078
57344
  // src/deploy/repo.ts
56079
57345
  var import_promises6 = __toESM(require("fs/promises"), 1);
56080
- var import_node_path36 = __toESM(require("path"), 1);
57346
+ var import_node_path39 = __toESM(require("path"), 1);
56081
57347
  init_protocol();
56082
57348
 
56083
57349
  // ../node_modules/.pnpm/libsodium@0.8.4/node_modules/libsodium/dist/modules-esm/libsodium.mjs
@@ -60019,7 +61285,7 @@ var runCommand = (cmd, args, opts = {}) => new Promise((resolve9) => {
60019
61285
  });
60020
61286
 
60021
61287
  // src/deploy/repo.ts
60022
- var DEPLOY_FILE_REL = DEPLOY_FILE.split(import_node_path36.default.sep).join("/");
61288
+ var DEPLOY_FILE_REL = DEPLOY_FILE.split(import_node_path39.default.sep).join("/");
60023
61289
  var PERSONA_FILE_REL = ".clawd/persona.json";
60024
61290
  var DIRTY_IGNORED = [DEPLOY_FILE_REL, PERSONA_FILE_REL];
60025
61291
  var RepoError = class extends Error {
@@ -60034,6 +61300,7 @@ async function mustGit(exec, dir, args, what, opts) {
60034
61300
  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
61301
  return r2.stdout.trim();
60036
61302
  }
61303
+ var cloudPersonaIdOf = (originUrl) => personaIdOf(repoSlug(originUrl).split("/").pop() ?? "");
60037
61304
  var INLINE_HELPER = '!f() { echo username=x-access-token; echo "password=$CLAWD_GITHUB_TOKEN"; }; f';
60038
61305
  async function gitPush(exec, dir, auth) {
60039
61306
  if (auth.source === "file") {
@@ -60058,7 +61325,7 @@ async function samePath(a2, b3) {
60058
61325
  try {
60059
61326
  return await import_promises6.default.realpath(x2);
60060
61327
  } catch {
60061
- return import_node_path36.default.resolve(x2);
61328
+ return import_node_path39.default.resolve(x2);
60062
61329
  }
60063
61330
  };
60064
61331
  return await real(a2) === await real(b3);
@@ -60084,7 +61351,7 @@ async function identityArgs(exec, dir) {
60084
61351
  return ["-c", "user.name=clawd", "-c", "user.email=clawd@clawos.local"];
60085
61352
  }
60086
61353
  async function writeGitignore(dir) {
60087
- const gi = import_node_path36.default.join(dir, ".gitignore");
61354
+ const gi = import_node_path39.default.join(dir, ".gitignore");
60088
61355
  let existing;
60089
61356
  try {
60090
61357
  existing = await import_promises6.default.readFile(gi, "utf8");
@@ -60095,14 +61362,14 @@ async function writeGitignore(dir) {
60095
61362
  }
60096
61363
  async function writeWorkflow(dir) {
60097
61364
  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 });
61365
+ const wf = import_node_path39.default.join(dir, rel);
61366
+ await import_promises6.default.mkdir(import_node_path39.default.dirname(wf), { recursive: true });
60100
61367
  await import_promises6.default.writeFile(wf, body);
60101
61368
  }
60102
61369
  }
60103
61370
  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);
61371
+ await import_promises6.default.writeFile(import_node_path39.default.join(dir, DOCKERFILE_PATH), renderDockerfile(personaId2));
61372
+ await import_promises6.default.writeFile(import_node_path39.default.join(dir, DOCKERIGNORE_PATH), DOCKERIGNORE_TEMPLATE);
60106
61373
  }
60107
61374
  var RepoAlreadyExistsError = class extends RepoError {
60108
61375
  constructor(slug) {
@@ -60139,7 +61406,7 @@ async function ensureRepo(exec, api, args) {
60139
61406
  const existing = await repoInfo(exec, dir);
60140
61407
  if (existing) return { created: false, url: existing.url };
60141
61408
  await writeGitignore(dir);
60142
- await import_promises6.default.rm(import_node_path36.default.join(dir, WORKFLOW_PATH), { force: true });
61409
+ await import_promises6.default.rm(import_node_path39.default.join(dir, WORKFLOW_PATH), { force: true });
60143
61410
  let created;
60144
61411
  try {
60145
61412
  created = await createOrgRepo(exec, api, { dir, org, repoName: personaId2, message: "clawd: \u4E0A\u4E91", auth: args.auth });
@@ -60196,7 +61463,7 @@ async function release(exec, api, args) {
60196
61463
  await api.enableActions(repoSlug(info.url));
60197
61464
  await writeGitignore(args.dir);
60198
61465
  await writeWorkflow(args.dir);
60199
- await writeDockerfile(args.dir, personaIdOf(repoSlug(info.url).split("/").pop() ?? ""));
61466
+ await writeDockerfile(args.dir, cloudPersonaIdOf(info.url));
60200
61467
  const identity = await identityArgs(exec, args.dir);
60201
61468
  await mustGit(exec, args.dir, ["add", "-A"], "\u6682\u5B58\u6587\u4EF6");
60202
61469
  const stamp = new Date(args.now()).toISOString();
@@ -60679,6 +61946,14 @@ function buildDeployHandlers(deps) {
60679
61946
  repo2 = await repoInfo(deps.exec, dir);
60680
61947
  if (!repo2) throw new ClawdError(ERROR_CODES.INTERNAL, "\u4ED3\u5E93\u5EFA\u597D\u4E86\u4F46\u8BFB\u4E0D\u5230 origin");
60681
61948
  }
61949
+ const cloudName = cloudPersonaIdOf(repo2.url);
61950
+ let claim;
61951
+ try {
61952
+ claim = await deps.claimPublisher(cloudName);
61953
+ } catch (err) {
61954
+ throw toClawdError(err);
61955
+ }
61956
+ 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
61957
  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
61958
  const live = liveScans.get(personaId2);
60684
61959
  if (live && live.sessionId === void 0) return view(personaId2);
@@ -60856,10 +62131,10 @@ async function probeLocalVersion(exec, pkg) {
60856
62131
 
60857
62132
  // src/tools/shell-path.ts
60858
62133
  var import_node_child_process10 = require("child_process");
60859
- var import_node_os11 = __toESM(require("os"), 1);
62134
+ var import_node_os12 = __toESM(require("os"), 1);
60860
62135
  function refreshPathFromLoginShell() {
60861
62136
  if (process.platform === "win32") return;
60862
- const shell = process.env.SHELL ?? import_node_os11.default.userInfo().shell ?? "/bin/zsh";
62137
+ const shell = process.env.SHELL ?? import_node_os12.default.userInfo().shell ?? "/bin/zsh";
60863
62138
  try {
60864
62139
  const out = (0, import_node_child_process10.execFileSync)(shell, ["-ilc", 'command echo -n "$PATH"'], {
60865
62140
  encoding: "utf8",
@@ -61405,15 +62680,15 @@ async function refreshTunnelUrlOnStartup(deps) {
61405
62680
  }
61406
62681
 
61407
62682
  // 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);
62683
+ var import_node_fs35 = __toESM(require("fs"), 1);
62684
+ var import_node_path40 = __toESM(require("path"), 1);
61410
62685
  function createLarkBotBindingStore(personaRoot) {
61411
- const filePath = (personaId2) => import_node_path37.default.join(personaRoot, safeFileName(personaId2), ".clawd", "lark-bot.json");
62686
+ const filePath = (personaId2) => import_node_path40.default.join(personaRoot, safeFileName(personaId2), ".clawd", "lark-bot.json");
61412
62687
  const read = (personaId2) => {
61413
62688
  const file = filePath(personaId2);
61414
- if (!import_node_fs32.default.existsSync(file)) return null;
62689
+ if (!import_node_fs35.default.existsSync(file)) return null;
61415
62690
  try {
61416
- const raw = JSON.parse(import_node_fs32.default.readFileSync(file, "utf8"));
62691
+ const raw = JSON.parse(import_node_fs35.default.readFileSync(file, "utf8"));
61417
62692
  if (typeof raw?.appId !== "string" || raw.appId.length === 0) return null;
61418
62693
  return raw;
61419
62694
  } catch {
@@ -61422,16 +62697,16 @@ function createLarkBotBindingStore(personaRoot) {
61422
62697
  };
61423
62698
  const write = (personaId2, meta) => {
61424
62699
  const file = filePath(personaId2);
61425
- import_node_fs32.default.mkdirSync(import_node_path37.default.dirname(file), { recursive: true });
62700
+ import_node_fs35.default.mkdirSync(import_node_path40.default.dirname(file), { recursive: true });
61426
62701
  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);
62702
+ import_node_fs35.default.writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
62703
+ import_node_fs35.default.renameSync(tmp, file);
61429
62704
  };
61430
62705
  return {
61431
62706
  read,
61432
62707
  write,
61433
62708
  remove(personaId2) {
61434
- import_node_fs32.default.rmSync(filePath(personaId2), { force: true });
62709
+ import_node_fs35.default.rmSync(filePath(personaId2), { force: true });
61435
62710
  },
61436
62711
  addRemovedChat(personaId2, chatId) {
61437
62712
  const meta = read(personaId2);
@@ -61583,8 +62858,8 @@ function createLarkBotCloudClient(opts) {
61583
62858
  }
61584
62859
 
61585
62860
  // 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);
62861
+ var import_node_fs36 = __toESM(require("fs"), 1);
62862
+ var import_node_path41 = __toESM(require("path"), 1);
61588
62863
  init_protocol();
61589
62864
  var ERROR_REPLY_TEXT = "\u5904\u7406\u51FA\u9519\u4E86\uFF0C\u8BF7\u91CD\u8BD5";
61590
62865
  var LOCAL_TURN_RETRIES = 2;
@@ -61662,9 +62937,9 @@ function createLarkChatRouter(deps) {
61662
62937
  continue;
61663
62938
  }
61664
62939
  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);
62940
+ import_node_fs36.default.mkdirSync(dir, { recursive: true });
62941
+ const p3 = import_node_path41.default.join(dir, `${safeMediaName(ownerMessageId)}-${i2}.${ext}`);
62942
+ import_node_fs36.default.writeFileSync(p3, r2.data);
61668
62943
  lines.push(`[\u56FE\u7247: ${p3}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
61669
62944
  } catch (err) {
61670
62945
  deps.logger?.warn(`larkBot image fetch failed ${key}: ${err.message}`);
@@ -61685,9 +62960,9 @@ function createLarkChatRouter(deps) {
61685
62960
  continue;
61686
62961
  }
61687
62962
  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);
62963
+ import_node_fs36.default.mkdirSync(dir, { recursive: true });
62964
+ const p3 = import_node_path41.default.join(dir, `${safeMediaName(ownerMessageId)}-${i2}-${safeAttachmentName(f2.name)}`);
62965
+ import_node_fs36.default.writeFileSync(p3, r2.data);
61691
62966
  lines.push(`[\u6587\u4EF6: ${p3}]\uFF08\u7528 Read \u5DE5\u5177\u67E5\u770B\uFF09`);
61692
62967
  } catch (err) {
61693
62968
  deps.logger?.warn(`larkBot file fetch failed ${f2.key}: ${err.message}`);
@@ -62335,954 +63610,184 @@ function buildPersonaDispatchHandlers(deps) {
62335
63610
  "personaDispatch:complete": complete,
62336
63611
  "personaDispatch:list": list,
62337
63612
  "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
- }
63613
+ };
62857
63614
  }
62858
- function readFirstLine(file, max = 512 * 1024) {
62859
- let fd;
63615
+
63616
+ // src/dispatch/peer-forward.ts
63617
+ init_protocol();
63618
+ function wsUrlToHttp(url) {
63619
+ if (url.startsWith("wss://")) return "https://" + url.slice("wss://".length);
63620
+ if (url.startsWith("ws://")) return "http://" + url.slice("ws://".length);
63621
+ return url;
63622
+ }
63623
+ var DISPATCH_POLL_INTERVAL_MS = 5e3;
63624
+ var DISPATCH_POLL_UNREACHABLE_MS = 6e5;
63625
+ var DISPATCH_POLL_TOTAL_MS = 864e5;
63626
+ var defaultSleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
63627
+ async function forwardDispatchToPeer(args) {
63628
+ const f2 = args.fetchImpl ?? fetch;
63629
+ const sleep2 = args.sleepImpl ?? defaultSleep;
63630
+ const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
63631
+ const url = `${base}/rpc/personaDispatch:run`;
63632
+ let res;
62860
63633
  try {
62861
- fd = import_node_fs35.default.openSync(file, "r");
62862
- } catch {
62863
- return null;
63634
+ res = await f2(url, {
63635
+ method: "POST",
63636
+ headers: {
63637
+ "content-type": "application/json",
63638
+ authorization: `Bearer ${args.contact.connectToken}`
63639
+ },
63640
+ // 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
63641
+ body: JSON.stringify({
63642
+ targetPersona: args.targetPersona,
63643
+ prompt: args.prompt,
63644
+ // T-20: 条件展开防 undefined 污染 JSON。远端 daemon handler 会再校验一次 model 合法性。
63645
+ ...args.model ? { model: args.model } : {}
63646
+ })
63647
+ });
63648
+ } catch (err) {
63649
+ const msg = err instanceof Error ? err.message : String(err);
63650
+ return { kind: "failure", reason: `forward to peer failed: ${msg}` };
62864
63651
  }
63652
+ let json;
62865
63653
  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;
63654
+ json = await res.json();
62881
63655
  } catch {
62882
- return null;
62883
- } finally {
62884
- import_node_fs35.default.closeSync(fd);
63656
+ return {
63657
+ kind: "failure",
63658
+ reason: `peer returned non-JSON response (HTTP ${res.status})`
63659
+ };
62885
63660
  }
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;
63661
+ if (json.ok === false) {
63662
+ return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
62895
63663
  }
62896
- for (const line of raw.split("\n")) {
62897
- if (!line.trim()) continue;
63664
+ if ("outcome" in json.result) return json.result.outcome;
63665
+ const dispatchId = json.result.dispatchId;
63666
+ let elapsed = 0;
63667
+ let unreachableFor = 0;
63668
+ while (elapsed < DISPATCH_POLL_TOTAL_MS) {
63669
+ await sleep2(DISPATCH_POLL_INTERVAL_MS);
63670
+ elapsed += DISPATCH_POLL_INTERVAL_MS;
63671
+ const markUnreachable = () => {
63672
+ unreachableFor += DISPATCH_POLL_INTERVAL_MS;
63673
+ return unreachableFor >= DISPATCH_POLL_UNREACHABLE_MS ? {
63674
+ kind: "failure",
63675
+ reason: "peer unreachable while polling dispatch outcome (TARGET_UNREACHABLE)"
63676
+ } : null;
63677
+ };
63678
+ let getRes;
62898
63679
  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
- }
63680
+ getRes = await f2(`${base}/rpc/personaDispatch:get`, {
63681
+ method: "POST",
63682
+ headers: {
63683
+ "content-type": "application/json",
63684
+ authorization: `Bearer ${args.contact.connectToken}`
63685
+ },
63686
+ body: JSON.stringify({ dispatchId })
63687
+ });
62903
63688
  } catch {
63689
+ const dead = markUnreachable();
63690
+ if (dead) return dead;
63691
+ continue;
62904
63692
  }
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;
63693
+ let getJson;
62934
63694
  try {
62935
- text = await loadChild(sub.agentId);
62936
- } catch (error) {
62937
- logger?.warn("codex subagent rollout unreadable", { childThreadId: sub.agentId, message: error.message });
63695
+ getJson = await getRes.json();
63696
+ } catch {
63697
+ const dead = markUnreachable();
63698
+ if (dead) return dead;
62938
63699
  continue;
62939
63700
  }
62940
- if (text === null) {
62941
- logger?.warn("codex subagent rollout not found", { parentThreadId, childThreadId: sub.agentId });
63701
+ if (getJson.ok === false) {
63702
+ if (getJson.error === ERROR_CODES.DISPATCH_NOT_FOUND) {
63703
+ return { kind: "failure", reason: `peer lost dispatch ${dispatchId} (daemon restarted?)` };
63704
+ }
63705
+ const dead = markUnreachable();
63706
+ if (dead) return dead;
62942
63707
  continue;
62943
63708
  }
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;
63709
+ unreachableFor = 0;
63710
+ const { status, outcome } = getJson.result;
63711
+ if (status === "completed" || status === "failed") {
63712
+ return outcome ?? { kind: "failure", reason: "peer returned terminal status without outcome" };
62952
63713
  }
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
63714
  }
62972
- return out;
63715
+ return { kind: "failure", reason: "dispatch polling exceeded 24h (TIMEOUT)" };
62973
63716
  }
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);
63717
+ async function forwardInboxPostToPeer(args) {
63718
+ const f2 = args.fetchImpl ?? fetch;
63719
+ const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
63720
+ const url = `${base}/rpc/inbox:postMessage`;
63083
63721
  let res;
63084
63722
  try {
63085
63723
  res = await f2(url, {
63086
- method: init.method,
63724
+ method: "POST",
63087
63725
  headers: {
63088
63726
  "content-type": "application/json",
63089
- ...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
63727
+ authorization: `Bearer ${args.contact.connectToken}`
63090
63728
  },
63091
- ...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
63092
- signal: ctrl.signal
63729
+ body: JSON.stringify({
63730
+ id: args.id,
63731
+ text: args.text,
63732
+ createdAt: args.createdAt,
63733
+ ...args.origin ? { origin: args.origin } : {}
63734
+ })
63093
63735
  });
63094
63736
  } 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}`);
63737
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
63110
63738
  }
63739
+ let json = null;
63111
63740
  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");
63741
+ json = await res.json();
63742
+ } catch {
63743
+ return { ok: false, error: `peer non-JSON (HTTP ${res.status})` };
63135
63744
  }
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");
63745
+ if (res.status < 200 || res.status >= 300 || json?.ok === false) {
63746
+ return { ok: false, error: json?.error ?? `HTTP ${res.status}` };
63149
63747
  }
63150
63748
  return { ok: true };
63151
63749
  }
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
63750
+ async function forwardPeerExecToPeer(args) {
63751
+ const f2 = args.fetchImpl ?? fetch;
63752
+ const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
63753
+ const url = `${base}/rpc/peerExec:run`;
63754
+ const tunnelFail = (msg) => ({
63755
+ type: "peerExec:run:ok",
63756
+ stdout: "",
63757
+ stderr: msg,
63758
+ exitCode: null,
63759
+ timedOut: false,
63760
+ stdoutTruncated: false,
63761
+ stderrTruncated: false
63166
63762
  });
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
63763
  let res;
63183
63764
  try {
63184
- res = await f2(url, { method: "GET", headers: { authorization: `Bearer ${opts.loginPass}` }, signal: ctrl.signal });
63765
+ res = await f2(url, {
63766
+ method: "POST",
63767
+ headers: {
63768
+ "content-type": "application/json",
63769
+ authorization: `Bearer ${args.contact.connectToken}`
63770
+ },
63771
+ body: JSON.stringify({ command: args.command, ...args.timeoutMs ? { timeoutMs: args.timeoutMs } : {} })
63772
+ });
63185
63773
  } 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");
63774
+ return tunnelFail(`forward to peer failed: ${err instanceof Error ? err.message : String(err)}`);
63227
63775
  }
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");
63776
+ let json;
63777
+ try {
63778
+ json = await res.json();
63779
+ } catch {
63780
+ return tunnelFail(`peer returned non-JSON response (HTTP ${res.status})`);
63237
63781
  }
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 });
63782
+ if (json.ok === false) {
63783
+ throw new ClawdError(json.error || ERROR_CODES.INTERNAL, json.message || json.error);
63245
63784
  }
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 : "" };
63785
+ return json.result;
63283
63786
  }
63284
63787
 
63285
63788
  // src/cloud/gateway-client.ts
63789
+ init_protocol();
63790
+ init_protocol();
63286
63791
  var CLOUD_WORKSPACE_SLOT = "{{WORKSPACE}}";
63287
63792
  var CLOUD_TURN_HEADER = "x-clawd-turn";
63288
63793
  var CLOUD_TURN_HEADER_START = "start";
@@ -63293,7 +63798,7 @@ var GATEWAY_ROUTES = {
63293
63798
  sessionMirror: (personaId2, sessionId) => `/p/${encodeURIComponent(personaId2)}/session/${encodeURIComponent(sessionId)}/mirror`,
63294
63799
  // 调用方自己那块盘的用量(design 2026-09-19-cloud-volume-usage §3.3):同样按人、不挂 persona
63295
63800
  volume: () => "/me/volume",
63296
- // 云 persona 运行看板的数据(design 2026-09-23-cloud-persona-metrics §3.6):任何有效 key 都能看(C1)
63801
+ // 云 persona 运行看板的数据(design 2026-09-23-cloud-persona-metrics §3.6):只给发布者与管理员(gateway 问中心,design 2026-09-24-cloud-persona-conversations §3.2)
63297
63802
  metrics: (personaId2) => `/metrics/${encodeURIComponent(personaId2)}`
63298
63803
  };
63299
63804
  var METRICS_TIMEOUT_MS = 3e4;
@@ -63524,7 +64029,7 @@ function openHttpLineStream(opts) {
63524
64029
  if (closed || !sessionId) return;
63525
64030
  const url = gatewayUrl + GATEWAY_ROUTES.sessionMirror(personaId2, sessionId);
63526
64031
  try {
63527
- const res = await fetchImpl(url, { method: "POST", headers, body: turn ? JSON.stringify({ turn: { outcome: turn.outcome } }) : "" });
64032
+ const res = await fetchImpl(url, { method: "POST", headers, body: turn ? JSON.stringify({ turn: { outcome: turn.outcome, ...turn.afterFirstByteMs !== void 0 ? { afterFirstByteMs: turn.afterFirstByteMs } : {} } }) : "" });
63528
64033
  if (!res.ok) logger?.warn("cloud session mirror failed", { status: res.status });
63529
64034
  } catch (e) {
63530
64035
  if (!closed) logger?.warn("cloud session mirror error", { message: e.message });
@@ -63653,68 +64158,7 @@ function openHttpLineStream(opts) {
63653
64158
  init_protocol();
63654
64159
 
63655
64160
  // 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
64161
+ var import_node_fs37 = __toESM(require("fs"), 1);
63718
64162
  function cloudThreadParams(ctx, paths) {
63719
64163
  const { model: _model, ...base } = threadStartParams(ctx);
63720
64164
  const developerInstructions = [ctx.extraSystemPrompt, workDirPrompt(paths.workDir, ctx.personaMode ?? "owner")].filter((x2) => Boolean(x2)).join("\n\n");
@@ -63771,7 +64215,7 @@ function cloudImageInput(deps) {
63771
64215
  const mime = lookupMime(p3);
63772
64216
  if (!mime.startsWith("image/")) return null;
63773
64217
  try {
63774
- const bytes = (deps.readFile ?? import_node_fs36.default.readFileSync)(p3);
64218
+ const bytes = (deps.readFile ?? import_node_fs37.default.readFileSync)(p3);
63775
64219
  return { type: "image", url: `data:${mime};base64,${bytes.toString("base64")}` };
63776
64220
  } catch (e) {
63777
64221
  deps.logger?.warn("cloud image attachment unreadable", { path: p3, message: e.message });
@@ -63804,7 +64248,9 @@ function createCloudCodexSession(ctx, sink, deps) {
63804
64248
  kill: () => stream.close(),
63805
64249
  // 云 persona 运行看板(design 2026-09-23-cloud-persona-metrics §3.7):提问那一行带「一轮开始」头,轮末镜像顺带报这一轮的结果
63806
64250
  onTurnStarting: () => channel?.markNextLineTurnStart(),
63807
- onTurnCompleted: (outcome) => void channel?.mirror({ outcome })
64251
+ // 轮末镜像带上这一轮的结果与首字节(design 2026-09-23-cloud-persona-metrics §3.7)
64252
+ onTurnCompleted: (outcome, afterFirstByteMs) => void channel?.mirror({ outcome, ...afterFirstByteMs !== void 0 ? { afterFirstByteMs } : {} }),
64253
+ now: deps.now
63808
64254
  });
63809
64255
  if (!personaId2) {
63810
64256
  queueMicrotask(() => {
@@ -64004,37 +64450,11 @@ var PiRpcClient = class {
64004
64450
  // src/tools/pi-session.ts
64005
64451
  var import_node_child_process11 = require("child_process");
64006
64452
  var import_node_crypto12 = require("crypto");
64007
- var import_node_fs37 = __toESM(require("fs"), 1);
64008
- var import_node_path41 = __toESM(require("path"), 1);
64453
+ var import_node_fs38 = __toESM(require("fs"), 1);
64454
+ var import_node_path42 = __toESM(require("path"), 1);
64009
64455
 
64010
64456
  // src/tools/pi-events.ts
64011
64457
  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
64458
  function createPiTranslatorState() {
64039
64459
  return { assistantSeq: 0, aborted: false };
64040
64460
  }
@@ -64045,23 +64465,23 @@ function translatePiEvent(ev, st2) {
64045
64465
  return [];
64046
64466
  }
64047
64467
  case "message_start": {
64048
- if (rec3(ev.message)?.role === "assistant") st2.assistantSeq += 1;
64468
+ if (rec2(ev.message)?.role === "assistant") st2.assistantSeq += 1;
64049
64469
  return [];
64050
64470
  }
64051
64471
  case "message_update":
64052
- return messageUpdate(rec3(ev.assistantMessageEvent), st2);
64472
+ return messageUpdate(rec2(ev.assistantMessageEvent), st2);
64053
64473
  case "message_end":
64054
- return messageEnd(rec3(ev.message), st2);
64474
+ return messageEnd(rec2(ev.message), st2);
64055
64475
  case "tool_execution_start": {
64056
- const toolUseId = str3(ev.toolCallId);
64057
- const tool = str3(ev.toolName);
64476
+ const toolUseId = str2(ev.toolCallId);
64477
+ const tool = str2(ev.toolName);
64058
64478
  if (!toolUseId || !tool) return [];
64059
64479
  return [{ kind: "tool_call", toolUseId, tool, toolKind: PI_TOOL_ID, input: ev.args ?? {} }];
64060
64480
  }
64061
64481
  case "tool_execution_end": {
64062
- const toolUseId = str3(ev.toolCallId);
64482
+ const toolUseId = str2(ev.toolCallId);
64063
64483
  if (!toolUseId) return [];
64064
- const text = contentText(rec3(ev.result)?.content);
64484
+ const text = contentText(rec2(ev.result)?.content);
64065
64485
  const out = { kind: "tool_result", toolUseId, output: text };
64066
64486
  if (ev.isError === true) out.error = text || "tool failed";
64067
64487
  return [out];
@@ -64077,9 +64497,9 @@ function translatePiEvent(ev, st2) {
64077
64497
  }
64078
64498
  function messageUpdate(e, st2) {
64079
64499
  if (!e) return [];
64080
- const delta = str3(e.delta);
64500
+ const delta = str2(e.delta);
64081
64501
  if (!delta) return [];
64082
- const partialId = `${st2.assistantSeq}-${num3(e.contentIndex) ?? 0}`;
64502
+ const partialId = `${st2.assistantSeq}-${num2(e.contentIndex) ?? 0}`;
64083
64503
  if (e.type === "text_delta") return [{ kind: "text", text: delta, partialId }];
64084
64504
  if (e.type === "thinking_delta") return [{ kind: "thinking", text: delta, partialId }];
64085
64505
  return [];
@@ -64088,32 +64508,32 @@ function messageEnd(m3, st2) {
64088
64508
  if (!m3 || m3.role !== "assistant") return [];
64089
64509
  const out = [];
64090
64510
  for (const b3 of arr(m3.content)) {
64091
- const block = rec3(b3);
64511
+ const block = rec2(b3);
64092
64512
  if (!block) continue;
64093
64513
  if (block.type === "thinking") {
64094
- const t2 = str3(block.thinking);
64514
+ const t2 = str2(block.thinking);
64095
64515
  if (t2) out.push({ kind: "thinking", text: t2 });
64096
64516
  } else if (block.type === "text") {
64097
- const t2 = str3(block.text);
64517
+ const t2 = str2(block.text);
64098
64518
  if (t2) out.push({ kind: "text", text: t2 });
64099
64519
  }
64100
64520
  }
64101
64521
  const patch = {};
64102
- const model = str3(m3.model);
64103
- const provider = str3(m3.provider);
64522
+ const model = str2(m3.model);
64523
+ const provider = str2(m3.provider);
64104
64524
  if (model) patch.resolvedModel = provider ? `${provider}/${model}` : model;
64105
- const u2 = rec3(m3.usage);
64106
- if (u2 && (num3(u2.totalTokens) ?? 0) > 0) {
64525
+ const u2 = rec2(m3.usage);
64526
+ if (u2 && (num2(u2.totalTokens) ?? 0) > 0) {
64107
64527
  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) } : {}
64528
+ inputTokens: num2(u2.input) ?? 0,
64529
+ outputTokens: num2(u2.output) ?? 0,
64530
+ ...num2(u2.cacheRead) !== void 0 ? { cacheReadTokens: num2(u2.cacheRead) } : {},
64531
+ ...num2(u2.cacheWrite) !== void 0 ? { cacheCreateTokens: num2(u2.cacheWrite) } : {}
64112
64532
  };
64113
64533
  }
64114
64534
  if (patch.resolvedModel || patch.contextUsage) out.push({ kind: "meta_update", patch });
64115
- const stop = str3(m3.stopReason);
64116
- const err = str3(m3.errorMessage);
64535
+ const stop = str2(m3.stopReason);
64536
+ const err = str2(m3.errorMessage);
64117
64537
  if (stop === "aborted") {
64118
64538
  st2.aborted = true;
64119
64539
  } else if (stop === "error" || err) {
@@ -64167,6 +64587,7 @@ function drivePiSession(opts) {
64167
64587
  let turnErrored = false;
64168
64588
  let turnInterrupted = false;
64169
64589
  let turnOpen = false;
64590
+ const firstByte = createFirstByteClock(opts.now);
64170
64591
  let handshakeFailed = false;
64171
64592
  let exited = false;
64172
64593
  const finish = (code) => {
@@ -64178,11 +64599,11 @@ function drivePiSession(opts) {
64178
64599
  sink.pushEvents([{ kind: "error", message }, { kind: "turn_end", reason: "completed" }]);
64179
64600
  if (!opened) return;
64180
64601
  turnOpen = false;
64181
- opts.onTurnCompleted?.("failure");
64602
+ opts.onTurnCompleted?.("failure", firstByte.afterFirstByteMs());
64182
64603
  };
64183
64604
  const handleUiRequest = (ev) => {
64184
- const method = str3(ev.method);
64185
- const id = str3(ev.id);
64605
+ const method = str2(ev.method);
64606
+ const id = str2(ev.id);
64186
64607
  logger?.warn("pi extension_ui_request unexpected in clawd session", { method });
64187
64608
  if (id && (method === "select" || method === "confirm" || method === "input" || method === "editor")) {
64188
64609
  client.send({ type: "extension_ui_response", id, cancelled: true });
@@ -64197,7 +64618,7 @@ function drivePiSession(opts) {
64197
64618
  }
64198
64619
  if (ev.type === "agent_settled") busy = false;
64199
64620
  if (ev.type === "response") {
64200
- logger?.warn("pi response without pending id", { command: str3(ev.command), error: str3(ev.error) });
64621
+ logger?.warn("pi response without pending id", { command: str2(ev.command), error: str2(ev.error) });
64201
64622
  return;
64202
64623
  }
64203
64624
  if (ev.type === "extension_ui_request") {
@@ -64205,29 +64626,32 @@ function drivePiSession(opts) {
64205
64626
  return;
64206
64627
  }
64207
64628
  const events = translatePiEvent(ev, st2);
64208
- if (events.length) sink.pushEvents(events);
64629
+ if (events.length) {
64630
+ if (turnOpen) firstByte.see(events);
64631
+ sink.pushEvents(events);
64632
+ }
64209
64633
  for (const e of events) {
64210
64634
  if (e.kind === "error") turnErrored = true;
64211
64635
  if (e.kind === "turn_end" && e.reason === "interrupted") turnInterrupted = true;
64212
64636
  }
64213
64637
  if (ev.type === "agent_settled") {
64214
64638
  turnOpen = false;
64215
- opts.onTurnCompleted?.(turnInterrupted ? "interrupted" : turnErrored ? "failure" : "success");
64639
+ opts.onTurnCompleted?.(turnInterrupted ? "interrupted" : turnErrored ? "failure" : "success", firstByte.afterFirstByteMs());
64216
64640
  }
64217
64641
  },
64218
64642
  onClose: () => finish(opts.exitCodeOnClose?.() ?? null)
64219
64643
  });
64220
64644
  const ready = client.request({ type: "get_state" }).then((r2) => {
64221
64645
  if (!r2.success) throw new Error(r2.error ?? "get_state failed");
64222
- const data = rec3(r2.data);
64223
- const sid = str3(data?.sessionId) ?? sessionId;
64646
+ const data = rec2(r2.data);
64647
+ const sid = str2(data?.sessionId) ?? sessionId;
64224
64648
  if (sid !== sessionId) logger?.error("pi sessionId mismatch", { expected: sessionId, actual: sid });
64225
- const model = rec3(data?.model);
64649
+ const model = rec2(data?.model);
64226
64650
  const patch = {};
64227
- const mid = str3(model?.id);
64228
- const prov = str3(model?.provider);
64651
+ const mid = str2(model?.id);
64652
+ const prov = str2(model?.provider);
64229
64653
  if (mid && mid !== "unknown") patch.resolvedModel = prov ? `${prov}/${mid}` : mid;
64230
- const cw = num3(model?.contextWindow);
64654
+ const cw = num2(model?.contextWindow);
64231
64655
  if (cw && cw > 0) patch.contextWindowSize = cw;
64232
64656
  const events = [{ kind: "session_init", toolSessionId: sid }];
64233
64657
  if (patch.resolvedModel || patch.contextWindowSize) events.push({ kind: "meta_update", patch });
@@ -64248,6 +64672,7 @@ function drivePiSession(opts) {
64248
64672
  opened = !turnOpen;
64249
64673
  if (opened) {
64250
64674
  turnOpen = true;
64675
+ firstByte.reset();
64251
64676
  opts.onTurnStarting?.();
64252
64677
  }
64253
64678
  const r2 = await client.request({
@@ -64298,8 +64723,8 @@ ${dirs.map((d2) => `- ${d2}`).join("\n")}`;
64298
64723
  function createPiSession(ctx, sink, deps = {}) {
64299
64724
  const cmd = process.env.PI_BIN ?? "pi";
64300
64725
  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));
64726
+ const skillsDir = import_node_path42.default.join(ctx.cwd, ".claude", "skills");
64727
+ const exists = deps.skillsDirExists ?? ((d2) => import_node_fs38.default.existsSync(d2));
64303
64728
  const args = buildPiArgs(ctx, {
64304
64729
  sessionId,
64305
64730
  skillsDir: exists(skillsDir) ? skillsDir : void 0,
@@ -64342,201 +64767,6 @@ function createPiSession(ctx, sink, deps = {}) {
64342
64767
  return driver.session;
64343
64768
  }
64344
64769
 
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
64770
  // src/tools/pi.ts
64541
64771
  var PI_DEFAULT_CONTEXT_WINDOW = 128e3;
64542
64772
  var PI_THINKING_EFFORTS = [
@@ -64565,20 +64795,20 @@ async function probePi(env = process.env) {
64565
64795
  return { available: false };
64566
64796
  }
64567
64797
  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));
64798
+ const models = arr(rec2(raw)?.models).map(rec2).filter((m3) => m3 !== void 0);
64799
+ const providers = new Set(models.map((m3) => str2(m3.provider)).filter(Boolean));
64570
64800
  const out = [];
64571
64801
  for (const m3 of models) {
64572
- const id = str3(m3.id);
64573
- const provider = str3(m3.provider);
64802
+ const id = str2(m3.id);
64803
+ const provider = str2(m3.provider);
64574
64804
  if (!id || !provider) continue;
64575
- const cw = num3(m3.contextWindow);
64805
+ const cw = num2(m3.contextWindow);
64576
64806
  const levels = arr(m3.thinkingLevels).filter((v3) => typeof v3 === "string");
64577
64807
  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
64808
  out.push({
64579
64809
  id: `${provider}/${id}`,
64580
64810
  // 与 --model 同一串
64581
- label: str3(m3.name) || id,
64811
+ label: str2(m3.name) || id,
64582
64812
  contextWindowSize: cw && cw > 0 ? cw : PI_DEFAULT_CONTEXT_WINDOW,
64583
64813
  efforts,
64584
64814
  // 分组按 provider;只有一个 provider 时不分组(一个横跨全表的标题没信息量,同 codex)
@@ -64644,18 +64874,18 @@ async function queryPiModels(deps = {}) {
64644
64874
  try {
64645
64875
  const r2 = await race(client.request({ type: "get_available_models" }));
64646
64876
  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);
64877
+ const models = arr(rec2(r2.data)?.models).map(rec2).filter((m3) => m3 !== void 0);
64648
64878
  const enriched = [];
64649
64879
  for (const m3 of models) {
64650
- const provider = str3(m3.provider);
64651
- const id = str3(m3.id);
64880
+ const provider = str2(m3.provider);
64881
+ const id = str2(m3.id);
64652
64882
  let thinkingLevels;
64653
64883
  if (provider && id && !timedOut) {
64654
64884
  try {
64655
64885
  const set = await race(client.request({ type: "set_model", provider, modelId: id }));
64656
64886
  if (set.success) {
64657
64887
  const lv = await race(client.request({ type: "get_available_thinking_levels" }));
64658
- const levels = arr(rec3(lv.data)?.levels).filter((v3) => typeof v3 === "string");
64888
+ const levels = arr(rec2(lv.data)?.levels).filter((v3) => typeof v3 === "string");
64659
64889
  if (lv.success && levels.length > 0) thinkingLevels = levels;
64660
64890
  }
64661
64891
  } catch {
@@ -64748,7 +64978,9 @@ function createCloudPiSession(ctx, sink, deps) {
64748
64978
  kill: () => stream.close(),
64749
64979
  // 云 persona 运行看板(design 2026-09-23-cloud-persona-metrics §3.7):提问那一行带「一轮开始」头,轮末镜像顺带报这一轮的结果
64750
64980
  onTurnStarting: () => channel?.markNextLineTurnStart(),
64751
- onTurnCompleted: (outcome) => void channel?.mirror({ outcome })
64981
+ // 轮末镜像带上这一轮的结果与首字节(design 2026-09-23-cloud-persona-metrics §3.7)
64982
+ onTurnCompleted: (outcome, afterFirstByteMs) => void channel?.mirror({ outcome, ...afterFirstByteMs !== void 0 ? { afterFirstByteMs } : {} }),
64983
+ now: deps.now
64752
64984
  });
64753
64985
  if (!personaId2) {
64754
64986
  queueMicrotask(() => {
@@ -64777,42 +65009,6 @@ function createCloudPiSession(ctx, sink, deps) {
64777
65009
  return driver.session;
64778
65010
  }
64779
65011
 
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
65012
  // src/tools/pi-cloud.ts
64817
65013
  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
65014
  var CLOUD_PI_CAPABILITIES = {
@@ -72088,7 +72284,7 @@ function computeMethodAccess(args) {
72088
72284
  }
72089
72285
 
72090
72286
  // src/version.ts
72091
- var version = "0.2.582".length > 0 ? "0.2.582" : "dev";
72287
+ var version = "0.2.584".length > 0 ? "0.2.584" : "dev";
72092
72288
 
72093
72289
  // src/cli-probe/probe.ts
72094
72290
  var fs80 = __toESM(require("fs"), 1);
@@ -75769,6 +75965,18 @@ async function startDaemon(config) {
75769
75965
  githubIdentity,
75770
75966
  githubApi: (token) => createGithubApi({ token }),
75771
75967
  org: PERSONA_REPO_ORG,
75968
+ // 发布者认领(design 2026-09-24-cloud-persona-conversations §3.1):身份由中心从登录票里取;409 = 名字是别人的
75969
+ claimPublisher: async (personaId2) => {
75970
+ const loginPass = ownerIdentityStore.read()?.loginPass;
75971
+ if (!loginPass) throw new Error("\u8FD8\u6CA1\u767B\u5F55 clawd\uFF1A\u5148\u5728\u8BBE\u7F6E\u91CC\u767B\u5F55\u98DE\u4E66\uFF0C\u518D\u53D1\u5E03");
75972
+ try {
75973
+ await centralClaimPublisher({ api: config.clawosApi ?? DEFAULT_CLAWOS_API, loginPass, personaId: personaId2 });
75974
+ return "ok";
75975
+ } catch (err) {
75976
+ if (err instanceof CentralClientError && err.status === 409) return "taken";
75977
+ throw new Error(`\u5411\u4E2D\u5FC3\u767B\u8BB0\u53D1\u5E03\u8005\u5931\u8D25\uFF1A${err.message}`);
75978
+ }
75979
+ },
75772
75980
  now: () => Date.now(),
75773
75981
  warn: (msg, meta) => logger.warn(msg, meta),
75774
75982
  // 在不在线上 = gateway 正式登记表里有没有它(design 2026-09-24-deploy-unpublish §3.3):跟 persona 列表的云卡片同一个来源。
@@ -75909,13 +76117,19 @@ async function startDaemon(config) {
75909
76117
  const viewerAssetLoader = tryLoadViewerAssets(logger);
75910
76118
  const shareUiAssetLoader = tryLoadShareUi(logger);
75911
76119
  const dashboardPages = loadDashboardPages(logger);
76120
+ const dashboardApi = () => config.clawosApi ?? DEFAULT_CLAWOS_API;
75912
76121
  const dashboardRoutes = createDashboardRoutes({
75913
76122
  getSecret: () => authFile.token,
75914
76123
  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
- }
76124
+ data: createDashboardData({
76125
+ loginPass: () => ownerIdentityStore.read()?.loginPass ?? null,
76126
+ registeredPersonas: async () => (await cloudGateway.listPersonas()).map((p3) => p3.personaId),
76127
+ visiblePersonas: (loginPass) => centralVisiblePersonas({ api: dashboardApi(), loginPass }),
76128
+ metrics: (q3) => cloudGateway.fetchMetrics(q3),
76129
+ threads: (loginPass, q3) => centralPersonaThreads({ api: dashboardApi(), loginPass, ...q3 }),
76130
+ thread: (loginPass, q3) => centralPersonaThread({ api: dashboardApi(), loginPass, ...q3 }),
76131
+ logger
76132
+ })
75919
76133
  });
75920
76134
  const httpRouter = createHttpRouter({
75921
76135
  authResolver,