tesseract-agent 0.4.16 → 0.4.18

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.
Files changed (2) hide show
  1. package/dist-bundle/cli.js +1646 -432
  2. package/package.json +3 -2
@@ -70,7 +70,7 @@ var require_buffer_util = __commonJS({
70
70
  "use strict";
71
71
  var { EMPTY_BUFFER } = require_constants();
72
72
  var FastBuffer = Buffer[Symbol.species];
73
- function concat(list, totalLength) {
73
+ function concat2(list, totalLength) {
74
74
  if (list.length === 0) return EMPTY_BUFFER;
75
75
  if (list.length === 1) return list[0];
76
76
  const target = Buffer.allocUnsafe(totalLength);
@@ -116,7 +116,7 @@ var require_buffer_util = __commonJS({
116
116
  return buf;
117
117
  }
118
118
  module.exports = {
119
- concat,
119
+ concat: concat2,
120
120
  mask: _mask,
121
121
  toArrayBuffer,
122
122
  toBuffer,
@@ -785,7 +785,7 @@ var require_receiver = __commonJS({
785
785
  kStatusCode,
786
786
  kWebSocket
787
787
  } = require_constants();
788
- var { concat, toArrayBuffer, unmask } = require_buffer_util();
788
+ var { concat: concat2, toArrayBuffer, unmask } = require_buffer_util();
789
789
  var { isValidStatusCode, isValidUTF8 } = require_validation();
790
790
  var FastBuffer = Buffer[Symbol.species];
791
791
  var GET_INFO = 0;
@@ -1271,9 +1271,9 @@ var require_receiver = __commonJS({
1271
1271
  if (this._opcode === 2) {
1272
1272
  let data;
1273
1273
  if (this._binaryType === "nodebuffer") {
1274
- data = concat(fragments, messageLength);
1274
+ data = concat2(fragments, messageLength);
1275
1275
  } else if (this._binaryType === "arraybuffer") {
1276
- data = toArrayBuffer(concat(fragments, messageLength));
1276
+ data = toArrayBuffer(concat2(fragments, messageLength));
1277
1277
  } else if (this._binaryType === "blob") {
1278
1278
  data = new Blob(fragments);
1279
1279
  } else {
@@ -1291,7 +1291,7 @@ var require_receiver = __commonJS({
1291
1291
  });
1292
1292
  }
1293
1293
  } else {
1294
- const buf = concat(fragments, messageLength);
1294
+ const buf = concat2(fragments, messageLength);
1295
1295
  if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
1296
1296
  const error51 = this.createError(
1297
1297
  Error,
@@ -2289,7 +2289,7 @@ var require_websocket = __commonJS({
2289
2289
  var http = __require("http");
2290
2290
  var net = __require("net");
2291
2291
  var tls2 = __require("tls");
2292
- var { randomBytes: randomBytes4, createHash: createHash6 } = __require("crypto");
2292
+ var { randomBytes: randomBytes5, createHash: createHash6 } = __require("crypto");
2293
2293
  var { Duplex, Readable: Readable2 } = __require("stream");
2294
2294
  var { URL: URL2 } = __require("url");
2295
2295
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -2827,7 +2827,7 @@ var require_websocket = __commonJS({
2827
2827
  }
2828
2828
  }
2829
2829
  const defaultPort = isSecure ? 443 : 80;
2830
- const key2 = randomBytes4(16).toString("base64");
2830
+ const key2 = randomBytes5(16).toString("base64");
2831
2831
  const request = isSecure ? https.request : http.request;
2832
2832
  const protocolSet = /* @__PURE__ */ new Set();
2833
2833
  let perMessageDeflate;
@@ -4129,8 +4129,8 @@ function createSchedulerLock(lockDir, ownerId, now = () => Date.now()) {
4129
4129
  }
4130
4130
  };
4131
4131
  const writeLease = () => {
4132
- const nonce = `${process.pid}-${now()}-${++nonceSeq}`;
4133
- const rec = { ownerId, pid: process.pid, refreshedAt: now(), nonce };
4132
+ const nonce2 = `${process.pid}-${now()}-${++nonceSeq}`;
4133
+ const rec = { ownerId, pid: process.pid, refreshedAt: now(), nonce: nonce2 };
4134
4134
  const tmp = `${lockFile}.${process.pid}.tmp`;
4135
4135
  try {
4136
4136
  writeFileSync2(tmp, JSON.stringify(rec));
@@ -4144,7 +4144,7 @@ function createSchedulerLock(lockDir, ownerId, now = () => Date.now()) {
4144
4144
  return false;
4145
4145
  }
4146
4146
  const after = read();
4147
- held = !!after && after.nonce === nonce;
4147
+ held = !!after && after.nonce === nonce2;
4148
4148
  return held;
4149
4149
  };
4150
4150
  return {
@@ -19350,6 +19350,76 @@ var init_cron_tools = __esm({
19350
19350
  }
19351
19351
  });
19352
19352
 
19353
+ // ../../packages/shared/dist/permissions.js
19354
+ var PERMISSIONS, PERMISSION_BY_ID;
19355
+ var init_permissions = __esm({
19356
+ "../../packages/shared/dist/permissions.js"() {
19357
+ "use strict";
19358
+ PERMISSIONS = [
19359
+ {
19360
+ id: "microphone",
19361
+ label: "Microphone",
19362
+ why: "Talk to the agent in voice mode and be heard on group calls.",
19363
+ icon: "Mic",
19364
+ category: "media",
19365
+ applicablePlatforms: ["darwin", "win32"],
19366
+ requestKind: "in-app"
19367
+ },
19368
+ {
19369
+ id: "camera",
19370
+ label: "Camera",
19371
+ why: "Share your video on group calls.",
19372
+ icon: "Camera",
19373
+ category: "media",
19374
+ applicablePlatforms: ["darwin", "win32"],
19375
+ requestKind: "in-app"
19376
+ },
19377
+ {
19378
+ id: "screen-recording",
19379
+ label: "Screen Recording",
19380
+ why: "Capture a screenshot region or share your screen on calls.",
19381
+ icon: "MonitorPlay",
19382
+ category: "media",
19383
+ applicablePlatforms: ["darwin"],
19384
+ requestKind: "settings-only",
19385
+ requiresRelaunchAfterGrant: true,
19386
+ settingsHint: "Enable Tesseract under Screen Recording, then relaunch."
19387
+ },
19388
+ {
19389
+ id: "full-disk-access",
19390
+ label: "Full Disk Access",
19391
+ why: "Show the folders you pinned in Finder\u2019s sidebar.",
19392
+ icon: "FolderLock",
19393
+ category: "system",
19394
+ applicablePlatforms: ["darwin"],
19395
+ requestKind: "settings-only",
19396
+ requiresRelaunchAfterGrant: true,
19397
+ settingsHint: "Enable Tesseract under Full Disk Access, then relaunch."
19398
+ },
19399
+ {
19400
+ id: "notifications",
19401
+ label: "Notifications",
19402
+ why: "Tell you when a background task or agent turn finishes while you\u2019re away.",
19403
+ icon: "Bell",
19404
+ category: "notifications",
19405
+ applicablePlatforms: ["darwin", "win32", "linux"],
19406
+ requestKind: "in-app"
19407
+ },
19408
+ {
19409
+ id: "local-network",
19410
+ label: "Local Network",
19411
+ why: "Connect to your other machines on the same network.",
19412
+ icon: "Wifi",
19413
+ category: "network",
19414
+ applicablePlatforms: ["darwin"],
19415
+ requestKind: "info-only",
19416
+ settingsHint: "Managed by macOS \u2014 allow Tesseract under Local Network in System Settings."
19417
+ }
19418
+ ];
19419
+ PERMISSION_BY_ID = Object.fromEntries(PERMISSIONS.map((p) => [p.id, p]));
19420
+ }
19421
+ });
19422
+
19353
19423
  // ../../packages/shared/dist/ws-protocol.js
19354
19424
  var MIRRORED_AGENT_EVENTS;
19355
19425
  var init_ws_protocol = __esm({
@@ -20477,6 +20547,128 @@ var init_design_tokens = __esm({
20477
20547
  }
20478
20548
  });
20479
20549
 
20550
+ // ../../packages/shared/dist/file-langs.js
20551
+ function langForFilename(name) {
20552
+ const lower = name.toLowerCase();
20553
+ if (SPECIAL_NAMES[lower])
20554
+ return SPECIAL_NAMES[lower];
20555
+ const dot = lower.lastIndexOf(".");
20556
+ if (dot > 0) {
20557
+ const ext = lower.slice(dot + 1);
20558
+ if (EXT_TO_LANG[ext])
20559
+ return EXT_TO_LANG[ext];
20560
+ }
20561
+ return null;
20562
+ }
20563
+ var EXT_TO_LANG, SPECIAL_NAMES, STATS_HEAVY_DIRS, STATS_SKIP_FILES;
20564
+ var init_file_langs = __esm({
20565
+ "../../packages/shared/dist/file-langs.js"() {
20566
+ "use strict";
20567
+ EXT_TO_LANG = {
20568
+ ts: "TypeScript",
20569
+ tsx: "TypeScript",
20570
+ mts: "TypeScript",
20571
+ cts: "TypeScript",
20572
+ js: "JavaScript",
20573
+ jsx: "JavaScript",
20574
+ mjs: "JavaScript",
20575
+ cjs: "JavaScript",
20576
+ py: "Python",
20577
+ pyi: "Python",
20578
+ rb: "Ruby",
20579
+ go: "Go",
20580
+ rs: "Rust",
20581
+ java: "Java",
20582
+ kt: "Kotlin",
20583
+ kts: "Kotlin",
20584
+ c: "C",
20585
+ h: "C",
20586
+ cpp: "C++",
20587
+ cc: "C++",
20588
+ cxx: "C++",
20589
+ hpp: "C++",
20590
+ hh: "C++",
20591
+ cs: "C#",
20592
+ swift: "Swift",
20593
+ m: "Objective-C",
20594
+ mm: "Objective-C++",
20595
+ php: "PHP",
20596
+ dart: "Dart",
20597
+ scala: "Scala",
20598
+ sh: "Shell",
20599
+ bash: "Shell",
20600
+ zsh: "Shell",
20601
+ fish: "Shell",
20602
+ sql: "SQL",
20603
+ html: "HTML",
20604
+ htm: "HTML",
20605
+ css: "CSS",
20606
+ scss: "SCSS",
20607
+ sass: "Sass",
20608
+ less: "Less",
20609
+ vue: "Vue",
20610
+ svelte: "Svelte",
20611
+ json: "JSON",
20612
+ json5: "JSON",
20613
+ jsonc: "JSON",
20614
+ yaml: "YAML",
20615
+ yml: "YAML",
20616
+ toml: "TOML",
20617
+ ini: "INI",
20618
+ xml: "XML",
20619
+ md: "Markdown",
20620
+ markdown: "Markdown",
20621
+ mdx: "MDX",
20622
+ rst: "reStructuredText",
20623
+ tex: "TeX",
20624
+ r: "R",
20625
+ lua: "Lua",
20626
+ pl: "Perl",
20627
+ pm: "Perl",
20628
+ ex: "Elixir",
20629
+ exs: "Elixir",
20630
+ erl: "Erlang",
20631
+ clj: "Clojure",
20632
+ cljs: "Clojure",
20633
+ hs: "Haskell",
20634
+ proto: "Protobuf",
20635
+ gradle: "Gradle",
20636
+ groovy: "Groovy",
20637
+ ps1: "PowerShell",
20638
+ bat: "Batch",
20639
+ cmd: "Batch",
20640
+ vim: "Vim Script",
20641
+ graphql: "GraphQL",
20642
+ gql: "GraphQL"
20643
+ };
20644
+ SPECIAL_NAMES = {
20645
+ dockerfile: "Dockerfile",
20646
+ makefile: "Makefile",
20647
+ cmakelists: "CMake",
20648
+ rakefile: "Ruby",
20649
+ gemfile: "Ruby"
20650
+ };
20651
+ STATS_HEAVY_DIRS = /* @__PURE__ */ new Set([
20652
+ ".git",
20653
+ "node_modules",
20654
+ "dist",
20655
+ "build",
20656
+ ".next",
20657
+ ".turbo",
20658
+ "out",
20659
+ "target",
20660
+ ".cache",
20661
+ "coverage",
20662
+ ".venv",
20663
+ "venv",
20664
+ "__pycache__",
20665
+ ".gradle",
20666
+ "vendor"
20667
+ ]);
20668
+ STATS_SKIP_FILES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db"]);
20669
+ }
20670
+ });
20671
+
20480
20672
  // ../../packages/shared/dist/snapshot.js
20481
20673
  var SNAPSHOT_MAX_TEXT_BYTES, SNAPSHOT_MAX_BINARY_BYTES, SNAPSHOT_MAX_TOTAL_BYTES;
20482
20674
  var init_snapshot = __esm({
@@ -20492,6 +20684,7 @@ var init_snapshot = __esm({
20492
20684
  var init_dist2 = __esm({
20493
20685
  "../../packages/shared/dist/index.js"() {
20494
20686
  "use strict";
20687
+ init_permissions();
20495
20688
  init_ws_protocol();
20496
20689
  init_frame();
20497
20690
  init_plans();
@@ -20505,6 +20698,7 @@ var init_dist2 = __esm({
20505
20698
  init_rotation();
20506
20699
  init_settings();
20507
20700
  init_design_tokens();
20701
+ init_file_langs();
20508
20702
  init_snapshot();
20509
20703
  }
20510
20704
  });
@@ -21269,7 +21463,7 @@ var init_canvas_tools = __esm({
21269
21463
  });
21270
21464
 
21271
21465
  // ../../packages/agent-core/dist/session-transfer.js
21272
- import { homedir } from "node:os";
21466
+ import { homedir as homedir2 } from "node:os";
21273
21467
  import { join as join6, dirname as dirname3, sep, resolve as resolve3 } from "node:path";
21274
21468
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, readdir } from "node:fs/promises";
21275
21469
  import { existsSync } from "node:fs";
@@ -21289,7 +21483,7 @@ function sanitizeProjectDir(cwd) {
21289
21483
  return s.length <= MAX_PROJECT_DIR_LEN ? s : `${s.slice(0, MAX_PROJECT_DIR_LEN)}-${cwdHash(cwd)}`;
21290
21484
  }
21291
21485
  function projectsRoot() {
21292
- return join6(homedir(), ".claude", "projects");
21486
+ return join6(homedir2(), ".claude", "projects");
21293
21487
  }
21294
21488
  function sessionDir(cwd) {
21295
21489
  return join6(projectsRoot(), sanitizeProjectDir(cwd));
@@ -21802,7 +21996,7 @@ ${input.command}
21802
21996
  }
21803
21997
  return blocks;
21804
21998
  }
21805
- var SEARCH_SCAN_LIMIT, SEARCH_RESULT_LIMIT, SEARCH_MAX_FILE_BYTES, SessionStore;
21999
+ var SEARCH_SCAN_LIMIT, SEARCH_RESULT_LIMIT, SEARCH_MAX_FILE_BYTES, FORK_RETRY_ATTEMPTS, FORK_RETRY_DELAY_MS, delay, SessionStore;
21806
22000
  var init_session_store = __esm({
21807
22001
  "../../packages/agent-core/dist/session-store.js"() {
21808
22002
  "use strict";
@@ -21810,6 +22004,9 @@ var init_session_store = __esm({
21810
22004
  SEARCH_SCAN_LIMIT = 200;
21811
22005
  SEARCH_RESULT_LIMIT = 50;
21812
22006
  SEARCH_MAX_FILE_BYTES = 8 * 1024 * 1024;
22007
+ FORK_RETRY_ATTEMPTS = 2;
22008
+ FORK_RETRY_DELAY_MS = 300;
22009
+ delay = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
21813
22010
  SessionStore = class {
21814
22011
  /** List resumable sessions. When `projectDir` is given, only sessions whose
21815
22012
  * cwd matches that project are returned, so each window's Resume list is
@@ -21890,16 +22087,20 @@ var init_session_store = __esm({
21890
22087
  * Returns the new session id, or null on failure. NOTE: forked sessions start
21891
22088
  * WITHOUT file-history/undo snapshots (SDK behavior). */
21892
22089
  async forkSession(sessionId, opts) {
21893
- try {
21894
- const upToMessageId = opts?.upToMessageId ?? await this.lastResumableMessageId(sessionId, opts?.projectDir);
21895
- const result = await forkSession(sessionId, {
21896
- ...opts?.projectDir ? { dir: opts.projectDir } : {},
21897
- ...upToMessageId ? { upToMessageId } : {},
21898
- ...opts?.title ? { title: opts.title } : {}
21899
- });
21900
- return result.sessionId;
21901
- } catch {
21902
- return null;
22090
+ for (let attempt = 0; ; attempt++) {
22091
+ try {
22092
+ const upToMessageId = opts?.upToMessageId ?? await this.lastResumableMessageId(sessionId, opts?.projectDir);
22093
+ const result = await forkSession(sessionId, {
22094
+ ...opts?.projectDir ? { dir: opts.projectDir } : {},
22095
+ ...upToMessageId ? { upToMessageId } : {},
22096
+ ...opts?.title ? { title: opts.title } : {}
22097
+ });
22098
+ return result.sessionId;
22099
+ } catch {
22100
+ if (attempt >= FORK_RETRY_ATTEMPTS)
22101
+ return null;
22102
+ await delay(FORK_RETRY_DELAY_MS);
22103
+ }
21903
22104
  }
21904
22105
  }
21905
22106
  /** The uuid of the last transcript message after which NO tool_use is left without
@@ -21965,7 +22166,7 @@ var init_log = __esm({
21965
22166
  import { readFile as readFile5 } from "node:fs/promises";
21966
22167
  import { execFile } from "node:child_process";
21967
22168
  import { promisify } from "node:util";
21968
- import { homedir as homedir2 } from "node:os";
22169
+ import { homedir as homedir3 } from "node:os";
21969
22170
  import { join as join8 } from "node:path";
21970
22171
  function tokenFromCredsBlob(raw) {
21971
22172
  try {
@@ -21995,7 +22196,7 @@ async function resolveOauthToken(env) {
21995
22196
  const ambientTok = process.env.CLAUDE_CODE_OAUTH_TOKEN?.trim();
21996
22197
  if (ambientTok)
21997
22198
  return ambientTok;
21998
- const fromAmbientDir = await tokenFromConfigDir(process.env.CLAUDE_CONFIG_DIR ?? join8(homedir2(), ".claude"));
22199
+ const fromAmbientDir = await tokenFromConfigDir(process.env.CLAUDE_CONFIG_DIR ?? join8(homedir3(), ".claude"));
21999
22200
  if (fromAmbientDir)
22000
22201
  return fromAmbientDir;
22001
22202
  if (process.platform === "darwin") {
@@ -22111,17 +22312,45 @@ var init_session_usage = __esm({
22111
22312
  }
22112
22313
  });
22113
22314
 
22315
+ // ../../packages/agent-core/dist/kill-tree.js
22316
+ import { spawn } from "node:child_process";
22317
+ function killProcessTree(child, signal = "SIGKILL") {
22318
+ if (!child || child.pid == null || child.exitCode !== null)
22319
+ return;
22320
+ const pid = child.pid;
22321
+ if (process.platform === "win32") {
22322
+ try {
22323
+ spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
22324
+ } catch {
22325
+ }
22326
+ return;
22327
+ }
22328
+ try {
22329
+ process.kill(-pid, signal);
22330
+ } catch {
22331
+ try {
22332
+ child.kill(signal);
22333
+ } catch {
22334
+ }
22335
+ }
22336
+ }
22337
+ var init_kill_tree = __esm({
22338
+ "../../packages/agent-core/dist/kill-tree.js"() {
22339
+ "use strict";
22340
+ }
22341
+ });
22342
+
22114
22343
  // ../../packages/agent-core/dist/agent-manager.js
22115
22344
  import { query, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
22116
22345
  import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile6 } from "node:fs/promises";
22117
22346
  import { randomUUID as randomUUID2 } from "node:crypto";
22118
- import { tmpdir, homedir as homedir3 } from "node:os";
22347
+ import { tmpdir, homedir as homedir4 } from "node:os";
22119
22348
  import { join as join9, dirname as dirname5 } from "node:path";
22120
22349
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
22121
22350
  import { fileURLToPath } from "node:url";
22122
22351
  import { createRequire } from "node:module";
22123
22352
  import { createServer } from "node:net";
22124
- import { spawn, execSync } from "node:child_process";
22353
+ import { spawn as spawn2, execSync } from "node:child_process";
22125
22354
  function findClaudeCliJs() {
22126
22355
  const require2 = createRequire(import.meta.url);
22127
22356
  const sdkMain = require2.resolve("@anthropic-ai/claude-agent-sdk");
@@ -22294,9 +22523,9 @@ function sourcesEqual(a, b) {
22294
22523
  if (a.length !== b.length)
22295
22524
  return false;
22296
22525
  const as = [...a].sort();
22297
- const bs = [...b].sort();
22526
+ const bs2 = [...b].sort();
22298
22527
  for (let i = 0; i < as.length; i++)
22299
- if (as[i] !== bs[i])
22528
+ if (as[i] !== bs2[i])
22300
22529
  return false;
22301
22530
  return true;
22302
22531
  }
@@ -22328,7 +22557,7 @@ MOBILE (${fw}):
22328
22557
  return "";
22329
22558
  }
22330
22559
  }
22331
- var cachedNodeExe, HUMAN_READABLE_FIELDS, TOOL_RESULT_CAP, MAX_RESULT_IMAGES, MAX_IMAGE_BASE64, PERMISSION_MODES, EDIT_PREVIEW_CAP, AsyncMessagePump, IDLE_REAP_MS, MODERATE_IDLE_MS, STEER_COALESCE_MS, AgentManager;
22560
+ var cachedNodeExe, HUMAN_READABLE_FIELDS, TOOL_RESULT_CAP, MAX_RESULT_IMAGES, MAX_IMAGE_BASE64, PERMISSION_MODES, EDIT_PREVIEW_CAP, AsyncMessagePump, IDLE_REAP_MS, MODERATE_IDLE_MS, STEER_COALESCE_MS, STOP_HARD_KILL_AFTER_MS, AgentManager;
22332
22561
  var init_agent_manager = __esm({
22333
22562
  "../../packages/agent-core/dist/agent-manager.js"() {
22334
22563
  "use strict";
@@ -22344,6 +22573,7 @@ var init_agent_manager = __esm({
22344
22573
  init_trace_store();
22345
22574
  init_session_store();
22346
22575
  init_session_usage();
22576
+ init_kill_tree();
22347
22577
  init_log();
22348
22578
  HUMAN_READABLE_FIELDS = ["message", "text", "content", "summary", "description", "output", "stdout", "error", "reason", "name"];
22349
22579
  TOOL_RESULT_CAP = 16e3;
@@ -22393,6 +22623,7 @@ var init_agent_manager = __esm({
22393
22623
  IDLE_REAP_MS = 5 * 60 * 1e3;
22394
22624
  MODERATE_IDLE_MS = 60 * 1e3;
22395
22625
  STEER_COALESCE_MS = 500;
22626
+ STOP_HARD_KILL_AFTER_MS = 1200;
22396
22627
  AgentManager = class {
22397
22628
  send;
22398
22629
  onBrowserRequest;
@@ -23145,7 +23376,7 @@ CRITICAL RULES:
23145
23376
  // fall back to ~/.tesseract/canvas when the session isn't on disk yet.
23146
23377
  canvasDir: async () => {
23147
23378
  const dir = await sessionSidecarDir(live.sessionId, projectDir);
23148
- return dir ? join9(dir, "canvas") : join9(process.env.CCB_TESSERACT_DIR ?? join9(homedir3(), ".tesseract"), "canvas");
23379
+ return dir ? join9(dir, "canvas") : join9(process.env.CCB_TESSERACT_DIR ?? join9(homedir4(), ".tesseract"), "canvas");
23149
23380
  },
23150
23381
  emitCanvasReady: (p) => {
23151
23382
  const ts = Date.now();
@@ -23298,12 +23529,18 @@ CRITICAL RULES:
23298
23529
  const exe = nodeExe2 ?? process.execPath;
23299
23530
  const runAsNode = exe === process.execPath && !!process.versions.electron;
23300
23531
  const spawnEnv = { ...options.env, ...params.env ?? {}, ...params.engineEnv ?? {}, CCB_IPC_PORT: String(live.ipcPort), ...runAsNode ? { ELECTRON_RUN_AS_NODE: "1" } : {} };
23301
- const child = spawn(exe, [cliJs, ...options.args], {
23532
+ const child = spawn2(exe, [cliJs, ...options.args], {
23302
23533
  cwd: options.cwd,
23303
23534
  env: spawnEnv,
23304
23535
  signal: options.signal,
23305
- stdio: ["pipe", "pipe", "pipe"]
23536
+ stdio: ["pipe", "pipe", "pipe"],
23537
+ // Own process group (POSIX) so Stop can SIGKILL the WHOLE tree — the
23538
+ // CLI re-execs node and its Bash tool spawns grandchildren (builds,
23539
+ // dev servers, sleeps) that a single-PID kill would orphan. Pipes keep
23540
+ // it attached; we never unref, so the parent still tracks it.
23541
+ detached: process.platform !== "win32"
23306
23542
  });
23543
+ live.child = child;
23307
23544
  return child;
23308
23545
  },
23309
23546
  // Claude Code preset + our browser instructions appended. The preset
@@ -23556,6 +23793,7 @@ CRITICAL RULES:
23556
23793
  refreshSessionUsage(turnSid);
23557
23794
  } else if (live.interrupted) {
23558
23795
  live.interrupted = false;
23796
+ this.clearStopWatchdog(live);
23559
23797
  log("Session interrupted by user:", turnSid);
23560
23798
  emit({ type: "chat:error", sessionId: turnSid, error: "Interrupted." });
23561
23799
  emit({ type: "agent:status", sessionId: turnSid, status: "idle" });
@@ -23779,6 +24017,7 @@ ${text3}` : header;
23779
24017
  const sid = live.sessionId;
23780
24018
  if (live.interrupted || live.abort.signal.aborted) {
23781
24019
  live.interrupted = false;
24020
+ this.clearStopWatchdog(live);
23782
24021
  log("Session interrupted by user (caught from iterator):", sid);
23783
24022
  emit({ type: "chat:error", sessionId: sid, error: "Interrupted." });
23784
24023
  emit({ type: "agent:status", sessionId: sid, status: "idle" });
@@ -23798,6 +24037,7 @@ ${err.stack}` : ""}` : String(err);
23798
24037
  clearTimeout(live.steerFlushTimer);
23799
24038
  live.steerFlushTimer = void 0;
23800
24039
  }
24040
+ this.clearStopWatchdog(live);
23801
24041
  if (live.sessionId)
23802
24042
  this.liveSessions.delete(live.sessionId);
23803
24043
  this.pendingLiveSessions.delete(live);
@@ -23805,6 +24045,8 @@ ${err.stack}` : ""}` : String(err);
23805
24045
  live.query.close();
23806
24046
  } catch {
23807
24047
  }
24048
+ if (live.abort.signal.aborted)
24049
+ killProcessTree(live.child, "SIGKILL");
23808
24050
  for (const s of live.ipcSockets) {
23809
24051
  try {
23810
24052
  s.destroy();
@@ -23832,28 +24074,54 @@ ${err.stack}` : ""}` : String(err);
23832
24074
  live.pendingSteer = void 0;
23833
24075
  live.interrupted = true;
23834
24076
  live.query.interrupt().catch((err) => {
23835
- log("interrupt() failed, falling back to abort:", err instanceof Error ? err.message : String(err));
23836
- try {
23837
- live.abort.abort();
23838
- } catch {
23839
- }
24077
+ log("interrupt() failed, force-killing process tree:", err instanceof Error ? err.message : String(err));
24078
+ this.hardKillSession(live);
23840
24079
  });
24080
+ this.armStopWatchdog(live);
23841
24081
  return true;
23842
24082
  }
23843
24083
  for (const pending of this.pendingLiveSessions) {
23844
24084
  log("Interrupting pending session (no canonical id yet)");
23845
24085
  pending.interrupted = true;
23846
- pending.query.interrupt().catch(() => {
23847
- try {
23848
- pending.abort.abort();
23849
- } catch {
23850
- }
23851
- });
24086
+ pending.query.interrupt().catch(() => this.hardKillSession(pending));
24087
+ this.armStopWatchdog(pending);
23852
24088
  return true;
23853
24089
  }
23854
24090
  log("No active session to interrupt for:", sessionId);
23855
24091
  return false;
23856
24092
  }
24093
+ /** Arm the Stop watchdog: if the graceful interrupt hasn't ended the turn
24094
+ * within STOP_HARD_KILL_AFTER_MS, force-kill the whole process tree. The
24095
+ * `interrupted` flag is reset to false when the interrupt is acknowledged
24096
+ * (result frame / caught iterator), so a timely graceful stop self-cancels. */
24097
+ armStopWatchdog(live) {
24098
+ if (live.killTimer)
24099
+ clearTimeout(live.killTimer);
24100
+ live.killTimer = setTimeout(() => {
24101
+ live.killTimer = void 0;
24102
+ if (live.interrupted && !live.reaped) {
24103
+ log("Stop watchdog: graceful interrupt stalled \u2014 force-killing process tree for", live.sessionId);
24104
+ this.hardKillSession(live);
24105
+ }
24106
+ }, STOP_HARD_KILL_AFTER_MS);
24107
+ }
24108
+ /** Cancel a pending Stop watchdog (graceful interrupt landed / session torn down). */
24109
+ clearStopWatchdog(live) {
24110
+ if (live.killTimer) {
24111
+ clearTimeout(live.killTimer);
24112
+ live.killTimer = void 0;
24113
+ }
24114
+ }
24115
+ /** Force-stop a session NOW: abort the SDK (so the consumer treats it as an
24116
+ * interrupt) and SIGKILL the CLI's entire process group, reaping any Bash /
24117
+ * tool / MCP grandchildren that a graceful stop left running. */
24118
+ hardKillSession(live) {
24119
+ try {
24120
+ live.abort.abort();
24121
+ } catch {
24122
+ }
24123
+ killProcessTree(live.child, "SIGKILL");
24124
+ }
23857
24125
  /** M7 — stop ONE background task (vs interruptSession which aborts the whole
23858
24126
  * turn). Driver-only, same origin guard. No-op if the session/task is gone. */
23859
24127
  async stopTask(sessionId, taskId, callerOrigin) {
@@ -24042,6 +24310,7 @@ ${err.stack}` : ""}` : String(err);
24042
24310
  clearTimeout(live.steerFlushTimer);
24043
24311
  live.steerFlushTimer = void 0;
24044
24312
  }
24313
+ this.clearStopWatchdog(live);
24045
24314
  if (live.sessionId)
24046
24315
  this.liveSessions.delete(live.sessionId);
24047
24316
  this.pendingLiveSessions.delete(live);
@@ -24049,6 +24318,7 @@ ${err.stack}` : ""}` : String(err);
24049
24318
  live.abort.abort();
24050
24319
  } catch {
24051
24320
  }
24321
+ killProcessTree(live.child, "SIGKILL");
24052
24322
  live.pump.end();
24053
24323
  }
24054
24324
  }
@@ -24109,6 +24379,7 @@ __export(dist_exports, {
24109
24379
  getTokenSegments: () => getTokenSegments,
24110
24380
  importSession: () => importSession,
24111
24381
  isToken: () => isToken,
24382
+ killProcessTree: () => killProcessTree,
24112
24383
  log: () => log,
24113
24384
  normalizeTokens: () => normalizeTokens,
24114
24385
  parseColorToRgb: () => parseColorToRgb,
@@ -24137,6 +24408,7 @@ var init_dist4 = __esm({
24137
24408
  init_session_usage();
24138
24409
  init_session_transfer();
24139
24410
  init_log();
24411
+ init_kill_tree();
24140
24412
  init_session_artifacts();
24141
24413
  init_imagegen_tools();
24142
24414
  init_design_tokens2();
@@ -26501,7 +26773,7 @@ var require_build2 = __commonJS({
26501
26773
  throw new Error("BigInt is not defined. Your environment doesn't implement BigInt.");
26502
26774
  }
26503
26775
  }
26504
- function concat(buffers) {
26776
+ function concat2(buffers) {
26505
26777
  let outputLength = 0;
26506
26778
  let prevLength = 0;
26507
26779
  for (let i = 0; i < buffers.length; i++) {
@@ -26547,7 +26819,7 @@ var require_build2 = __commonJS({
26547
26819
  this.items.push(buf);
26548
26820
  }
26549
26821
  final() {
26550
- return concat(this.items);
26822
+ return concat2(this.items);
26551
26823
  }
26552
26824
  };
26553
26825
  var powers2 = [new Uint8Array([1])];
@@ -28267,7 +28539,7 @@ ${values.join("\n")}` : `${blockName} :`;
28267
28539
  }
28268
28540
  retBuffers.push(valueBuf);
28269
28541
  }
28270
- return concat(retBuffers);
28542
+ return concat2(retBuffers);
28271
28543
  }
28272
28544
  fromString(string4) {
28273
28545
  this.value = [];
@@ -28496,7 +28768,7 @@ ${values.join("\n")}` : `${blockName} :`;
28496
28768
  }
28497
28769
  retBuffers.push(valueBuf);
28498
28770
  }
28499
- return concat(retBuffers);
28771
+ return concat2(retBuffers);
28500
28772
  }
28501
28773
  fromString(string4) {
28502
28774
  this.value = [];
@@ -29710,7 +29982,7 @@ var require_concat = __commonJS({
29710
29982
  "use strict";
29711
29983
  Object.defineProperty(exports, "__esModule", { value: true });
29712
29984
  exports.concatToUint8Array = concatToUint8Array;
29713
- exports.concat = concat;
29985
+ exports.concat = concat2;
29714
29986
  var buffer_source_js_1 = require_buffer_source();
29715
29987
  function concatToUint8Array(buffers) {
29716
29988
  const views = [];
@@ -29728,7 +30000,7 @@ var require_concat = __commonJS({
29728
30000
  }
29729
30001
  return result;
29730
30002
  }
29731
- function concat(first, second, ...rest) {
30003
+ function concat2(first, second, ...rest) {
29732
30004
  let buffers;
29733
30005
  let type;
29734
30006
  if (typeof second === "function") {
@@ -36978,13 +37250,13 @@ var require_lazy_helpers = __commonJS({
36978
37250
  }
36979
37251
  };
36980
37252
  exports.DelayedConstructor = DelayedConstructor;
36981
- function delay2(wrappedConstructor) {
37253
+ function delay3(wrappedConstructor) {
36982
37254
  if (typeof wrappedConstructor === "undefined") {
36983
37255
  throw new Error("Attempt to `delay` undefined. Constructor must be wrapped in a callback");
36984
37256
  }
36985
37257
  return new DelayedConstructor(wrappedConstructor);
36986
37258
  }
36987
- exports.delay = delay2;
37259
+ exports.delay = delay3;
36988
37260
  }
36989
37261
  });
36990
37262
 
@@ -38896,11 +39168,11 @@ var require_x509_cjs = __commonJS({
38896
39168
  var brainpoolP384t1 = "brainpoolP384t1";
38897
39169
  var brainpoolP512r1 = "brainpoolP512r1";
38898
39170
  var brainpoolP512t1 = "brainpoolP512t1";
38899
- var ECDSA = "ECDSA";
39171
+ var ECDSA2 = "ECDSA";
38900
39172
  exports.EcAlgorithm = EcAlgorithm_1 = class EcAlgorithm {
38901
39173
  toAsnAlgorithm(alg) {
38902
39174
  switch (alg.name.toLowerCase()) {
38903
- case ECDSA.toLowerCase():
39175
+ case ECDSA2.toLowerCase():
38904
39176
  if ("hash" in alg) {
38905
39177
  const hash2 = typeof alg.hash === "string" ? alg.hash : alg.hash.name;
38906
39178
  switch (hash2.toLowerCase()) {
@@ -38985,22 +39257,22 @@ var require_x509_cjs = __commonJS({
38985
39257
  switch (alg.algorithm) {
38986
39258
  case asn1Ecc__namespace.id_ecdsaWithSHA1:
38987
39259
  return {
38988
- name: ECDSA,
39260
+ name: ECDSA2,
38989
39261
  hash: { name: "SHA-1" }
38990
39262
  };
38991
39263
  case asn1Ecc__namespace.id_ecdsaWithSHA256:
38992
39264
  return {
38993
- name: ECDSA,
39265
+ name: ECDSA2,
38994
39266
  hash: { name: "SHA-256" }
38995
39267
  };
38996
39268
  case asn1Ecc__namespace.id_ecdsaWithSHA384:
38997
39269
  return {
38998
- name: ECDSA,
39270
+ name: ECDSA2,
38999
39271
  hash: { name: "SHA-384" }
39000
39272
  };
39001
39273
  case asn1Ecc__namespace.id_ecdsaWithSHA512:
39002
39274
  return {
39003
- name: ECDSA,
39275
+ name: ECDSA2,
39004
39276
  hash: { name: "SHA-512" }
39005
39277
  };
39006
39278
  case asn1Ecc__namespace.id_ecPublicKey: {
@@ -39011,92 +39283,92 @@ var require_x509_cjs = __commonJS({
39011
39283
  switch (parameters.namedCurve) {
39012
39284
  case asn1Ecc__namespace.id_secp256r1:
39013
39285
  return {
39014
- name: ECDSA,
39286
+ name: ECDSA2,
39015
39287
  namedCurve: "P-256"
39016
39288
  };
39017
39289
  case EcAlgorithm_1.SECP256K1:
39018
39290
  return {
39019
- name: ECDSA,
39291
+ name: ECDSA2,
39020
39292
  namedCurve: "K-256"
39021
39293
  };
39022
39294
  case asn1Ecc__namespace.id_secp384r1:
39023
39295
  return {
39024
- name: ECDSA,
39296
+ name: ECDSA2,
39025
39297
  namedCurve: "P-384"
39026
39298
  };
39027
39299
  case asn1Ecc__namespace.id_secp521r1:
39028
39300
  return {
39029
- name: ECDSA,
39301
+ name: ECDSA2,
39030
39302
  namedCurve: "P-521"
39031
39303
  };
39032
39304
  case idBrainpoolP160r1:
39033
39305
  return {
39034
- name: ECDSA,
39306
+ name: ECDSA2,
39035
39307
  namedCurve: brainpoolP160r1
39036
39308
  };
39037
39309
  case idBrainpoolP160t1:
39038
39310
  return {
39039
- name: ECDSA,
39311
+ name: ECDSA2,
39040
39312
  namedCurve: brainpoolP160t1
39041
39313
  };
39042
39314
  case idBrainpoolP192r1:
39043
39315
  return {
39044
- name: ECDSA,
39316
+ name: ECDSA2,
39045
39317
  namedCurve: brainpoolP192r1
39046
39318
  };
39047
39319
  case idBrainpoolP192t1:
39048
39320
  return {
39049
- name: ECDSA,
39321
+ name: ECDSA2,
39050
39322
  namedCurve: brainpoolP192t1
39051
39323
  };
39052
39324
  case idBrainpoolP224r1:
39053
39325
  return {
39054
- name: ECDSA,
39326
+ name: ECDSA2,
39055
39327
  namedCurve: brainpoolP224r1
39056
39328
  };
39057
39329
  case idBrainpoolP224t1:
39058
39330
  return {
39059
- name: ECDSA,
39331
+ name: ECDSA2,
39060
39332
  namedCurve: brainpoolP224t1
39061
39333
  };
39062
39334
  case idBrainpoolP256r1:
39063
39335
  return {
39064
- name: ECDSA,
39336
+ name: ECDSA2,
39065
39337
  namedCurve: brainpoolP256r1
39066
39338
  };
39067
39339
  case idBrainpoolP256t1:
39068
39340
  return {
39069
- name: ECDSA,
39341
+ name: ECDSA2,
39070
39342
  namedCurve: brainpoolP256t1
39071
39343
  };
39072
39344
  case idBrainpoolP320r1:
39073
39345
  return {
39074
- name: ECDSA,
39346
+ name: ECDSA2,
39075
39347
  namedCurve: brainpoolP320r1
39076
39348
  };
39077
39349
  case idBrainpoolP320t1:
39078
39350
  return {
39079
- name: ECDSA,
39351
+ name: ECDSA2,
39080
39352
  namedCurve: brainpoolP320t1
39081
39353
  };
39082
39354
  case idBrainpoolP384r1:
39083
39355
  return {
39084
- name: ECDSA,
39356
+ name: ECDSA2,
39085
39357
  namedCurve: brainpoolP384r1
39086
39358
  };
39087
39359
  case idBrainpoolP384t1:
39088
39360
  return {
39089
- name: ECDSA,
39361
+ name: ECDSA2,
39090
39362
  namedCurve: brainpoolP384t1
39091
39363
  };
39092
39364
  case idBrainpoolP512r1:
39093
39365
  return {
39094
- name: ECDSA,
39366
+ name: ECDSA2,
39095
39367
  namedCurve: brainpoolP512r1
39096
39368
  };
39097
39369
  case idBrainpoolP512t1:
39098
39370
  return {
39099
- name: ECDSA,
39371
+ name: ECDSA2,
39100
39372
  namedCurve: brainpoolP512t1
39101
39373
  };
39102
39374
  }
@@ -42479,6 +42751,95 @@ var require_selfsigned = __commonJS({
42479
42751
  }
42480
42752
  });
42481
42753
 
42754
+ // ../../packages/host-core/dist/peer-link/tls-direct.js
42755
+ import tls from "node:tls";
42756
+ import { X509Certificate, timingSafeEqual } from "node:crypto";
42757
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync10, mkdirSync as mkdirSync8 } from "node:fs";
42758
+ import { dirname as dirname10 } from "node:path";
42759
+ function fingerprintOf(certPem) {
42760
+ return new X509Certificate(certPem).fingerprint256;
42761
+ }
42762
+ function fingerprintsEqual(a, b) {
42763
+ if (!a || !b)
42764
+ return false;
42765
+ const ba = Buffer.from(a);
42766
+ const bb = Buffer.from(b);
42767
+ return ba.length === bb.length && timingSafeEqual(ba, bb);
42768
+ }
42769
+ async function loadOrCreateIdentity(certPath, keyPath) {
42770
+ if (existsSync10(certPath) && existsSync10(keyPath)) {
42771
+ const cert = readFileSync9(certPath, "utf8");
42772
+ const key2 = readFileSync9(keyPath, "utf8");
42773
+ return { cert, key: key2, fingerprint256: fingerprintOf(cert) };
42774
+ }
42775
+ const tenYears = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3);
42776
+ const pems = await (0, import_selfsigned.generate)([{ name: "commonName", value: "tesseract-peer" }], { keySize: 2048, notAfterDate: tenYears, algorithm: "sha256" });
42777
+ try {
42778
+ mkdirSync8(dirname10(certPath), { recursive: true });
42779
+ } catch {
42780
+ }
42781
+ try {
42782
+ writeFileSync9(certPath, pems.cert, { mode: 384 });
42783
+ writeFileSync9(keyPath, pems.private, { mode: 384 });
42784
+ } catch {
42785
+ }
42786
+ return { cert: pems.cert, key: pems.private, fingerprint256: fingerprintOf(pems.cert) };
42787
+ }
42788
+ function pinnedConnect(opts) {
42789
+ return new Promise((resolve10, reject) => {
42790
+ let settled = false;
42791
+ const done = (err, sock) => {
42792
+ if (settled)
42793
+ return;
42794
+ settled = true;
42795
+ if (err) {
42796
+ try {
42797
+ sock?.destroy();
42798
+ } catch {
42799
+ }
42800
+ reject(err);
42801
+ } else
42802
+ resolve10(sock);
42803
+ };
42804
+ const socket2 = tls.connect({
42805
+ host: opts.host,
42806
+ port: opts.port,
42807
+ // No CA / hostname identity — we authenticate SOLELY by the brokered
42808
+ // fingerprint, so accept the self-signed cert at the TLS layer and verify
42809
+ // the pin ourselves below (fail-closed).
42810
+ rejectUnauthorized: false,
42811
+ checkServerIdentity: () => void 0,
42812
+ timeout: opts.timeoutMs ?? 8e3
42813
+ }, () => {
42814
+ const peer = socket2.getPeerCertificate();
42815
+ const fp = peer && peer.fingerprint256;
42816
+ if (!fingerprintsEqual(fp, opts.peerFingerprint256)) {
42817
+ done(new Error("peer-link: TLS fingerprint mismatch (fail-closed)"));
42818
+ return;
42819
+ }
42820
+ socket2.setNoDelay(true);
42821
+ done(void 0, socket2);
42822
+ });
42823
+ socket2.once("timeout", () => done(new Error("peer-link: TLS connect timeout")));
42824
+ socket2.once("error", (e) => done(e instanceof Error ? e : new Error(String(e))));
42825
+ });
42826
+ }
42827
+ function createListener(identity, port, onSocket) {
42828
+ const server = tls.createServer({ cert: identity.cert, key: identity.key, requestCert: false, rejectUnauthorized: false }, (sock) => {
42829
+ sock.setNoDelay(true);
42830
+ onSocket(sock);
42831
+ });
42832
+ server.listen(port, "0.0.0.0");
42833
+ return server;
42834
+ }
42835
+ var import_selfsigned;
42836
+ var init_tls_direct = __esm({
42837
+ "../../packages/host-core/dist/peer-link/tls-direct.js"() {
42838
+ "use strict";
42839
+ import_selfsigned = __toESM(require_selfsigned(), 1);
42840
+ }
42841
+ });
42842
+
42482
42843
  // ../../packages/host-core/dist/peer-link/framing.js
42483
42844
  function encodeFrame2(payload) {
42484
42845
  const out = new Uint8Array(4 + payload.length);
@@ -42628,9 +42989,9 @@ var init_transport = __esm({
42628
42989
  });
42629
42990
 
42630
42991
  // ../../packages/host-core/dist/peer-link/stun.js
42631
- import { randomBytes } from "node:crypto";
42992
+ import { randomBytes as randomBytes2 } from "node:crypto";
42632
42993
  function encodeBindingRequest() {
42633
- const txId = randomBytes(12);
42994
+ const txId = randomBytes2(12);
42634
42995
  const packet = Buffer.alloc(20);
42635
42996
  packet.writeUInt16BE(BINDING_REQUEST, 0);
42636
42997
  packet.writeUInt16BE(0, 2);
@@ -42766,11 +43127,11 @@ function quicByteSocket(stream) {
42766
43127
  }
42767
43128
  function pinningVerify(expectedFingerprint256) {
42768
43129
  return async (certs) => {
42769
- const { X509Certificate: X509Certificate2 } = await import("node:crypto");
42770
43130
  if (!certs.length)
42771
43131
  throw new Error("quic: peer presented no certificate");
43132
+ const { X509Certificate: X509Certificate2 } = await import("node:crypto");
42772
43133
  const fp = new X509Certificate2(Buffer.from(certs[0])).fingerprint256;
42773
- if (fp !== expectedFingerprint256)
43134
+ if (!fingerprintsEqual(fp, expectedFingerprint256))
42774
43135
  throw new Error("quic: TLS fingerprint mismatch (fail-closed)");
42775
43136
  return void 0;
42776
43137
  };
@@ -42852,6 +43213,7 @@ var init_quic_transport = __esm({
42852
43213
  "use strict";
42853
43214
  init_transport();
42854
43215
  init_stun();
43216
+ init_tls_direct();
42855
43217
  ALPN = ["tesseract-peer"];
42856
43218
  quicModP = null;
42857
43219
  loadQuic = () => quicModP ??= import("@matrixai/quic");
@@ -43934,17 +44296,17 @@ var require_main = __commonJS({
43934
44296
  });
43935
44297
 
43936
44298
  // src/cli.ts
43937
- import { readFileSync as readFileSync14 } from "node:fs";
44299
+ import { readFileSync as readFileSync15 } from "node:fs";
43938
44300
  import { createHash as createHash5 } from "node:crypto";
43939
- import { spawn as spawn7 } from "node:child_process";
44301
+ import { spawn as spawn8 } from "node:child_process";
43940
44302
  import { fileURLToPath as fileURLToPath2 } from "node:url";
43941
- import { homedir as homedir17 } from "node:os";
43942
- import { dirname as dirname18, join as join36, resolve as resolve9 } from "node:path";
44303
+ import { homedir as homedir16 } from "node:os";
44304
+ import { dirname as dirname18, join as join38, resolve as resolve9 } from "node:path";
43943
44305
  import { createInterface } from "node:readline/promises";
43944
44306
 
43945
44307
  // ../../packages/host-core/dist/control-link.js
43946
- import { hostname as hostname3, homedir as homedir13 } from "node:os";
43947
- import { join as join29, basename as basename7 } from "node:path";
44308
+ import { hostname as hostname3 } from "node:os";
44309
+ import { join as join30, basename as basename7 } from "node:path";
43948
44310
  import { mkdirSync as mkdirSync11 } from "node:fs";
43949
44311
 
43950
44312
  // ../../node_modules/ws/wrapper.mjs
@@ -43960,6 +44322,7 @@ var wrapper_default = import_websocket.default;
43960
44322
 
43961
44323
  // ../../packages/host-core/dist/path-guard.js
43962
44324
  import { realpathSync } from "node:fs";
44325
+ import { homedir } from "node:os";
43963
44326
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
43964
44327
  function canonicalForGuard(p) {
43965
44328
  const abs = resolve(p);
@@ -43990,6 +44353,380 @@ function isInsideRoot(p, root) {
43990
44353
  const rel = relative(realRoot, canonicalForGuard(p));
43991
44354
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
43992
44355
  }
44356
+ function safeResolve(p, root) {
44357
+ if (!root)
44358
+ return null;
44359
+ try {
44360
+ const real = realpathSync.native(p);
44361
+ return isInsideRoot(real, root) ? real : null;
44362
+ } catch {
44363
+ return isInsideRoot(p, root) ? p : null;
44364
+ }
44365
+ }
44366
+ function resolveContainmentRoot() {
44367
+ const env = process.env.CCB_HOST_ROOT;
44368
+ return env && env.trim() ? env : null;
44369
+ }
44370
+ function resolveHostRoot() {
44371
+ return resolveContainmentRoot() ?? homedir();
44372
+ }
44373
+
44374
+ // ../../packages/e2e/dist/suite.js
44375
+ var subtle = globalThis.crypto.subtle;
44376
+ var bs = (b) => b;
44377
+ var SUITE = "TSE1";
44378
+ var EC = { name: "ECDH", namedCurve: "P-256" };
44379
+ var ECDSA = { name: "ECDSA", namedCurve: "P-256" };
44380
+ function randomBytes(n) {
44381
+ return globalThis.crypto.getRandomValues(new Uint8Array(n));
44382
+ }
44383
+ function b64u(bytes) {
44384
+ let s = "";
44385
+ for (const b of bytes)
44386
+ s += String.fromCharCode(b);
44387
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
44388
+ }
44389
+ function unb64u(s) {
44390
+ const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
44391
+ const b = atob(s.replace(/-/g, "+").replace(/_/g, "/") + pad);
44392
+ const out = new Uint8Array(b.length);
44393
+ for (let i = 0; i < b.length; i++)
44394
+ out[i] = b.charCodeAt(i);
44395
+ return out;
44396
+ }
44397
+ function utf8(s) {
44398
+ return new TextEncoder().encode(s);
44399
+ }
44400
+ function concat(...parts) {
44401
+ const len = parts.reduce((n, p) => n + p.length, 0);
44402
+ const out = new Uint8Array(len);
44403
+ let o = 0;
44404
+ for (const p of parts) {
44405
+ out.set(p, o);
44406
+ o += p.length;
44407
+ }
44408
+ return out;
44409
+ }
44410
+ async function generateIdentityKeyPair(extractable = false) {
44411
+ const kp = await subtle.generateKey(ECDSA, extractable, ["sign", "verify"]);
44412
+ const publicRaw = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
44413
+ return { privateKey: kp.privateKey, publicKey: kp.publicKey, publicRaw };
44414
+ }
44415
+ async function importIdentityPublic(raw) {
44416
+ return subtle.importKey("raw", bs(raw), ECDSA, true, ["verify"]);
44417
+ }
44418
+ async function exportIdentity(kp) {
44419
+ const privateJwk = await subtle.exportKey("jwk", kp.privateKey);
44420
+ return { privateJwk, publicRaw: b64u(kp.publicRaw) };
44421
+ }
44422
+ async function importIdentity(privateJwk) {
44423
+ const privateKey = await subtle.importKey("jwk", privateJwk, ECDSA, true, ["sign"]);
44424
+ const pubJwk = { kty: privateJwk.kty, crv: privateJwk.crv, x: privateJwk.x, y: privateJwk.y, ext: true };
44425
+ const publicKey = await subtle.importKey("jwk", pubJwk, ECDSA, true, ["verify"]);
44426
+ const publicRaw = new Uint8Array(await subtle.exportKey("raw", publicKey));
44427
+ return { privateKey, publicKey, publicRaw };
44428
+ }
44429
+ async function sign(privateKey, message) {
44430
+ return new Uint8Array(await subtle.sign({ name: "ECDSA", hash: "SHA-256" }, privateKey, bs(message)));
44431
+ }
44432
+ async function verify(publicRaw, message, sig) {
44433
+ try {
44434
+ const key2 = await importIdentityPublic(publicRaw);
44435
+ return await subtle.verify({ name: "ECDSA", hash: "SHA-256" }, key2, bs(sig), bs(message));
44436
+ } catch {
44437
+ return false;
44438
+ }
44439
+ }
44440
+ async function generateEphemeral() {
44441
+ const kp = await subtle.generateKey(EC, false, ["deriveBits"]);
44442
+ const publicRaw = new Uint8Array(await subtle.exportKey("raw", kp.publicKey));
44443
+ return { privateKey: kp.privateKey, publicRaw };
44444
+ }
44445
+ async function ecdh(privateKey, peerPublicRaw) {
44446
+ const peer = await subtle.importKey("raw", bs(peerPublicRaw), EC, false, []);
44447
+ return new Uint8Array(await subtle.deriveBits({ name: "ECDH", public: peer }, privateKey, 256));
44448
+ }
44449
+ async function hkdf(ikm, salt, info, bytes) {
44450
+ const key2 = await subtle.importKey("raw", bs(ikm), "HKDF", false, ["deriveBits"]);
44451
+ return new Uint8Array(await subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt: bs(salt), info: bs(info) }, key2, bytes * 8));
44452
+ }
44453
+ async function importAesKey(raw) {
44454
+ return subtle.importKey("raw", bs(raw), { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
44455
+ }
44456
+ async function aesSeal(key2, nonce2, plaintext, aad) {
44457
+ return new Uint8Array(await subtle.encrypt({ name: "AES-GCM", iv: bs(nonce2), additionalData: bs(aad) }, key2, bs(plaintext)));
44458
+ }
44459
+ async function aesOpen(key2, nonce2, ciphertext, aad) {
44460
+ return new Uint8Array(await subtle.decrypt({ name: "AES-GCM", iv: bs(nonce2), additionalData: bs(aad) }, key2, bs(ciphertext)));
44461
+ }
44462
+ async function sha256(bytes) {
44463
+ return new Uint8Array(await subtle.digest("SHA-256", bs(bytes)));
44464
+ }
44465
+
44466
+ // ../../packages/e2e/dist/record.js
44467
+ function canonicalAad(r, dir, seq) {
44468
+ return utf8(JSON.stringify([1, SUITE, r.ch, r.host, r.client, r.sess, r.bid ?? "", dir, seq]));
44469
+ }
44470
+ function nonce(prefix, seq) {
44471
+ const n = new Uint8Array(12);
44472
+ n.set(prefix.subarray(0, 4), 0);
44473
+ const dv = new DataView(n.buffer);
44474
+ dv.setUint32(4, Math.floor(seq / 4294967296));
44475
+ dv.setUint32(8, seq >>> 0);
44476
+ return n;
44477
+ }
44478
+ var WINDOW = 1024;
44479
+ var ReplayWindow = class {
44480
+ highest = -1;
44481
+ seen = /* @__PURE__ */ new Set();
44482
+ permits(seq) {
44483
+ if (seq < 0)
44484
+ return false;
44485
+ if (seq <= this.highest - WINDOW)
44486
+ return false;
44487
+ return !this.seen.has(seq);
44488
+ }
44489
+ accept(seq) {
44490
+ this.seen.add(seq);
44491
+ if (seq > this.highest)
44492
+ this.highest = seq;
44493
+ for (const v of this.seen)
44494
+ if (v <= this.highest - WINDOW)
44495
+ this.seen.delete(v);
44496
+ }
44497
+ };
44498
+ var Sealer = class {
44499
+ key;
44500
+ prefix;
44501
+ dir;
44502
+ seq = 0;
44503
+ constructor(key2, prefix, dir) {
44504
+ this.key = key2;
44505
+ this.prefix = prefix;
44506
+ this.dir = dir;
44507
+ }
44508
+ async seal(routing, plaintext) {
44509
+ const s = this.seq++;
44510
+ const ct = await aesSeal(this.key, nonce(this.prefix, s), plaintext, canonicalAad(routing, this.dir, s));
44511
+ return { e2e: 1, s, ct: b64u(ct) };
44512
+ }
44513
+ async sealJson(routing, payload) {
44514
+ return this.seal(routing, utf8(JSON.stringify(payload)));
44515
+ }
44516
+ };
44517
+ var Opener = class {
44518
+ key;
44519
+ prefix;
44520
+ dir;
44521
+ window = new ReplayWindow();
44522
+ constructor(key2, prefix, dir) {
44523
+ this.key = key2;
44524
+ this.prefix = prefix;
44525
+ this.dir = dir;
44526
+ }
44527
+ async open(rec, routing) {
44528
+ if (!this.window.permits(rec.s))
44529
+ throw new Error("e2e: replayed or out-of-window sequence");
44530
+ const pt = await aesOpen(this.key, nonce(this.prefix, rec.s), unb64u(rec.ct), canonicalAad(routing, this.dir, rec.s));
44531
+ this.window.accept(rec.s);
44532
+ return pt;
44533
+ }
44534
+ async openJson(rec, routing) {
44535
+ return JSON.parse(new TextDecoder().decode(await this.open(rec, routing)));
44536
+ }
44537
+ };
44538
+
44539
+ // ../../packages/e2e/dist/handshake.js
44540
+ var HELLO_CAPS_DEFAULT = ["fs", "term", "agent"];
44541
+ async function helloTranscript(h) {
44542
+ return sha256(concat(utf8(`${h.suite}|${h.sess}|${h.from}|${h.to}|`), unb64u(h.eD), unb64u(h.nD), utf8(`|${h.caps.join(",")}`)));
44543
+ }
44544
+ function signedBytes(th, eD, eH, nD, nH, signer, tag) {
44545
+ return concat(th, unb64u(eD), unb64u(eH), unb64u(nD), unb64u(nH), utf8(`|${signer}|${tag}`));
44546
+ }
44547
+ async function deriveSession(rk, role, peerDeviceId, caps) {
44548
+ const kD2H = await importAesKey(await hkdf(rk, new Uint8Array(0), utf8("d2h key"), 32));
44549
+ const kH2D = await importAesKey(await hkdf(rk, new Uint8Array(0), utf8("h2d key"), 32));
44550
+ const pD2H = await hkdf(rk, new Uint8Array(0), utf8("d2h nonce"), 4);
44551
+ const pH2D = await hkdf(rk, new Uint8Array(0), utf8("h2d nonce"), 4);
44552
+ return role === "initiator" ? { sealer: new Sealer(kD2H, pD2H, "d2h"), opener: new Opener(kH2D, pH2D, "h2d"), peerDeviceId, caps } : { sealer: new Sealer(kH2D, pH2D, "h2d"), opener: new Opener(kD2H, pD2H, "d2h"), peerDeviceId, caps };
44553
+ }
44554
+ async function confirmKey(rk) {
44555
+ return importAesKey(await hkdf(rk, new Uint8Array(0), utf8("confirm"), 32));
44556
+ }
44557
+ var ZERO_NONCE = new Uint8Array(12);
44558
+ async function startHandshake(opts) {
44559
+ const eph = await generateEphemeral();
44560
+ const hello = {
44561
+ t: "e2e:hello",
44562
+ v: 1,
44563
+ suite: SUITE,
44564
+ sess: opts.sess,
44565
+ from: opts.selfDeviceId,
44566
+ to: opts.peerDeviceId,
44567
+ eD: b64u(eph.publicRaw),
44568
+ nD: b64u(randomBytes(16)),
44569
+ caps: opts.caps ?? HELLO_CAPS_DEFAULT
44570
+ };
44571
+ return { hello, state: { hello, eph, identityPriv: opts.identityPriv, selfDeviceId: opts.selfDeviceId } };
44572
+ }
44573
+ async function finishInitiator(state, resp, peerIdentityPub) {
44574
+ const th = await helloTranscript(state.hello);
44575
+ const okH = await verify(peerIdentityPub, signedBytes(th, state.hello.eD, resp.eH, state.hello.nD, resp.nH, resp.from, "resp"), unb64u(resp.sigH));
44576
+ if (!okH)
44577
+ throw new Error("e2e: host identity signature did not verify");
44578
+ const z2 = await ecdh(state.eph.privateKey, unb64u(resp.eH));
44579
+ const rk = await hkdf(z2, concat(unb64u(state.hello.nD), unb64u(resp.nH)), th, 32);
44580
+ const sigD = await sign(state.identityPriv, signedBytes(th, state.hello.eD, resp.eH, state.hello.nD, resp.nH, state.selfDeviceId, "confirm"));
44581
+ const fin = await aesSeal(await confirmKey(rk), ZERO_NONCE, new Uint8Array(0), th);
44582
+ const confirm2 = { t: "e2e:confirm", v: 1, sess: state.hello.sess, from: state.selfDeviceId, to: resp.from, sigD: b64u(sigD), fin: b64u(fin) };
44583
+ const session = await deriveSession(rk, "initiator", resp.from, state.hello.caps);
44584
+ return { confirm: confirm2, session };
44585
+ }
44586
+ async function respondHandshake(opts) {
44587
+ const { hello } = opts;
44588
+ if (hello.to !== opts.selfDeviceId)
44589
+ throw new Error("e2e: hello addressed to a different device");
44590
+ const th = await helloTranscript(hello);
44591
+ const eph = await generateEphemeral();
44592
+ const nH = b64u(randomBytes(16));
44593
+ const sigH = await sign(opts.identityPriv, signedBytes(th, hello.eD, b64u(eph.publicRaw), hello.nD, nH, opts.selfDeviceId, "resp"));
44594
+ const resp = { t: "e2e:resp", v: 1, sess: hello.sess, from: opts.selfDeviceId, to: hello.from, eH: b64u(eph.publicRaw), nH, sigH: b64u(sigH) };
44595
+ const z2 = await ecdh(eph.privateKey, unb64u(hello.eD));
44596
+ const rk = await hkdf(z2, concat(unb64u(hello.nD), unb64u(nH)), th, 32);
44597
+ return { resp, pending: { hello, th, rk, resp, selfDeviceId: opts.selfDeviceId } };
44598
+ }
44599
+ async function finishResponder(pending, confirm2, peerIdentityPub) {
44600
+ const okD = await verify(peerIdentityPub, signedBytes(pending.th, pending.hello.eD, pending.resp.eH, pending.hello.nD, pending.resp.nH, confirm2.from, "confirm"), unb64u(confirm2.sigD));
44601
+ if (!okD)
44602
+ throw new Error("e2e: driver identity signature did not verify");
44603
+ try {
44604
+ await aesOpen(await confirmKey(pending.rk), ZERO_NONCE, unb64u(confirm2.fin), pending.th);
44605
+ } catch {
44606
+ throw new Error("e2e: key confirmation failed");
44607
+ }
44608
+ return deriveSession(pending.rk, "responder", confirm2.from, pending.hello.caps);
44609
+ }
44610
+
44611
+ // ../../packages/e2e/dist/session.js
44612
+ var E2ESessions = class {
44613
+ cfg;
44614
+ map = /* @__PURE__ */ new Map();
44615
+ constructor(cfg2) {
44616
+ this.cfg = cfg2;
44617
+ }
44618
+ has(sessionId) {
44619
+ return !!this.map.get(sessionId)?.session;
44620
+ }
44621
+ getSession(sessionId) {
44622
+ return this.map.get(sessionId)?.session;
44623
+ }
44624
+ /** Tear a session down (decrypt failure / driver gone). Rejects a pending handshake. */
44625
+ drop(sessionId) {
44626
+ const e = this.map.get(sessionId);
44627
+ if (e?.reject && !e.session)
44628
+ e.reject(new Error("e2e: session dropped"));
44629
+ this.map.delete(sessionId);
44630
+ }
44631
+ /** Initiator: ensure an established session for `sessionId` to `peerDeviceId`. Idempotent —
44632
+ * concurrent callers share the in-flight handshake. */
44633
+ async ensure(sessionId, peerDeviceId) {
44634
+ const existing = this.map.get(sessionId);
44635
+ if (existing?.session)
44636
+ return existing.session;
44637
+ if (existing?.ready)
44638
+ return existing.ready;
44639
+ const entry = {};
44640
+ entry.ready = new Promise((res, rej) => {
44641
+ entry.resolve = res;
44642
+ entry.reject = rej;
44643
+ });
44644
+ this.map.set(sessionId, entry);
44645
+ try {
44646
+ const { hello, state } = await startHandshake({
44647
+ sess: sessionId,
44648
+ selfDeviceId: this.cfg.selfDeviceId,
44649
+ peerDeviceId,
44650
+ identityPriv: this.cfg.identityPriv,
44651
+ caps: this.cfg.caps
44652
+ });
44653
+ entry.initState = state;
44654
+ this.cfg.sendHandshake(hello);
44655
+ } catch (e) {
44656
+ entry.reject?.(e);
44657
+ this.map.delete(sessionId);
44658
+ }
44659
+ return entry.ready;
44660
+ }
44661
+ /** Feed an inbound handshake frame. Throws never (handshake failure rejects the
44662
+ * pending ensure() / drops the responder entry — callers fall back to plaintext or
44663
+ * surface the error). */
44664
+ async onHandshake(msg) {
44665
+ try {
44666
+ if (msg.t === "e2e:hello")
44667
+ return await this.onHello(msg);
44668
+ if (msg.t === "e2e:resp")
44669
+ return await this.onResp(msg);
44670
+ if (msg.t === "e2e:confirm")
44671
+ return await this.onConfirm(msg);
44672
+ } catch {
44673
+ }
44674
+ }
44675
+ async onHello(hello) {
44676
+ const { resp, pending } = await respondHandshake({ hello, selfDeviceId: this.cfg.selfDeviceId, identityPriv: this.cfg.identityPriv });
44677
+ this.map.set(hello.sess, { pending });
44678
+ this.cfg.sendHandshake(resp);
44679
+ }
44680
+ async onResp(resp) {
44681
+ const entry = this.map.get(resp.sess);
44682
+ if (!entry?.initState)
44683
+ return;
44684
+ const peerPub = await this.cfg.getPeerIdentityPub(resp.from);
44685
+ if (!peerPub) {
44686
+ entry.reject?.(new Error("e2e: no identity key for peer " + resp.from));
44687
+ this.map.delete(resp.sess);
44688
+ return;
44689
+ }
44690
+ try {
44691
+ const { confirm: confirm2, session } = await finishInitiator(entry.initState, resp, peerPub);
44692
+ entry.session = session;
44693
+ entry.initState = void 0;
44694
+ this.cfg.sendHandshake(confirm2);
44695
+ entry.resolve?.(session);
44696
+ } catch (e) {
44697
+ entry.reject?.(e);
44698
+ this.map.delete(resp.sess);
44699
+ }
44700
+ }
44701
+ async onConfirm(confirm2) {
44702
+ const entry = this.map.get(confirm2.sess);
44703
+ if (!entry?.pending)
44704
+ return;
44705
+ const peerPub = await this.cfg.getPeerIdentityPub(confirm2.from);
44706
+ if (!peerPub) {
44707
+ this.map.delete(confirm2.sess);
44708
+ return;
44709
+ }
44710
+ try {
44711
+ entry.session = await finishResponder(entry.pending, confirm2, peerPub);
44712
+ entry.pending = void 0;
44713
+ } catch {
44714
+ this.map.delete(confirm2.sess);
44715
+ }
44716
+ }
44717
+ /** Seal a payload for an established session, or null if none is established yet. */
44718
+ async seal(sessionId, routing, payload) {
44719
+ const s = this.map.get(sessionId)?.session;
44720
+ return s ? s.sealer.sealJson(routing, payload) : null;
44721
+ }
44722
+ /** Open a sealed record. Throws if no session or on auth failure (caller drops + rehandshakes). */
44723
+ async open(sessionId, routing, rec) {
44724
+ const s = this.map.get(sessionId)?.session;
44725
+ if (!s)
44726
+ throw new Error("e2e: no session for " + sessionId);
44727
+ return s.opener.openJson(rec, routing);
44728
+ }
44729
+ };
43993
44730
 
43994
44731
  // ../../packages/host-core/dist/daemon-agent.js
43995
44732
  import { join as join18 } from "node:path";
@@ -44056,8 +44793,16 @@ async function createClaudeCodeBackend(send2, onBrowserRequest, loadPluginRuntim
44056
44793
  }
44057
44794
 
44058
44795
  // ../../packages/host-core/dist/engines/cli-backend-base.js
44059
- import { spawn as spawn2 } from "node:child_process";
44796
+ init_dist4();
44797
+ import { spawn as spawn3 } from "node:child_process";
44060
44798
  import { randomUUID as randomUUID3 } from "node:crypto";
44799
+ var TESSERACT_INTERNAL_SECRET = /^(CCB_JWT_SECRET|JWT_SECRET|TESSERACT_TOKEN|CCB_RELAY_SECRET|TURN_SECRET|LIVEKIT_API_SECRET)$/;
44800
+ function scrubInternalSecrets(env) {
44801
+ for (const k of Object.keys(env))
44802
+ if (TESSERACT_INTERNAL_SECRET.test(k))
44803
+ delete env[k];
44804
+ return env;
44805
+ }
44061
44806
  var MAX_LIVE = 8;
44062
44807
  var CliAgentBackend = class {
44063
44808
  adapter;
@@ -44098,12 +44843,17 @@ var CliAgentBackend = class {
44098
44843
  emitMsg({ type: "session:created", sessionId, clientRequestId: params.clientRequestId });
44099
44844
  emitMsg({ type: "agent:status", sessionId, clientRequestId: params.clientRequestId, status: "running" });
44100
44845
  const args = this.adapter.buildArgs(params, ctx);
44101
- const child = spawn2(spawnCfg.command, args, {
44846
+ const child = spawn3(spawnCfg.command, args, {
44102
44847
  cwd: params.projectDir,
44103
44848
  // engineEnv carries the resolved runner env + enabled-skill secrets (the desktop
44104
44849
  // resolves them into the 0600 snapshot); merge so CLI engines get them too.
44105
- env: { ...process.env, ...spawnCfg.env, ...params.engineEnv ?? {} },
44106
- stdio: ["ignore", "pipe", "pipe"]
44850
+ // scrubInternalSecrets drops Tesseract's own control-plane secrets (provider
44851
+ // keys are kept — the engine needs them).
44852
+ env: scrubInternalSecrets({ ...process.env, ...spawnCfg.env, ...params.engineEnv ?? {} }),
44853
+ stdio: ["ignore", "pipe", "pipe"],
44854
+ // Own process group (POSIX) so Stop can SIGKILL the whole tree, not just the
44855
+ // CLI's PID — these engines spawn their own tool subprocesses too.
44856
+ detached: process.platform !== "win32"
44107
44857
  });
44108
44858
  const turn = { child, sessionId, clientRequestId: params.clientRequestId, messageId: ctx.messageId, acc: "", buf: "", errored: false, interrupted: false };
44109
44859
  this.live.set(sessionId, turn);
@@ -44151,6 +44901,10 @@ var CliAgentBackend = class {
44151
44901
  emitMsg({ type: "agent:status", sessionId, clientRequestId: turn.clientRequestId, status: "error" });
44152
44902
  });
44153
44903
  child.on("close", (code) => {
44904
+ if (turn.killTimer) {
44905
+ clearTimeout(turn.killTimer);
44906
+ turn.killTimer = void 0;
44907
+ }
44154
44908
  if (this.adapter.streaming) {
44155
44909
  if (turn.buf.trim())
44156
44910
  handleLine(turn.buf);
@@ -44180,10 +44934,14 @@ var CliAgentBackend = class {
44180
44934
  if (!turn)
44181
44935
  return false;
44182
44936
  turn.interrupted = true;
44183
- try {
44184
- turn.child.kill("SIGTERM");
44185
- } catch {
44186
- }
44937
+ killProcessTree(turn.child, "SIGTERM");
44938
+ if (turn.killTimer)
44939
+ clearTimeout(turn.killTimer);
44940
+ turn.killTimer = setTimeout(() => {
44941
+ turn.killTimer = void 0;
44942
+ if (this.live.has(sessionId))
44943
+ killProcessTree(turn.child, "SIGKILL");
44944
+ }, 1200);
44187
44945
  return true;
44188
44946
  }
44189
44947
  // CLI engines don't expose a resumable on-disk session list to us in v1, so the
@@ -44200,10 +44958,11 @@ var CliAgentBackend = class {
44200
44958
  if (level !== "critical")
44201
44959
  return;
44202
44960
  for (const [id, turn] of this.live) {
44203
- try {
44204
- turn.child.kill("SIGTERM");
44205
- } catch {
44961
+ if (turn.killTimer) {
44962
+ clearTimeout(turn.killTimer);
44963
+ turn.killTimer = void 0;
44206
44964
  }
44965
+ killProcessTree(turn.child, "SIGKILL");
44207
44966
  this.live.delete(id);
44208
44967
  }
44209
44968
  }
@@ -44432,7 +45191,7 @@ init_dist4();
44432
45191
  // ../../packages/host-core/dist/plugin-tools.js
44433
45192
  import { existsSync as existsSync7, readdirSync } from "node:fs";
44434
45193
  import { join as join16 } from "node:path";
44435
- import { homedir as homedir8 } from "node:os";
45194
+ import { homedir as homedir9 } from "node:os";
44436
45195
 
44437
45196
  // ../../packages/plugin-host/dist/discovery.js
44438
45197
  import fs2 from "node:fs";
@@ -44767,9 +45526,9 @@ import { join as join15 } from "node:path";
44767
45526
  // ../../packages/host-core/dist/credentials.js
44768
45527
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync3, renameSync as renameSync3, chmodSync } from "node:fs";
44769
45528
  import { join as join10 } from "node:path";
44770
- import { homedir as homedir4 } from "node:os";
45529
+ import { homedir as homedir5 } from "node:os";
44771
45530
  function daemonDir() {
44772
- return process.env.CCB_DAEMON_DIR ?? join10(homedir4(), ".tesseract", "daemon");
45531
+ return process.env.CCB_DAEMON_DIR ?? join10(homedir5(), ".tesseract", "daemon");
44773
45532
  }
44774
45533
  function credentialsPath() {
44775
45534
  return join10(daemonDir(), "credentials.json");
@@ -44801,24 +45560,24 @@ function writeCredentials(c) {
44801
45560
  // ../../packages/host-core/dist/runners/resolve.js
44802
45561
  init_dist2();
44803
45562
  import { readFileSync as readFileSync5, existsSync as existsSync5, statSync, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
44804
- import { homedir as homedir6 } from "node:os";
45563
+ import { homedir as homedir7 } from "node:os";
44805
45564
  import { join as join12 } from "node:path";
44806
45565
  import { createHash as createHash2 } from "node:crypto";
44807
45566
 
44808
45567
  // ../../packages/host-core/dist/runners/proxy-router.js
44809
45568
  init_dist2();
44810
- import { spawn as spawn3, execFile as execFile2, execSync as execSync2 } from "node:child_process";
45569
+ import { spawn as spawn4, execFile as execFile2, execSync as execSync2 } from "node:child_process";
44811
45570
  import { createRequire as createRequire2 } from "node:module";
44812
45571
  import { randomUUID as randomUUID4, createHash } from "node:crypto";
44813
45572
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
44814
- import { homedir as homedir5 } from "node:os";
45573
+ import { homedir as homedir6 } from "node:os";
44815
45574
  import { join as join11, dirname as dirname6, sep as sep3 } from "node:path";
44816
45575
  import { request as httpRequest } from "node:http";
44817
45576
  var requireFromHere = createRequire2(import.meta.url);
44818
45577
  var ROUTER_PORT = 8787;
44819
45578
  var BIND_HOST = "127.0.0.1";
44820
45579
  var ROUTER_TOKEN = randomUUID4();
44821
- var routerHomeDir = join11(homedir5(), ".tesseract", "router-home");
45580
+ var routerHomeDir = join11(homedir6(), ".tesseract", "router-home");
44822
45581
  var lastConfigHash = "";
44823
45582
  var running = false;
44824
45583
  var starting = null;
@@ -44963,7 +45722,7 @@ async function restart() {
44963
45722
  await ccr(["stop"]).catch(() => void 0);
44964
45723
  try {
44965
45724
  const { exe, asNode } = nodeExe();
44966
- const child = spawn3(exe, [ccrCliPath(), "start"], { env: ccrEnv(asNode), detached: true, stdio: "ignore" });
45725
+ const child = spawn4(exe, [ccrCliPath(), "start"], { env: ccrEnv(asNode), detached: true, stdio: "ignore" });
44967
45726
  child.unref();
44968
45727
  } catch {
44969
45728
  running = false;
@@ -44989,7 +45748,7 @@ function routerEndpointSync(desired) {
44989
45748
  }
44990
45749
 
44991
45750
  // ../../packages/host-core/dist/runners/resolve.js
44992
- var runnerHomeBase = join12(homedir6(), ".tesseract", "runner-home");
45751
+ var runnerHomeBase = join12(homedir7(), ".tesseract", "runner-home");
44993
45752
  function materializeClaudeAuthHome(contents) {
44994
45753
  const id = createHash2("sha256").update(contents).digest("hex").slice(0, 16);
44995
45754
  const home = join12(runnerHomeBase, id);
@@ -45045,7 +45804,7 @@ function resolveRunnerEnv(spec) {
45045
45804
  return { ANTHROPIC_BASE_URL: endpoint.baseUrl, ANTHROPIC_AUTH_TOKEN: endpoint.authToken, ANTHROPIC_API_KEY: "" };
45046
45805
  }
45047
45806
  function overridePath() {
45048
- return join12(process.env.CCB_TESSERACT_DIR ?? join12(homedir6(), ".tesseract"), "runners.json");
45807
+ return join12(process.env.CCB_TESSERACT_DIR ?? join12(homedir7(), ".tesseract"), "runners.json");
45049
45808
  }
45050
45809
  var cache2 = null;
45051
45810
  var cachedMtime = -1;
@@ -45274,7 +46033,7 @@ function setActiveConfig(id) {
45274
46033
  // ../../packages/host-core/dist/daemon-engine-spawn.js
45275
46034
  init_dist2();
45276
46035
  import { execFileSync } from "node:child_process";
45277
- import { homedir as homedir7 } from "node:os";
46036
+ import { homedir as homedir8 } from "node:os";
45278
46037
  import { join as join14 } from "node:path";
45279
46038
  function envKey(id) {
45280
46039
  return id.toUpperCase().replace(/[^A-Z0-9]/g, "_");
@@ -45286,10 +46045,10 @@ function augmentedPath() {
45286
46045
  "/opt/homebrew/bin",
45287
46046
  "/usr/bin",
45288
46047
  "/bin",
45289
- join14(homedir7(), ".local", "bin"),
45290
- join14(homedir7(), "bin"),
45291
- join14(homedir7(), ".bun", "bin"),
45292
- join14(homedir7(), ".npm-global", "bin")
46048
+ join14(homedir8(), ".local", "bin"),
46049
+ join14(homedir8(), "bin"),
46050
+ join14(homedir8(), ".bun", "bin"),
46051
+ join14(homedir8(), ".npm-global", "bin")
45293
46052
  ];
45294
46053
  const seen = /* @__PURE__ */ new Set();
45295
46054
  const parts = [];
@@ -45432,7 +46191,7 @@ function buildDaemonPluginLoader(pluginsDir) {
45432
46191
  };
45433
46192
  }
45434
46193
  function daemonSkillsPluginConfig() {
45435
- const root = readDaemonAgentSettings().skillsRoot ?? join16(process.env.CCB_TESSERACT_DIR ?? join16(homedir8(), ".tesseract"), "extensions-skills");
46194
+ const root = readDaemonAgentSettings().skillsRoot ?? join16(process.env.CCB_TESSERACT_DIR ?? join16(homedir9(), ".tesseract"), "extensions-skills");
45436
46195
  if (!existsSync7(join16(root, ".claude-plugin", "plugin.json")))
45437
46196
  return null;
45438
46197
  const enabled = join16(root, "skills");
@@ -45728,7 +46487,7 @@ async function closeEnginePage(browserId) {
45728
46487
  // ../../packages/host-core/dist/image-gen-runner.js
45729
46488
  import { randomUUID as randomUUID6 } from "node:crypto";
45730
46489
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8, readFileSync as readFileSync8 } from "node:fs";
45731
- import { homedir as homedir9 } from "node:os";
46490
+ import { homedir as homedir10 } from "node:os";
45732
46491
  import { join as join17 } from "node:path";
45733
46492
 
45734
46493
  // ../../packages/image-edit-core/dist/types.js
@@ -45945,7 +46704,7 @@ function createImageEditProvider(id, cfg2) {
45945
46704
 
45946
46705
  // ../../packages/host-core/dist/image-gen-runner.js
45947
46706
  function generatedImagesDir() {
45948
- const home = process.env.CCB_TESSERACT_DIR ?? join17(homedir9(), ".tesseract");
46707
+ const home = process.env.CCB_TESSERACT_DIR ?? join17(homedir10(), ".tesseract");
45949
46708
  return join17(home, "generated-images");
45950
46709
  }
45951
46710
  function creds() {
@@ -46129,12 +46888,143 @@ function refreshDaemonScheduling(b) {
46129
46888
  }
46130
46889
 
46131
46890
  // ../../packages/host-core/dist/fs-remote.js
46132
- import { readdir as readdir2, readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5, cp, rm, rename as renameAsync, stat as stat2 } from "node:fs/promises";
46133
- import { openSync as openSync2, readSync, closeSync as closeSync2, statSync as statSync3, existsSync as existsSync8, watch } from "node:fs";
46134
- import { basename as basename2, dirname as dirname7, join as join19, relative as relative4, resolve as resolve5, sep as sep4, isAbsolute as isAbsolute4 } from "node:path";
46891
+ import { readdir as readdir3, readFile as readFile8, writeFile as writeFile5, mkdir as mkdir5, cp, rm, rename as renameAsync, stat as stat2 } from "node:fs/promises";
46892
+ import { openSync as openSync2, readSync, closeSync as closeSync2, statSync as statSync3, existsSync as existsSync8, watch, realpathSync as realpathSync2, constants as fsConstants } from "node:fs";
46893
+ import { basename as basename2, dirname as dirname7, join as join20, relative as relative4, resolve as resolve5, sep as sep4, isAbsolute as isAbsolute4 } from "node:path";
46135
46894
  import { execFile as execFile3 } from "node:child_process";
46136
46895
  import { promisify as promisify2 } from "node:util";
46896
+
46897
+ // ../../packages/host-core/dist/fs-stats.js
46898
+ init_dist2();
46899
+ import { readdir as readdir2, lstat, readFile as readFile7 } from "node:fs/promises";
46900
+ import { join as join19 } from "node:path";
46901
+ var TEXT_CAP = 5 * 1024 * 1024;
46902
+ async function dirStats(root, opts) {
46903
+ const start2 = Date.now();
46904
+ const MAX_ENTRIES = 2e5, TIME_MS = 1e4;
46905
+ let fileCount = 0, dirCount = 0, totalBytes = 0, visited = 0, truncated = false;
46906
+ const stack = [root];
46907
+ while (stack.length) {
46908
+ if (visited >= MAX_ENTRIES || Date.now() - start2 > TIME_MS) {
46909
+ truncated = true;
46910
+ break;
46911
+ }
46912
+ const dir = stack.pop();
46913
+ let ents;
46914
+ try {
46915
+ ents = await readdir2(dir, { withFileTypes: true });
46916
+ } catch {
46917
+ continue;
46918
+ }
46919
+ for (const e of ents) {
46920
+ if (visited >= MAX_ENTRIES || Date.now() - start2 > TIME_MS) {
46921
+ truncated = true;
46922
+ break;
46923
+ }
46924
+ if (STATS_SKIP_FILES.has(e.name))
46925
+ continue;
46926
+ visited++;
46927
+ if (e.isDirectory()) {
46928
+ if (opts?.excludeHeavy && STATS_HEAVY_DIRS.has(e.name))
46929
+ continue;
46930
+ dirCount++;
46931
+ stack.push(join19(dir, e.name));
46932
+ } else if (e.isFile()) {
46933
+ fileCount++;
46934
+ try {
46935
+ totalBytes += (await lstat(join19(dir, e.name))).size;
46936
+ } catch {
46937
+ }
46938
+ }
46939
+ }
46940
+ }
46941
+ return { fileCount, dirCount, totalBytes, truncated };
46942
+ }
46943
+ async function codeStats(root) {
46944
+ const start2 = Date.now();
46945
+ const MAX_FILES = 25e3, MAX_BYTES = 256 * 1024 * 1024, TIME_MS = 12e3;
46946
+ const byLang = /* @__PURE__ */ new Map();
46947
+ let totalFiles = 0, totalLines = 0, bytes = 0, truncated = false;
46948
+ const stack = [root];
46949
+ while (stack.length) {
46950
+ if (totalFiles >= MAX_FILES || bytes >= MAX_BYTES || Date.now() - start2 > TIME_MS) {
46951
+ truncated = true;
46952
+ break;
46953
+ }
46954
+ const dir = stack.pop();
46955
+ let ents;
46956
+ try {
46957
+ ents = await readdir2(dir, { withFileTypes: true });
46958
+ } catch {
46959
+ continue;
46960
+ }
46961
+ for (const e of ents) {
46962
+ if (totalFiles >= MAX_FILES || bytes >= MAX_BYTES || Date.now() - start2 > TIME_MS) {
46963
+ truncated = true;
46964
+ break;
46965
+ }
46966
+ if (STATS_SKIP_FILES.has(e.name))
46967
+ continue;
46968
+ if (e.isDirectory()) {
46969
+ if (!STATS_HEAVY_DIRS.has(e.name))
46970
+ stack.push(join19(dir, e.name));
46971
+ continue;
46972
+ }
46973
+ if (!e.isFile())
46974
+ continue;
46975
+ const lang = langForFilename(e.name);
46976
+ if (!lang)
46977
+ continue;
46978
+ const full = join19(dir, e.name);
46979
+ let size = 0;
46980
+ try {
46981
+ size = (await lstat(full)).size;
46982
+ } catch {
46983
+ continue;
46984
+ }
46985
+ const row = byLang.get(lang) ?? { files: 0, lines: 0 };
46986
+ row.files++;
46987
+ totalFiles++;
46988
+ if (size <= TEXT_CAP) {
46989
+ try {
46990
+ const txt = await readFile7(full, "utf8");
46991
+ bytes += size;
46992
+ let lines = 0;
46993
+ for (let i = 0; i < txt.length; i++)
46994
+ if (txt.charCodeAt(i) === 10)
46995
+ lines++;
46996
+ if (txt.length && txt.charCodeAt(txt.length - 1) !== 10)
46997
+ lines++;
46998
+ row.lines += lines;
46999
+ totalLines += lines;
47000
+ } catch {
47001
+ }
47002
+ }
47003
+ byLang.set(lang, row);
47004
+ }
47005
+ }
47006
+ const byLangSorted = [...byLang.entries()].map(([lang, v]) => ({ lang, files: v.files, lines: v.lines })).sort((a, b) => b.lines - a.lines || b.files - a.files);
47007
+ return { byLang: byLangSorted, totalFiles, totalLines, truncated };
47008
+ }
47009
+
47010
+ // ../../packages/host-core/dist/fs-remote.js
46137
47011
  var execFileAsync = promisify2(execFile3);
47012
+ function insideOk(p) {
47013
+ const c = resolveContainmentRoot();
47014
+ return c ? isInsideRoot(p, c) : true;
47015
+ }
47016
+ function safeContentPath(p) {
47017
+ const c = resolveContainmentRoot();
47018
+ if (c)
47019
+ return safeResolve(p, c);
47020
+ try {
47021
+ return realpathSync2.native(p);
47022
+ } catch {
47023
+ return p;
47024
+ }
47025
+ }
47026
+ var O_NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
47027
+ var CREATE_EXCL_NOFOLLOW = fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | O_NOFOLLOW;
46138
47028
  var LISTALL_SKIP_DIRS = /* @__PURE__ */ new Set([
46139
47029
  ".git",
46140
47030
  ".hg",
@@ -46169,7 +47059,7 @@ async function listAllFilesUnder(dir) {
46169
47059
  return;
46170
47060
  let entries;
46171
47061
  try {
46172
- entries = await readdir2(d, { withFileTypes: true });
47062
+ entries = await readdir3(d, { withFileTypes: true });
46173
47063
  } catch {
46174
47064
  return;
46175
47065
  }
@@ -46178,7 +47068,7 @@ async function listAllFilesUnder(dir) {
46178
47068
  return;
46179
47069
  if (ent.name === ".DS_Store")
46180
47070
  continue;
46181
- const full = join19(d, ent.name);
47071
+ const full = join20(d, ent.name);
46182
47072
  if (ent.isDirectory()) {
46183
47073
  if (!LISTALL_SKIP_DIRS.has(ent.name))
46184
47074
  await walk(full);
@@ -46294,76 +47184,107 @@ function resolveParent(raw, root) {
46294
47184
  if (raw === "" || raw === "~")
46295
47185
  return root;
46296
47186
  if (raw.startsWith("~/"))
46297
- return join19(root, raw.slice(2));
46298
- return isAbsolute4(raw) ? raw : join19(root, raw);
47187
+ return join20(root, raw.slice(2));
47188
+ return isAbsolute4(raw) ? raw : join20(root, raw);
46299
47189
  }
46300
47190
  function resolveUniqueName(dir, name) {
46301
- const direct = join19(dir, name);
47191
+ const direct = join20(dir, name);
46302
47192
  if (!existsSync8(direct))
46303
47193
  return direct;
46304
47194
  const dot = name.lastIndexOf(".");
46305
47195
  const base = dot > 0 ? name.slice(0, dot) : name;
46306
47196
  const ext = dot > 0 ? name.slice(dot) : "";
46307
47197
  for (let i = 2; i < 1e4; i++) {
46308
- const candidate = join19(dir, `${base} ${i}${ext}`);
47198
+ const candidate = join20(dir, `${base} ${i}${ext}`);
46309
47199
  if (!existsSync8(candidate))
46310
47200
  return candidate;
46311
47201
  }
46312
47202
  return direct;
46313
47203
  }
47204
+ var RL_WINDOW_MS = 1e4;
47205
+ var RL_MAX_OPS = 400;
47206
+ var RL_MAX_WRITE_BYTES = 256 * 1024 * 1024;
47207
+ var rlBuckets = /* @__PURE__ */ new Map();
47208
+ var WRITE_OPS = /* @__PURE__ */ new Set(["writeFile", "writeBinary", "createFile", "createProject"]);
47209
+ var HEAVY_OPS = /* @__PURE__ */ new Set(["dirStats", "codeStats"]);
47210
+ var HEAVY_OP_COST = 25;
47211
+ function withinRate(key2, op, bytes) {
47212
+ const now = Date.now();
47213
+ let b = rlBuckets.get(key2);
47214
+ if (!b || now >= b.resetAt) {
47215
+ b = { resetAt: now + RL_WINDOW_MS, ops: 0, writeBytes: 0 };
47216
+ rlBuckets.set(key2, b);
47217
+ }
47218
+ b.ops += HEAVY_OPS.has(op) ? HEAVY_OP_COST : 1;
47219
+ if (WRITE_OPS.has(op))
47220
+ b.writeBytes += bytes;
47221
+ return b.ops <= RL_MAX_OPS && b.writeBytes <= RL_MAX_WRITE_BYTES;
47222
+ }
47223
+ function releaseFsClient(key2) {
47224
+ rlBuckets.delete(key2);
47225
+ }
46314
47226
  var OUTSIDE = (verb) => ({ ok: false, error: `Refusing to ${verb} outside the shared root.` });
46315
47227
  var validName = (n) => !!n && !n.includes("/") && !n.includes("\\") && n !== "." && n !== "..";
46316
47228
  async function readRemoteBytes(root, path5) {
46317
- if (!isInsideRoot(path5, root))
47229
+ const real = safeContentPath(path5);
47230
+ if (!real)
46318
47231
  return { error: "Path is outside the shared root." };
46319
47232
  try {
46320
- const s = statSync3(path5);
47233
+ const s = statSync3(real);
46321
47234
  if (!s.isFile())
46322
47235
  return { error: "Not a file" };
46323
47236
  if (s.size > REMOTE_READ_CAP)
46324
47237
  return { error: `File is too large to read whole over the network (${(s.size / 1024 / 1024).toFixed(1)} MB) \u2014 open it locally.` };
46325
- return { bytes: await readFile7(path5), size: s.size };
47238
+ return { bytes: await readFile8(real), size: s.size };
46326
47239
  } catch (err) {
46327
47240
  return { error: String(err.message) };
46328
47241
  }
46329
47242
  }
46330
- async function runRemoteFsOp(root, op, args) {
47243
+ async function runRemoteFsOp(root, op, args, quotaKey) {
46331
47244
  const a = args ?? [];
47245
+ if (quotaKey) {
47246
+ const bytes = op === "writeFile" ? Buffer.byteLength(String(a[1] ?? "")) : op === "writeBinary" ? Math.floor(String(a[1] ?? "").length * 0.75) : 0;
47247
+ if (!withinRate(quotaKey, op, bytes))
47248
+ throw new Error("Too many filesystem requests \u2014 please slow down.");
47249
+ }
46332
47250
  switch (op) {
46333
47251
  case "readDir": {
46334
47252
  const dir = String(a[0] ?? root);
46335
- if (!isInsideRoot(dir, root))
47253
+ const real = safeContentPath(dir);
47254
+ if (!real)
46336
47255
  return [];
46337
47256
  try {
46338
- const entries = (await readdir2(dir, { withFileTypes: true })).filter((e) => e.name !== ".DS_Store");
46339
- const ignored = await gitIgnoredNames(dir, entries.map((e) => e.name));
46340
- return entries.map((e) => ({ name: e.name, path: join19(dir, e.name), isDir: e.isDirectory(), ignored: ignored.has(e.name) })).sort((x, y) => x.isDir === y.isDir ? x.name.localeCompare(y.name) : x.isDir ? -1 : 1);
47257
+ const entries = (await readdir3(real, { withFileTypes: true })).filter((e) => e.name !== ".DS_Store");
47258
+ const ignored = await gitIgnoredNames(real, entries.map((e) => e.name));
47259
+ return entries.map((e) => ({ name: e.name, path: join20(dir, e.name), isDir: e.isDirectory(), ignored: ignored.has(e.name) })).sort((x, y) => x.isDir === y.isDir ? x.name.localeCompare(y.name) : x.isDir ? -1 : 1);
46341
47260
  } catch {
46342
47261
  return [];
46343
47262
  }
46344
47263
  }
46345
47264
  case "listAllFiles": {
46346
47265
  const dir = String(a[0] ?? root);
46347
- if (!isInsideRoot(dir, root))
47266
+ const real = safeContentPath(dir);
47267
+ if (!real)
46348
47268
  return [];
46349
47269
  try {
46350
- return await listAllFilesUnder(dir);
47270
+ return await listAllFilesUnder(real);
46351
47271
  } catch {
46352
47272
  return [];
46353
47273
  }
46354
47274
  }
46355
47275
  case "readFile": {
46356
47276
  const p = String(a[0] ?? "");
46357
- if (!isInsideRoot(p, root))
47277
+ const real = safeContentPath(p);
47278
+ if (!real)
46358
47279
  return { error: "Path is outside the shared root." };
46359
47280
  const ext = p.split(".").pop()?.toLowerCase() ?? "";
46360
47281
  if (BINARY_EXT.has(ext))
46361
47282
  return { error: "Binary file \u2014 not shown in the editor." };
46362
47283
  try {
46363
- const s = statSync3(p);
47284
+ const s = statSync3(real);
46364
47285
  if (s.size > TEXT_SIZE_LIMIT)
46365
47286
  return { error: "File too large for text view." };
46366
- return { content: await readFile7(p, "utf-8") };
47287
+ return { content: await readFile8(real, "utf-8") };
46367
47288
  } catch (err) {
46368
47289
  return { error: String(err.message) };
46369
47290
  }
@@ -46371,11 +47292,12 @@ async function runRemoteFsOp(root, op, args) {
46371
47292
  case "writeFile": {
46372
47293
  const p = String(a[0] ?? "");
46373
47294
  const content = String(a[1] ?? "");
46374
- if (!isInsideRoot(p, root))
47295
+ const real = safeContentPath(p);
47296
+ if (!real)
46375
47297
  return OUTSIDE("write");
46376
47298
  try {
46377
- await mkdir5(dirname7(p), { recursive: true });
46378
- await writeFile5(p, content, "utf-8");
47299
+ await mkdir5(dirname7(real), { recursive: true });
47300
+ await writeFile5(real, content, "utf-8");
46379
47301
  return { ok: true };
46380
47302
  } catch (err) {
46381
47303
  return { ok: false, error: String(err.message) };
@@ -46384,11 +47306,12 @@ async function runRemoteFsOp(root, op, args) {
46384
47306
  case "writeBinary": {
46385
47307
  const p = String(a[0] ?? "");
46386
47308
  const base643 = String(a[1] ?? "");
46387
- if (!isInsideRoot(p, root))
47309
+ const real = safeContentPath(p);
47310
+ if (!real)
46388
47311
  return OUTSIDE("write");
46389
47312
  try {
46390
- await mkdir5(dirname7(p), { recursive: true });
46391
- await writeFile5(p, Buffer.from(base643, "base64"));
47313
+ await mkdir5(dirname7(real), { recursive: true });
47314
+ await writeFile5(real, Buffer.from(base643, "base64"));
46392
47315
  return { ok: true };
46393
47316
  } catch (err) {
46394
47317
  return { ok: false, error: String(err.message) };
@@ -46396,10 +47319,11 @@ async function runRemoteFsOp(root, op, args) {
46396
47319
  }
46397
47320
  case "statFile": {
46398
47321
  const p = String(a[0] ?? "");
46399
- if (!isInsideRoot(p, root))
47322
+ const real = safeContentPath(p);
47323
+ if (!real)
46400
47324
  return { error: "Path is outside the shared root." };
46401
47325
  try {
46402
- const s = statSync3(p);
47326
+ const s = statSync3(real);
46403
47327
  if (!s.isFile())
46404
47328
  return { error: "Not a file" };
46405
47329
  const ext = p.split(".").pop()?.toLowerCase() ?? "";
@@ -46410,7 +47334,8 @@ async function runRemoteFsOp(root, op, args) {
46410
47334
  }
46411
47335
  case "readChunk": {
46412
47336
  const p = String(a[0] ?? "");
46413
- if (!isInsideRoot(p, root))
47337
+ const real = safeContentPath(p);
47338
+ if (!real)
46414
47339
  return { error: "Path is outside the shared root." };
46415
47340
  const off = Math.max(0, Math.floor(Number(a[1]) || 0));
46416
47341
  const len = Math.min(1024 * 1024, Math.max(0, Math.floor(Number(a[2]) || 0)));
@@ -46418,7 +47343,7 @@ async function runRemoteFsOp(root, op, args) {
46418
47343
  return { base64: "", bytesRead: 0 };
46419
47344
  let fd = null;
46420
47345
  try {
46421
- fd = openSync2(p, "r");
47346
+ fd = openSync2(real, "r");
46422
47347
  const buf = Buffer.alloc(len);
46423
47348
  const bytesRead = readSync(fd, buf, 0, len, off);
46424
47349
  return { base64: buf.subarray(0, bytesRead).toString("base64"), bytesRead };
@@ -46434,13 +47359,14 @@ async function runRemoteFsOp(root, op, args) {
46434
47359
  }
46435
47360
  case "readBinary": {
46436
47361
  const p = String(a[0] ?? "");
46437
- if (!isInsideRoot(p, root))
47362
+ const real = safeContentPath(p);
47363
+ if (!real)
46438
47364
  return { error: "Path is outside the shared root." };
46439
47365
  try {
46440
- const s = statSync3(p);
47366
+ const s = statSync3(real);
46441
47367
  if (s.size > REMOTE_READ_CAP)
46442
47368
  return { error: `File is too large to read whole over the network (${(s.size / 1024 / 1024).toFixed(1)} MB) \u2014 open it locally.` };
46443
- return { base64: (await readFile7(p)).toString("base64"), size: s.size };
47369
+ return { base64: (await readFile8(real)).toString("base64"), size: s.size };
46444
47370
  } catch (err) {
46445
47371
  return { error: String(err.message) };
46446
47372
  }
@@ -46450,8 +47376,8 @@ async function runRemoteFsOp(root, op, args) {
46450
47376
  const name = String(a[1] ?? "").trim();
46451
47377
  if (!validName(name))
46452
47378
  return { ok: false, error: "Invalid project name." };
46453
- const target = join19(parent, name);
46454
- if (!isInsideRoot(target, root))
47379
+ const target = join20(parent, name);
47380
+ if (!insideOk(target))
46455
47381
  return { ok: false, error: "Refusing to create a project outside the shared root." };
46456
47382
  if (existsSync8(target))
46457
47383
  return { ok: false, error: `A folder named "${name}" already exists.` };
@@ -46468,14 +47394,14 @@ async function runRemoteFsOp(root, op, args) {
46468
47394
  const name = String(a[1] ?? "").trim();
46469
47395
  if (!validName(name))
46470
47396
  return { ok: false, error: "Invalid name." };
46471
- if (!isInsideRoot(parent, root))
47397
+ const target = join20(parent, name);
47398
+ if (!insideOk(target))
46472
47399
  return OUTSIDE("create");
46473
- const target = join19(parent, name);
46474
47400
  if (existsSync8(target))
46475
47401
  return { ok: false, error: `"${name}" already exists.` };
46476
47402
  try {
46477
47403
  if (op === "createFile")
46478
- await writeFile5(target, "", "utf-8");
47404
+ await writeFile5(target, "", { encoding: "utf-8", flag: CREATE_EXCL_NOFOLLOW });
46479
47405
  else
46480
47406
  await mkdir5(target);
46481
47407
  return { ok: true, path: target };
@@ -46488,9 +47414,9 @@ async function runRemoteFsOp(root, op, args) {
46488
47414
  const newName = String(a[1] ?? "").trim();
46489
47415
  if (!validName(newName))
46490
47416
  return { ok: false, error: "Invalid name." };
46491
- if (!isInsideRoot(oldPath, root))
47417
+ if (!insideOk(oldPath))
46492
47418
  return OUTSIDE("rename");
46493
- const newPath = join19(dirname7(oldPath), newName);
47419
+ const newPath = join20(dirname7(oldPath), newName);
46494
47420
  if (existsSync8(newPath))
46495
47421
  return { ok: false, error: `A file named "${newName}" already exists.` };
46496
47422
  try {
@@ -46504,7 +47430,7 @@ async function runRemoteFsOp(root, op, args) {
46504
47430
  const p = String(a[0] ?? "");
46505
47431
  if (!p)
46506
47432
  return { ok: false, error: "Bad request" };
46507
- if (!isInsideRoot(p, root))
47433
+ if (!insideOk(p))
46508
47434
  return OUTSIDE("delete");
46509
47435
  if (resolve5(p) === resolve5(root))
46510
47436
  return { ok: false, error: "Refusing to delete the shared root." };
@@ -46519,14 +47445,14 @@ async function runRemoteFsOp(root, op, args) {
46519
47445
  case "movePaths": {
46520
47446
  const srcPaths = Array.isArray(a[0]) ? a[0] : [];
46521
47447
  const destDir = String(a[1] ?? "");
46522
- if (!isInsideRoot(destDir, root))
47448
+ if (!insideOk(destDir))
46523
47449
  return OUTSIDE(op === "copyPaths" ? "copy" : "move");
46524
47450
  try {
46525
47451
  const newPaths = [];
46526
47452
  for (const src of srcPaths) {
46527
47453
  if (typeof src !== "string" || !src)
46528
47454
  continue;
46529
- if (!isInsideRoot(src, root))
47455
+ if (!insideOk(src))
46530
47456
  return OUTSIDE(op === "copyPaths" ? "copy from" : "move from");
46531
47457
  if (op === "movePaths" && dirname7(src) === destDir) {
46532
47458
  newPaths.push(src);
@@ -46545,18 +47471,31 @@ async function runRemoteFsOp(root, op, args) {
46545
47471
  }
46546
47472
  }
46547
47473
  case "openFolder": {
46548
- const p = a[0] != null ? resolveParent(String(a[0]), root) : root;
46549
- if (!isInsideRoot(p, root))
47474
+ const requested = a[0] != null ? resolveParent(String(a[0]), root) : root;
47475
+ const real = safeContentPath(requested);
47476
+ if (!real)
46550
47477
  return { error: "outside_root" };
46551
47478
  try {
46552
- const s = await stat2(p);
47479
+ const s = await stat2(real);
46553
47480
  if (!s.isDirectory())
46554
47481
  return { error: "not_dir" };
46555
- return { root: resolve5(p), name: basename2(p) };
47482
+ return { root: resolve5(real), name: basename2(real) };
46556
47483
  } catch {
46557
47484
  return { error: "not_found" };
46558
47485
  }
46559
47486
  }
47487
+ case "dirStats": {
47488
+ const real = safeContentPath(String(a[0] ?? root));
47489
+ if (!real)
47490
+ return { fileCount: 0, dirCount: 0, totalBytes: 0, truncated: false, error: "outside_root" };
47491
+ return dirStats(real, a[1]);
47492
+ }
47493
+ case "codeStats": {
47494
+ const real = safeContentPath(String(a[0] ?? root));
47495
+ if (!real)
47496
+ return { byLang: [], totalFiles: 0, totalLines: 0, truncated: false, error: "outside_root" };
47497
+ return codeStats(real);
47498
+ }
46560
47499
  case "currentRoot":
46561
47500
  return { root, name: basename2(root) };
46562
47501
  case "homeDir":
@@ -46582,7 +47521,7 @@ function watchRemoteRoot(root, key2, onChange) {
46582
47521
  const rel = String(filename);
46583
47522
  if (rel.split(sep4).some((seg) => IGNORED.has(seg)))
46584
47523
  return;
46585
- pending.add(join19(resolved, rel));
47524
+ pending.add(join20(resolved, rel));
46586
47525
  if (timer)
46587
47526
  clearTimeout(timer);
46588
47527
  timer = setTimeout(() => {
@@ -46766,21 +47705,21 @@ function toNdcIceServers(configs) {
46766
47705
  if (!m)
46767
47706
  continue;
46768
47707
  const scheme = m[1].toLowerCase();
46769
- const hostname5 = m[2];
47708
+ const hostname6 = m[2];
46770
47709
  const port = m[3] ? parseInt(m[3], 10) : scheme.endsWith("s") ? 5349 : 3478;
46771
47710
  if (scheme.startsWith("stun")) {
46772
- out.push({ hostname: hostname5, port });
47711
+ out.push({ hostname: hostname6, port });
46773
47712
  continue;
46774
47713
  }
46775
47714
  const transport = (m[4] ?? "udp").toLowerCase();
46776
47715
  const relayType = scheme === "turns" ? "TurnTls" : transport === "tcp" ? "TurnTcp" : "TurnUdp";
46777
- out.push({ hostname: hostname5, port, username: c.username, password: c.credential, relayType });
47716
+ out.push({ hostname: hostname6, port, username: c.username, password: c.credential, relayType });
46778
47717
  }
46779
47718
  }
46780
47719
  return out;
46781
47720
  }
46782
47721
  var peers = /* @__PURE__ */ new Map();
46783
- var delay = (ms) => new Promise((r) => setTimeout(r, ms));
47722
+ var delay2 = (ms) => new Promise((r) => setTimeout(r, ms));
46784
47723
  var CHUNK = 64 * 1024;
46785
47724
  var MAX_MSG = 256 * 1024;
46786
47725
  var HIGH_WATER = 8 * 1024 * 1024;
@@ -46802,7 +47741,7 @@ async function serveGet(dc, id, path5, root) {
46802
47741
  } catch {
46803
47742
  return;
46804
47743
  }
46805
- const waitDrain = drainers.get(dc) ?? (() => delay(15));
47744
+ const waitDrain = drainers.get(dc) ?? (() => delay2(15));
46806
47745
  for (let off = 0, seq = 0; off < bytes.length; off += CHUNK, seq++) {
46807
47746
  while (dc.isOpen() && dc.bufferedAmount() > HIGH_WATER)
46808
47747
  await waitDrain();
@@ -46946,13 +47885,13 @@ function dynamicMetrics() {
46946
47885
  }
46947
47886
 
46948
47887
  // ../../packages/host-core/dist/fs-transfer.js
46949
- import { readdir as readdir3, lstat, mkdir as mkdir6, chmod, rm as rm2, readlink, symlink, stat as stat3 } from "node:fs/promises";
47888
+ import { readdir as readdir4, lstat as lstat2, mkdir as mkdir6, chmod, rm as rm2, readlink, symlink, stat as stat3 } from "node:fs/promises";
46950
47889
  import { openSync as openSync3, readSync as readSync2, closeSync as closeSync3, existsSync as existsSync9, createWriteStream } from "node:fs";
46951
- import { dirname as dirname9, join as join21, relative as relative6, sep as sep6 } from "node:path";
47890
+ import { dirname as dirname9, join as join22, relative as relative6, sep as sep6 } from "node:path";
46952
47891
 
46953
47892
  // ../../packages/host-core/dist/active-link/paths.js
46954
- import { homedir as homedir10 } from "node:os";
46955
- import { join as join20, relative as relative5, sep as sep5 } from "node:path";
47893
+ import { homedir as homedir11 } from "node:os";
47894
+ import { join as join21, relative as relative5, sep as sep5 } from "node:path";
46956
47895
  function activeLinkEnabled() {
46957
47896
  return process.env.CCB_ACTIVE_LINK !== "0";
46958
47897
  }
@@ -46978,7 +47917,7 @@ function toPosixRel(root, abs) {
46978
47917
  return relative5(root, abs).split(sep5).join("/");
46979
47918
  }
46980
47919
  function fromPosixRel(root, rel) {
46981
- return join20(root, rel.split("/").join(sep5));
47920
+ return join21(root, rel.split("/").join(sep5));
46982
47921
  }
46983
47922
  function isExcludedAbs(abs, root) {
46984
47923
  const rel = toPosixRel(root, abs);
@@ -46987,10 +47926,10 @@ function isExcludedAbs(abs, root) {
46987
47926
  return rel.split("/").some(isExcludedName);
46988
47927
  }
46989
47928
  function activeLinkRoot() {
46990
- return process.env.CCB_ACTIVE_LINK_DIR ?? join20(homedir10(), ".tesseract", "active-link");
47929
+ return process.env.CCB_ACTIVE_LINK_DIR ?? join21(homedir11(), ".tesseract", "active-link");
46991
47930
  }
46992
47931
  function linkStateDir(linkId) {
46993
- return join20(activeLinkRoot(), linkId);
47932
+ return join21(activeLinkRoot(), linkId);
46994
47933
  }
46995
47934
 
46996
47935
  // ../../packages/host-core/dist/fs-transfer.js
@@ -47003,9 +47942,9 @@ function clearTimer(t) {
47003
47942
  return null;
47004
47943
  }
47005
47944
  function uniqueDir(parent, name) {
47006
- let candidate = join21(parent, name);
47945
+ let candidate = join22(parent, name);
47007
47946
  for (let i = 2; existsSync9(candidate) && i < 1e4; i++)
47008
- candidate = join21(parent, `${name} ${i}`);
47947
+ candidate = join22(parent, `${name} ${i}`);
47009
47948
  return candidate;
47010
47949
  }
47011
47950
  var producers = /* @__PURE__ */ new Map();
@@ -47042,15 +47981,15 @@ async function buildManifest(root, followSymlinks, excludeHeavy) {
47042
47981
  const files = [];
47043
47982
  let totalBytes = 0;
47044
47983
  async function walk(dir) {
47045
- const entries = await readdir3(dir, { withFileTypes: true });
47984
+ const entries = await readdir4(dir, { withFileTypes: true });
47046
47985
  for (const e of entries) {
47047
47986
  if (ALWAYS_SKIP.has(e.name))
47048
47987
  continue;
47049
47988
  if (e.isDirectory() && excludeHeavy && HEAVY_DIRS.has(e.name))
47050
47989
  continue;
47051
- const abs = join21(dir, e.name);
47990
+ const abs = join22(dir, e.name);
47052
47991
  const rel = relative6(root, abs).split(sep6).join("/");
47053
- const st = await lstat(abs).catch(() => null);
47992
+ const st = await lstat2(abs).catch(() => null);
47054
47993
  if (!st)
47055
47994
  continue;
47056
47995
  if (st.isSymbolicLink() && !followSymlinks) {
@@ -47115,11 +48054,11 @@ async function startOutboundTransfer(msg, controllerClientId, ownHostId2, hostRo
47115
48054
  for (let fileIx = 0; fileIx < manifest.files.length && !state.cancelled; fileIx++) {
47116
48055
  const f = manifest.files[fileIx];
47117
48056
  if (f.kind === "symlink") {
47118
- const target = await readlink(join21(srcPath, f.rel.split("/").join(sep6))).catch(() => "");
48057
+ const target = await readlink(join22(srcPath, f.rel.split("/").join(sep6))).catch(() => "");
47119
48058
  send2({ type: "fs:transferChunk", transferId, toHost, fileIx, rel: f.rel, mode: f.mode, kind: "symlink", offset: 0, base64: Buffer.from(target, "utf-8").toString("base64"), eof: true }, { hostId: toHost });
47120
48059
  continue;
47121
48060
  }
47122
- const abs = join21(srcPath, f.rel.split("/").join(sep6));
48061
+ const abs = join22(srcPath, f.rel.split("/").join(sep6));
47123
48062
  let fd = null;
47124
48063
  try {
47125
48064
  fd = openSync3(abs, "r");
@@ -47231,7 +48170,7 @@ async function beginInboundTransfer(msg, hostRoot, send2) {
47231
48170
  failNoState("Invalid destination name.");
47232
48171
  return;
47233
48172
  }
47234
- let destRoot = join21(parent, msg.destName);
48173
+ let destRoot = join22(parent, msg.destName);
47235
48174
  let createdRoot = false;
47236
48175
  if (existsSync9(destRoot)) {
47237
48176
  if (msg.onCollision === "abort") {
@@ -47273,7 +48212,7 @@ async function beginInboundTransfer(msg, hostRoot, send2) {
47273
48212
  try {
47274
48213
  await mkdir6(destRoot, { recursive: true });
47275
48214
  for (const d of msg.dirs) {
47276
- const abs = join21(destRoot, d.rel.split("/").join(sep6));
48215
+ const abs = join22(destRoot, d.rel.split("/").join(sep6));
47277
48216
  if (isInsideRoot(abs, destRoot))
47278
48217
  await mkdir6(abs, { recursive: true }).catch(() => {
47279
48218
  });
@@ -47287,7 +48226,7 @@ async function writeInboundChunk(msg, send2) {
47287
48226
  if (!s || s.cancelled)
47288
48227
  return;
47289
48228
  armConsumerWatchdog(msg.transferId, send2);
47290
- const abs = join21(s.destRoot, msg.rel.split("/").join(sep6));
48229
+ const abs = join22(s.destRoot, msg.rel.split("/").join(sep6));
47291
48230
  if (msg.rel.includes("..") || !isInsideRoot(abs, s.destRoot)) {
47292
48231
  failConsumer(msg.transferId, `Refusing a path outside the destination: ${msg.rel}`, send2);
47293
48232
  return;
@@ -47295,7 +48234,7 @@ async function writeInboundChunk(msg, send2) {
47295
48234
  try {
47296
48235
  if (msg.kind === "symlink") {
47297
48236
  const target = Buffer.from(msg.base64, "base64").toString("utf-8");
47298
- const resolved = join21(dirname9(abs), target);
48237
+ const resolved = join22(dirname9(abs), target);
47299
48238
  if (isInsideRoot(resolved, s.destRoot)) {
47300
48239
  await mkdir6(dirname9(abs), { recursive: true }).catch(() => {
47301
48240
  });
@@ -47370,90 +48309,13 @@ function cancelTransfersForClient(clientId, send2) {
47370
48309
  }
47371
48310
 
47372
48311
  // ../../packages/host-core/dist/peer-link/index.js
47373
- import { homedir as homedir11 } from "node:os";
47374
- import { join as join22 } from "node:path";
47375
-
47376
- // ../../packages/host-core/dist/peer-link/manager.js
47377
- import { randomBytes as randomBytes2 } from "node:crypto";
47378
-
47379
- // ../../packages/host-core/dist/peer-link/tls-direct.js
47380
- var import_selfsigned = __toESM(require_selfsigned(), 1);
47381
- import tls from "node:tls";
47382
- import { X509Certificate } from "node:crypto";
47383
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync10, mkdirSync as mkdirSync8 } from "node:fs";
47384
- import { dirname as dirname10 } from "node:path";
47385
- function fingerprintOf(certPem) {
47386
- return new X509Certificate(certPem).fingerprint256;
47387
- }
47388
- async function loadOrCreateIdentity(certPath, keyPath) {
47389
- if (existsSync10(certPath) && existsSync10(keyPath)) {
47390
- const cert = readFileSync9(certPath, "utf8");
47391
- const key2 = readFileSync9(keyPath, "utf8");
47392
- return { cert, key: key2, fingerprint256: fingerprintOf(cert) };
47393
- }
47394
- const tenYears = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1e3);
47395
- const pems = await (0, import_selfsigned.generate)([{ name: "commonName", value: "tesseract-peer" }], { keySize: 2048, notAfterDate: tenYears, algorithm: "sha256" });
47396
- try {
47397
- mkdirSync8(dirname10(certPath), { recursive: true });
47398
- } catch {
47399
- }
47400
- try {
47401
- writeFileSync9(certPath, pems.cert, { mode: 384 });
47402
- writeFileSync9(keyPath, pems.private, { mode: 384 });
47403
- } catch {
47404
- }
47405
- return { cert: pems.cert, key: pems.private, fingerprint256: fingerprintOf(pems.cert) };
47406
- }
47407
- function pinnedConnect(opts) {
47408
- return new Promise((resolve10, reject) => {
47409
- let settled = false;
47410
- const done = (err, sock) => {
47411
- if (settled)
47412
- return;
47413
- settled = true;
47414
- if (err) {
47415
- try {
47416
- sock?.destroy();
47417
- } catch {
47418
- }
47419
- reject(err);
47420
- } else
47421
- resolve10(sock);
47422
- };
47423
- const socket2 = tls.connect({
47424
- host: opts.host,
47425
- port: opts.port,
47426
- // No CA / hostname identity — we authenticate SOLELY by the brokered
47427
- // fingerprint, so accept the self-signed cert at the TLS layer and verify
47428
- // the pin ourselves below (fail-closed).
47429
- rejectUnauthorized: false,
47430
- checkServerIdentity: () => void 0,
47431
- timeout: opts.timeoutMs ?? 8e3
47432
- }, () => {
47433
- const peer = socket2.getPeerCertificate();
47434
- const fp = peer && peer.fingerprint256;
47435
- if (!fp || fp !== opts.peerFingerprint256) {
47436
- done(new Error("peer-link: TLS fingerprint mismatch (fail-closed)"));
47437
- return;
47438
- }
47439
- socket2.setNoDelay(true);
47440
- done(void 0, socket2);
47441
- });
47442
- socket2.once("timeout", () => done(new Error("peer-link: TLS connect timeout")));
47443
- socket2.once("error", (e) => done(e instanceof Error ? e : new Error(String(e))));
47444
- });
47445
- }
47446
- function createListener(identity, port, onSocket) {
47447
- const server = tls.createServer({ cert: identity.cert, key: identity.key, requestCert: false, rejectUnauthorized: false }, (sock) => {
47448
- sock.setNoDelay(true);
47449
- onSocket(sock);
47450
- });
47451
- server.listen(port, "0.0.0.0");
47452
- return server;
47453
- }
48312
+ import { homedir as homedir12 } from "node:os";
48313
+ import { join as join23 } from "node:path";
47454
48314
 
47455
48315
  // ../../packages/host-core/dist/peer-link/manager.js
48316
+ init_tls_direct();
47456
48317
  init_transport();
48318
+ import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
47457
48319
 
47458
48320
  // ../../packages/host-core/dist/peer-link/candidates.js
47459
48321
  import os2 from "node:os";
@@ -47484,6 +48346,7 @@ function orderCandidates(cands) {
47484
48346
  }
47485
48347
 
47486
48348
  // ../../packages/host-core/dist/peer-link/ladder.js
48349
+ init_tls_direct();
47487
48350
  init_transport();
47488
48351
  function pathFor(c) {
47489
48352
  return c.type === "host" ? "lan" : "public";
@@ -47589,7 +48452,7 @@ var PeerLinkManager = class {
47589
48452
  const q = await Promise.resolve().then(() => (init_quic_transport(), quic_transport_exports));
47590
48453
  const socket2 = await q.makeQuicSocket(this.udpPort);
47591
48454
  this.quicSocket = socket2;
47592
- await q.startQuicServerOnSocket({ socket: socket2, identity, onStream: (bs) => this.acceptInbound(bs, "punch") });
48455
+ await q.startQuicServerOnSocket({ socket: socket2, identity, onStream: (bs2) => this.acceptInbound(bs2, "punch") });
47593
48456
  const refresh = async () => {
47594
48457
  try {
47595
48458
  const s = await q.discoverSrflx(socket2, stunHost, stunPort);
@@ -47651,10 +48514,13 @@ var PeerLinkManager = class {
47651
48514
  let matchedHex;
47652
48515
  const { link, ready } = socketPeerLink(sock, "", path5, {
47653
48516
  expectToken: (f) => {
47654
- const h = toHex(f);
47655
- if (this.pendingInbound.has(h)) {
47656
- matchedHex = h;
47657
- return true;
48517
+ const hb = Buffer.from(toHex(f));
48518
+ for (const key2 of this.pendingInbound.keys()) {
48519
+ const kb = Buffer.from(key2);
48520
+ if (kb.length === hb.length && timingSafeEqual2(kb, hb)) {
48521
+ matchedHex = key2;
48522
+ return true;
48523
+ }
47658
48524
  }
47659
48525
  return false;
47660
48526
  }
@@ -47798,7 +48664,7 @@ var PeerLinkManager = class {
47798
48664
  this.attach(ctx);
47799
48665
  await this.ensureListener();
47800
48666
  const myFingerprint = (await this.identity()).fingerprint256;
47801
- const rid = randomBytes2(8).toString("hex");
48667
+ const rid = randomBytes3(8).toString("hex");
47802
48668
  const myCandidates = gatherTcpCandidates(this.listenPort, this.observedIp);
47803
48669
  return new Promise((resolve10, reject) => {
47804
48670
  let settled = false;
@@ -47882,8 +48748,8 @@ var PeerLinkManager = class {
47882
48748
  // ../../packages/host-core/dist/peer-link/index.js
47883
48749
  var P2P_ENABLED = process.env.CCB_P2P !== "0";
47884
48750
  function identityPaths() {
47885
- const dir = process.env.CCB_P2P_DIR ?? join22(homedir11(), ".tesseract");
47886
- return { certPath: join22(dir, "peer-cert.pem"), keyPath: join22(dir, "peer-key.pem") };
48751
+ const dir = process.env.CCB_P2P_DIR ?? join23(homedir12(), ".tesseract");
48752
+ return { certPath: join23(dir, "peer-cert.pem"), keyPath: join23(dir, "peer-key.pem") };
47887
48753
  }
47888
48754
  var manager = null;
47889
48755
  function peerLinkManager() {
@@ -47898,9 +48764,9 @@ async function openPeerLink(peerHostId, ctx) {
47898
48764
  }
47899
48765
 
47900
48766
  // ../../packages/host-core/dist/active-link/engine.js
47901
- import { lstat as lstat6, readlink as readlink4, rm as rm3, mkdir as mkdir8, symlink as symlink2, rename as rename2, utimes as utimes2, writeFile as writeFile6 } from "node:fs/promises";
48767
+ import { lstat as lstat7, readlink as readlink4, rm as rm3, mkdir as mkdir8, symlink as symlink2, rename as rename2, utimes as utimes2, writeFile as writeFile6 } from "node:fs/promises";
47902
48768
  import { existsSync as existsSync12 } from "node:fs";
47903
- import { dirname as dirname14, join as join27, resolve as resolve8 } from "node:path";
48769
+ import { dirname as dirname14, join as join28, resolve as resolve8 } from "node:path";
47904
48770
 
47905
48771
  // ../../packages/host-core/dist/active-link/blocks.js
47906
48772
  import { createHash as createHash3 } from "node:crypto";
@@ -48165,7 +49031,7 @@ var BlockAssembler = class {
48165
49031
 
48166
49032
  // ../../packages/host-core/dist/active-link/manifest.js
48167
49033
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync10, mkdirSync as mkdirSync9, existsSync as existsSync11, renameSync as renameSync5 } from "node:fs";
48168
- import { join as join23 } from "node:path";
49034
+ import { join as join24 } from "node:path";
48169
49035
 
48170
49036
  // ../../packages/host-core/dist/active-link/reconcile.js
48171
49037
  var ABSENT = { kind: "absent", t: -Infinity, hash: "" };
@@ -48201,7 +49067,7 @@ var LinkManifest = class {
48201
49067
  this.dir = dir;
48202
49068
  }
48203
49069
  file() {
48204
- return join23(this.dir, "manifest.json");
49070
+ return join24(this.dir, "manifest.json");
48205
49071
  }
48206
49072
  load() {
48207
49073
  try {
@@ -48302,14 +49168,14 @@ var LinkManifest = class {
48302
49168
  };
48303
49169
 
48304
49170
  // ../../packages/host-core/dist/active-link/scanner.js
48305
- import { readdir as readdir4, lstat as lstat2, readlink as readlink2 } from "node:fs/promises";
48306
- import { join as join24, sep as sep7 } from "node:path";
49171
+ import { readdir as readdir5, lstat as lstat3, readlink as readlink2 } from "node:fs/promises";
49172
+ import { join as join25, sep as sep7 } from "node:path";
48307
49173
  async function walkRels(root, extraIgnore) {
48308
49174
  const out = [];
48309
49175
  async function rec(dir) {
48310
49176
  let entries;
48311
49177
  try {
48312
- entries = await readdir4(dir, { withFileTypes: true });
49178
+ entries = await readdir5(dir, { withFileTypes: true });
48313
49179
  } catch {
48314
49180
  return;
48315
49181
  }
@@ -48318,7 +49184,7 @@ async function walkRels(root, extraIgnore) {
48318
49184
  continue;
48319
49185
  if (e.isDirectory() && HEAVY_DIRS.has(e.name))
48320
49186
  continue;
48321
- const abs = join24(dir, e.name);
49187
+ const abs = join25(dir, e.name);
48322
49188
  if (extraIgnore?.(abs))
48323
49189
  continue;
48324
49190
  if (e.isDirectory())
@@ -48335,7 +49201,7 @@ async function scanTree(root, prior, allow, extraIgnore) {
48335
49201
  const rels = allow ? [...allow] : await walkRels(root, extraIgnore);
48336
49202
  for (const rel of rels) {
48337
49203
  const abs = fromPosixRel(root, rel);
48338
- const st = await lstat2(abs).catch(() => null);
49204
+ const st = await lstat3(abs).catch(() => null);
48339
49205
  if (!st)
48340
49206
  continue;
48341
49207
  if (st.isSymbolicLink()) {
@@ -48343,9 +49209,9 @@ async function scanTree(root, prior, allow, extraIgnore) {
48343
49209
  records.set(rel, { kind: "symlink", mtimeMs: Math.floor(st.mtimeMs), size: 0, mode: st.mode, hash: hashString(target), blocks: [] });
48344
49210
  } else if (st.isFile()) {
48345
49211
  const mtimeMs = Math.floor(st.mtimeMs);
48346
- const cached2 = prior?.getRecord(rel);
48347
- if (cached2 && cached2.kind === "file" && cached2.size === st.size && cached2.mtimeMs === mtimeMs) {
48348
- records.set(rel, { ...cached2, mode: st.mode });
49212
+ const cached3 = prior?.getRecord(rel);
49213
+ if (cached3 && cached3.kind === "file" && cached3.size === st.size && cached3.mtimeMs === mtimeMs) {
49214
+ records.set(rel, { ...cached3, mode: st.mode });
48349
49215
  } else {
48350
49216
  const h = await hashFileBlocks(abs).catch(() => null);
48351
49217
  if (!h)
@@ -48357,7 +49223,7 @@ async function scanTree(root, prior, allow, extraIgnore) {
48357
49223
  return { records };
48358
49224
  }
48359
49225
  function absOf(root, rel) {
48360
- return join24(root, rel.split("/").join(sep7));
49226
+ return join25(root, rel.split("/").join(sep7));
48361
49227
  }
48362
49228
 
48363
49229
  // ../../packages/host-core/dist/active-link/gitignore.js
@@ -48375,7 +49241,7 @@ async function gitSyncedFiles(root) {
48375
49241
 
48376
49242
  // ../../packages/host-core/dist/active-link/session.js
48377
49243
  import { createHash as createHash4 } from "node:crypto";
48378
- import { lstat as lstat3, readlink as readlink3, open as open2 } from "node:fs/promises";
49244
+ import { lstat as lstat4, readlink as readlink3, open as open2 } from "node:fs/promises";
48379
49245
  var OutboundSender = class {
48380
49246
  deps;
48381
49247
  queue = [];
@@ -48433,8 +49299,12 @@ var OutboundSender = class {
48433
49299
  }
48434
49300
  }
48435
49301
  async sendWholeFile(rel) {
49302
+ if (rel.includes("..") || !isInsideRoot(absOf(this.deps.localRoot, rel), this.deps.localRoot)) {
49303
+ this.deps.onError?.(`Refused a path outside the link root: ${rel}`);
49304
+ return;
49305
+ }
48436
49306
  const abs = absOf(this.deps.localRoot, rel);
48437
- const st = await lstat3(abs).catch(() => null);
49307
+ const st = await lstat4(abs).catch(() => null);
48438
49308
  if (!st)
48439
49309
  return;
48440
49310
  const { linkId, ownHostId: ownHostId2, send: send2 } = this.deps;
@@ -48522,8 +49392,12 @@ var OutboundSender = class {
48522
49392
  this.deps.onSent?.(rel);
48523
49393
  }
48524
49394
  async sendBlocks(rel, indexes) {
49395
+ if (rel.includes("..") || !isInsideRoot(absOf(this.deps.localRoot, rel), this.deps.localRoot)) {
49396
+ this.deps.onError?.(`Refused a path outside the link root: ${rel}`);
49397
+ return;
49398
+ }
48525
49399
  const abs = absOf(this.deps.localRoot, rel);
48526
- const st = await lstat3(abs).catch(() => null);
49400
+ const st = await lstat4(abs).catch(() => null);
48527
49401
  if (!st || !st.isFile())
48528
49402
  return;
48529
49403
  const { linkId, ownHostId: ownHostId2, send: send2 } = this.deps;
@@ -48568,12 +49442,12 @@ var OutboundSender = class {
48568
49442
 
48569
49443
  // ../../node_modules/chokidar/esm/index.js
48570
49444
  import { stat as statcb } from "fs";
48571
- import { stat as stat6, readdir as readdir6 } from "fs/promises";
49445
+ import { stat as stat6, readdir as readdir7 } from "fs/promises";
48572
49446
  import { EventEmitter as EventEmitter2 } from "events";
48573
49447
  import * as sysPath2 from "path";
48574
49448
 
48575
49449
  // ../../node_modules/readdirp/esm/index.js
48576
- import { stat as stat4, lstat as lstat4, readdir as readdir5, realpath } from "node:fs/promises";
49450
+ import { stat as stat4, lstat as lstat5, readdir as readdir6, realpath } from "node:fs/promises";
48577
49451
  import { Readable } from "node:stream";
48578
49452
  import { resolve as presolve, relative as prelative, join as pjoin, sep as psep } from "node:path";
48579
49453
  var EntryTypes = {
@@ -48640,7 +49514,7 @@ var ReaddirpStream = class extends Readable {
48640
49514
  const { root, type } = opts;
48641
49515
  this._fileFilter = normalizeFilter(opts.fileFilter);
48642
49516
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
48643
- const statMethod = opts.lstat ? lstat4 : stat4;
49517
+ const statMethod = opts.lstat ? lstat5 : stat4;
48644
49518
  if (wantBigintFsStats) {
48645
49519
  this._stat = (path5) => statMethod(path5, { bigint: true });
48646
49520
  } else {
@@ -48711,7 +49585,7 @@ var ReaddirpStream = class extends Readable {
48711
49585
  async _exploreDir(path5, depth) {
48712
49586
  let files;
48713
49587
  try {
48714
- files = await readdir5(path5, this._rdOptions);
49588
+ files = await readdir6(path5, this._rdOptions);
48715
49589
  } catch (error51) {
48716
49590
  this._onError(error51);
48717
49591
  }
@@ -48750,7 +49624,7 @@ var ReaddirpStream = class extends Readable {
48750
49624
  const full = entry.fullPath;
48751
49625
  try {
48752
49626
  const entryRealPath = await realpath(full);
48753
- const entryRealPathStats = await lstat4(entryRealPath);
49627
+ const entryRealPathStats = await lstat5(entryRealPath);
48754
49628
  if (entryRealPathStats.isFile()) {
48755
49629
  return "file";
48756
49630
  }
@@ -48793,7 +49667,7 @@ function readdirp(root, options = {}) {
48793
49667
 
48794
49668
  // ../../node_modules/chokidar/esm/handler.js
48795
49669
  import { watchFile, unwatchFile, watch as fs_watch } from "fs";
48796
- import { open as open3, stat as stat5, lstat as lstat5, realpath as fsrealpath } from "fs/promises";
49670
+ import { open as open3, stat as stat5, lstat as lstat6, realpath as fsrealpath } from "fs/promises";
48797
49671
  import * as sysPath from "path";
48798
49672
  import { type as osType } from "os";
48799
49673
  var STR_DATA = "data";
@@ -48820,7 +49694,7 @@ var EVENTS = {
48820
49694
  };
48821
49695
  var EV = EVENTS;
48822
49696
  var THROTTLE_MODE_WATCH = "watch";
48823
- var statMethods = { lstat: lstat5, stat: stat5 };
49697
+ var statMethods = { lstat: lstat6, stat: stat5 };
48824
49698
  var KEY_LISTENERS = "listeners";
48825
49699
  var KEY_ERR = "errHandlers";
48826
49700
  var KEY_RAW = "rawEmitters";
@@ -49680,7 +50554,7 @@ var DirEntry = class {
49680
50554
  return;
49681
50555
  const dir = this.path;
49682
50556
  try {
49683
- await readdir6(dir);
50557
+ await readdir7(dir);
49684
50558
  } catch (err) {
49685
50559
  if (this._removeWatcher) {
49686
50560
  this._removeWatcher(sysPath2.dirname(dir), sysPath2.basename(dir));
@@ -50374,7 +51248,7 @@ var ActiveLinkEngine = class {
50374
51248
  }
50375
51249
  async start() {
50376
51250
  this.emit({ type: "status", linkId: this.opts.linkId, state: "scanning" });
50377
- await rm3(join27(this.stateDir, ".staging"), { recursive: true, force: true }).catch(() => {
51251
+ await rm3(join28(this.stateDir, ".staging"), { recursive: true, force: true }).catch(() => {
50378
51252
  });
50379
51253
  this.manifest.load();
50380
51254
  await this.reconcileScan();
@@ -50596,16 +51470,18 @@ var ActiveLinkEngine = class {
50596
51470
  // ── Block-delta: sender side (reply to a block-map request) ───────────────────
50597
51471
  async onBlockMapReq(rel) {
50598
51472
  const abs = absOf(this.opts.localRoot, rel);
50599
- const st = await lstat6(abs).catch(() => null);
51473
+ if (rel.includes("..") || !isInsideRoot(abs, this.opts.localRoot))
51474
+ return;
51475
+ const st = await lstat7(abs).catch(() => null);
50600
51476
  if (!st || !st.isFile())
50601
51477
  return;
50602
51478
  const mtimeMs = Math.floor(st.mtimeMs);
50603
- const cached2 = this.manifest.getRecord(rel);
51479
+ const cached3 = this.manifest.getRecord(rel);
50604
51480
  let wholeHash;
50605
51481
  let blocks;
50606
- if (cached2 && cached2.kind === "file" && cached2.size === st.size && cached2.mtimeMs === mtimeMs) {
50607
- wholeHash = cached2.hash;
50608
- blocks = cached2.blocks;
51482
+ if (cached3 && cached3.kind === "file" && cached3.size === st.size && cached3.mtimeMs === mtimeMs) {
51483
+ wholeHash = cached3.hash;
51484
+ blocks = cached3.blocks;
50609
51485
  } else {
50610
51486
  const h = await hashFileBlocks(abs).catch(() => null);
50611
51487
  if (!h)
@@ -50819,6 +51695,8 @@ var ActiveLinkEngine = class {
50819
51695
  const fromRec = this.manifest.getRecord(fromRel);
50820
51696
  const fromAbs = absOf(this.opts.localRoot, fromRel);
50821
51697
  const toAbs2 = absOf(this.opts.localRoot, toRel2);
51698
+ if (fromRel.includes("..") || !isInsideRoot(fromAbs, this.opts.localRoot))
51699
+ return;
50822
51700
  if (toRel2.includes("..") || !isInsideRoot(toAbs2, this.opts.localRoot))
50823
51701
  return;
50824
51702
  if (fromRec && fromRec.kind === "file") {
@@ -50916,7 +51794,7 @@ var ActiveLinkEngine = class {
50916
51794
  /** Current on-disk record for a rel, or null if missing/a directory. */
50917
51795
  async computeRecord(rel) {
50918
51796
  const abs = absOf(this.opts.localRoot, rel);
50919
- const st = await lstat6(abs).catch(() => null);
51797
+ const st = await lstat7(abs).catch(() => null);
50920
51798
  if (!st || st.isDirectory())
50921
51799
  return null;
50922
51800
  if (st.isSymbolicLink()) {
@@ -50926,9 +51804,9 @@ var ActiveLinkEngine = class {
50926
51804
  if (!st.isFile())
50927
51805
  return null;
50928
51806
  const mtimeMs = Math.floor(st.mtimeMs);
50929
- const cached2 = this.manifest.getRecord(rel);
50930
- if (cached2 && cached2.kind === "file" && cached2.size === st.size && cached2.mtimeMs === mtimeMs) {
50931
- return { ...cached2, mode: st.mode };
51807
+ const cached3 = this.manifest.getRecord(rel);
51808
+ if (cached3 && cached3.kind === "file" && cached3.size === st.size && cached3.mtimeMs === mtimeMs) {
51809
+ return { ...cached3, mode: st.mode };
50932
51810
  }
50933
51811
  const h = await hashFileBlocks(abs).catch(() => null);
50934
51812
  if (!h)
@@ -50998,7 +51876,7 @@ var ActiveLinkEngine = class {
50998
51876
  this.inboundWatchdog.clear();
50999
51877
  }
51000
51878
  stagingPath(rel, tag) {
51001
- return join27(this.stateDir, ".staging", `${hashString(rel)}.${tag}`);
51879
+ return join28(this.stateDir, ".staging", `${hashString(rel)}.${tag}`);
51002
51880
  }
51003
51881
  pruneSuppress() {
51004
51882
  const now = this.now();
@@ -51024,9 +51902,9 @@ var ActiveLinkEngine = class {
51024
51902
 
51025
51903
  // ../../packages/host-core/dist/active-link/config.js
51026
51904
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync11, mkdirSync as mkdirSync10, existsSync as existsSync13, renameSync as renameSync6, watch as watch4 } from "node:fs";
51027
- import { join as join28 } from "node:path";
51905
+ import { join as join29 } from "node:path";
51028
51906
  function configPath2() {
51029
- return join28(activeLinkRoot(), "links.json");
51907
+ return join29(activeLinkRoot(), "links.json");
51030
51908
  }
51031
51909
  function loadLinkConfigs() {
51032
51910
  try {
@@ -51063,7 +51941,7 @@ function saveLinkConfig(cfg2) {
51063
51941
  writeAll(links);
51064
51942
  }
51065
51943
  function pendingPath() {
51066
- return join28(activeLinkRoot(), "pending-deprovisions.json");
51944
+ return join29(activeLinkRoot(), "pending-deprovisions.json");
51067
51945
  }
51068
51946
  function loadPendingDeprovisions() {
51069
51947
  try {
@@ -51239,7 +52117,7 @@ init_dist4();
51239
52117
 
51240
52118
  // ../../packages/host-core/dist/pty-manager.js
51241
52119
  import { existsSync as existsSync14 } from "node:fs";
51242
- import { homedir as homedir12 } from "node:os";
52120
+ import { homedir as homedir13 } from "node:os";
51243
52121
  var ptyPromise = null;
51244
52122
  function loadPty() {
51245
52123
  if (!ptyPromise) {
@@ -51276,15 +52154,13 @@ async function openPty(opts, sink) {
51276
52154
  const pty = await loadPty();
51277
52155
  if (!pty)
51278
52156
  throw new Error("Terminal support is unavailable on this machine.");
52157
+ if (!opts.local && opts.shell && !new Set(listInterpreters().map((i) => i.path)).has(opts.shell)) {
52158
+ throw new Error("Requested shell is not an allowed interpreter on this machine.");
52159
+ }
51279
52160
  const shell = opts.shell || defaultShell();
51280
52161
  const args = opts.args ?? defaultArgs(shell);
51281
52162
  const cwd = resolveCwd(opts.cwd, opts.hostRoot);
51282
- const env = {
51283
- ...process.env,
51284
- ...opts.env,
51285
- TERM: "xterm-256color",
51286
- COLORTERM: "truecolor"
51287
- };
52163
+ const env = buildChildEnv(opts);
51288
52164
  let proc;
51289
52165
  try {
51290
52166
  proc = pty.spawn(shell, args, {
@@ -51464,8 +52340,34 @@ function defaultArgs(shell) {
51464
52340
  return [];
51465
52341
  return ["bash", "zsh", "fish"].includes(idForPath(shell)) ? ["-l"] : [];
51466
52342
  }
52343
+ var DANGEROUS_ENV_KEY = /^(LD_|DYLD_)|PRELOAD|^NODE_OPTIONS$|^PATH$|^BASH_ENV$|^ENV$|^ZDOTDIR$|^IFS$|^PROMPT_COMMAND$|^PERL5LIB$|^PYTHONPATH$|^PYTHONSTARTUP$|^GIT_SSH|^GIT_EXTERNAL_DIFF$/i;
52344
+ var REMOTE_ENV_ALLOW = ["HOME", "USER", "LOGNAME", "LANG", "LC_ALL", "LC_CTYPE", "TZ", "PATH", "SHELL", "TMPDIR", "SystemRoot", "ComSpec", "PATHEXT", "USERPROFILE", "APPDATA", "LOCALAPPDATA"];
52345
+ function stripDangerousEnv(env) {
52346
+ const out = {};
52347
+ if (env) {
52348
+ for (const [k, v] of Object.entries(env))
52349
+ if (!DANGEROUS_ENV_KEY.test(k))
52350
+ out[k] = v;
52351
+ }
52352
+ return out;
52353
+ }
52354
+ function buildChildEnv(opts) {
52355
+ const base = {};
52356
+ if (opts.local) {
52357
+ Object.assign(base, process.env);
52358
+ Object.assign(base, opts.env ?? {});
52359
+ } else {
52360
+ for (const k of REMOTE_ENV_ALLOW)
52361
+ if (process.env[k] != null)
52362
+ base[k] = process.env[k];
52363
+ Object.assign(base, stripDangerousEnv(opts.env));
52364
+ }
52365
+ base.TERM = "xterm-256color";
52366
+ base.COLORTERM = "truecolor";
52367
+ return base;
52368
+ }
51467
52369
  function resolveCwd(cwd, hostRoot) {
51468
- const fallback = hostRoot || homedir12();
52370
+ const fallback = hostRoot || homedir13();
51469
52371
  if (!cwd)
51470
52372
  return fallback;
51471
52373
  if (!existsSync14(cwd))
@@ -51666,7 +52568,17 @@ async function createElevenLabsAgent(args) {
51666
52568
  first_message: "",
51667
52569
  language
51668
52570
  },
51669
- asr: { quality: "high", provider: "elevenlabs", user_input_audio_format: "pcm_16000" },
52571
+ // ElevenLabs retired "Original ASR" (provider:'elevenlabs') — after 2026-07-08
52572
+ // create/patch with it returns an error. scribe_realtime (Scribe v2.2 Realtime)
52573
+ // is the successor and supports pcm_16000 (verified against a live agent).
52574
+ // keyterm prompting boosts recognition of brand/feature terms the guide
52575
+ // discusses (Scribe realtime caps at 50 terms × 20 chars).
52576
+ asr: {
52577
+ quality: "high",
52578
+ provider: "scribe_realtime",
52579
+ user_input_audio_format: "pcm_16000",
52580
+ keywords: ["Tesseract", "Claude", "Anthropic", "MCP", "worktree", "subagent", "plugin"]
52581
+ },
51670
52582
  tts: {
51671
52583
  model_id: "eleven_flash_v2",
51672
52584
  voice_id: voiceId,
@@ -51675,7 +52587,12 @@ async function createElevenLabsAgent(args) {
51675
52587
  speed: 1,
51676
52588
  similarity_boost: 0.8
51677
52589
  },
51678
- turn: { turn_timeout: 7, turn_eagerness: "normal" },
52590
+ // turn_v3 = latest turn-taking model (better accuracy + lower turn latency).
52591
+ // Eagerness stays 'normal' (NOT 'patient' like the phone info-collection
52592
+ // bots): the guide is a real-time desktop assistant and must stay responsive.
52593
+ turn: { turn_timeout: 7, turn_eagerness: "normal", turn_model: "turn_v3" },
52594
+ // Ignore background voices (room noise / other people) to cut false barge-ins.
52595
+ vad: { background_voice_detection: true },
51679
52596
  conversation: {
51680
52597
  text_only: false,
51681
52598
  // 2h — long working sessions. NOT per-conversation overridable (EL pins
@@ -52879,7 +53796,8 @@ function turnEnded(key2) {
52879
53796
  function turnRekeyed(from, to) {
52880
53797
  cfg?.power.turnRekeyed(from, to);
52881
53798
  }
52882
- var HOST_ROOT = process.env.CCB_HOST_ROOT ?? homedir13();
53799
+ var HOST_ROOT = resolveHostRoot();
53800
+ var HOST_CONTAIN = resolveContainmentRoot() ?? void 0;
52883
53801
  var hooks = null;
52884
53802
  var socket = null;
52885
53803
  var heartbeat = null;
@@ -52920,13 +53838,43 @@ function wsUrl(token) {
52920
53838
  return `${backendOrigin.replace(/^http/, "ws")}/ws?token=${encodeURIComponent(token)}`;
52921
53839
  }
52922
53840
  var outbox = [];
52923
- function send(channel, payload, extra) {
53841
+ function rawSend(channel, payload, extra) {
52924
53842
  const data = JSON.stringify({ channel, payload, ...extra });
52925
53843
  if (socket && socket.readyState === 1)
52926
53844
  socket.send(data);
52927
53845
  else if (socket && socket.readyState === 0 && outbox.length < 256)
52928
53846
  outbox.push(data);
52929
53847
  }
53848
+ var hostSessions = null;
53849
+ var sessToClient = /* @__PURE__ */ new Map();
53850
+ var clientToSess = /* @__PURE__ */ new Map();
53851
+ var sessHost = /* @__PURE__ */ new Map();
53852
+ var sealChains = /* @__PURE__ */ new Map();
53853
+ var SEALABLE_CHANNELS = /* @__PURE__ */ new Set(["agent", "fs", "term", "browser", "editor"]);
53854
+ function e2eRouting(channel, sess, browserId) {
53855
+ return { ch: channel, host: sessHost.get(sess) ?? "", client: "", sess, bid: typeof browserId === "string" ? browserId : "" };
53856
+ }
53857
+ function e2eRequired() {
53858
+ return !!hostSessions && process.env.CCB_E2E_REQUIRED === "1";
53859
+ }
53860
+ function send(channel, payload, extra) {
53861
+ const clientId = extra && typeof extra.clientId === "string" ? extra.clientId : void 0;
53862
+ const sess = clientId ? clientToSess.get(clientId) : void 0;
53863
+ const isHandshake = !!payload && typeof payload.t === "string" && payload.t.startsWith("e2e:");
53864
+ if (hostSessions && clientId && sess && hostSessions.has(sess) && SEALABLE_CHANNELS.has(channel) && !isHandshake) {
53865
+ const routing = e2eRouting(channel, sess, extra?.browserId);
53866
+ const prev = sealChains.get(clientId) ?? Promise.resolve();
53867
+ const next = prev.then(async () => {
53868
+ const rec = await hostSessions.seal(sess, routing, payload);
53869
+ rawSend(channel, rec ?? payload, extra);
53870
+ }).catch(() => {
53871
+ rawSend(channel, payload, extra);
53872
+ });
53873
+ sealChains.set(clientId, next);
53874
+ return;
53875
+ }
53876
+ rawSend(channel, payload, extra);
53877
+ }
52930
53878
  var p2pSubscribers = /* @__PURE__ */ new Set();
52931
53879
  var p2pCtx = null;
52932
53880
  var p2pReady = false;
@@ -53160,9 +54108,9 @@ function provisionActiveLinkMirror(payload) {
53160
54108
  const remoteSubfolder = typeof payload.remoteSubfolder === "string" ? payload.remoteSubfolder : void 0;
53161
54109
  let localRoot;
53162
54110
  if (remoteParent && remoteSubfolder) {
53163
- localRoot = join29(remoteParent, safeFolderName(remoteSubfolder));
54111
+ localRoot = join30(remoteParent, safeFolderName(remoteSubfolder));
53164
54112
  } else {
53165
- localRoot = join29(HOST_ROOT, "Tesseract Mirrors", safeFolderName(projectName));
54113
+ localRoot = join30(HOST_ROOT, "Tesseract Mirrors", safeFolderName(projectName));
53166
54114
  }
53167
54115
  if (loadLinkConfigs().some((c) => c.localRoot === localRoot && c.linkId !== linkId)) {
53168
54116
  localRoot = `${localRoot}-${linkId.slice(0, 6)}`;
@@ -53387,6 +54335,8 @@ async function handleInboundAgent(msg, driverClientId) {
53387
54335
  const newId = await b.forkSession?.(msg.sessionId, { projectDir: msg.projectDir, upToMessageId: msg.upToMessageId, title: msg.title }).catch(() => null);
53388
54336
  if (newId)
53389
54337
  send("agent", { type: "session:forked", clientRequestId: msg.clientRequestId, sessionId: newId, sourceSessionId: msg.sessionId, title: msg.title }, route2);
54338
+ else
54339
+ send("agent", { type: "session:forkFailed", clientRequestId: msg.clientRequestId, sourceSessionId: msg.sessionId }, route2);
53390
54340
  break;
53391
54341
  }
53392
54342
  case "agent:context_usage":
@@ -53463,7 +54413,7 @@ async function serveInboundFs(requestId, op, args, driverClientId) {
53463
54413
  return;
53464
54414
  const route2 = driverClientId ? { clientId: driverClientId } : {};
53465
54415
  try {
53466
- const result = await runRemoteFsOp(HOST_ROOT, op, args ?? []);
54416
+ const result = await runRemoteFsOp(HOST_ROOT, op, args ?? [], driverClientId);
53467
54417
  send("fs", { type: "fs:res", requestId, result }, route2);
53468
54418
  if (op === "openFolder" && driverClientId && result && typeof result.root === "string") {
53469
54419
  const root = result.root;
@@ -53482,7 +54432,7 @@ async function serveInboundTerm(p, driverClientId) {
53482
54432
  exit: (ptyId, exitCode, signal) => send("term", { type: "term:exit", ptyId, exitCode, signal }, route2)
53483
54433
  };
53484
54434
  try {
53485
- const { pid } = await openPty({ ptyId: p.ptyId, clientId: driverClientId, shell: p.shell, args: p.args, cwd: p.cwd, cols: p.cols, rows: p.rows, env: p.env, hostRoot: HOST_ROOT }, sink);
54435
+ const { pid } = await openPty({ ptyId: p.ptyId, clientId: driverClientId, shell: p.shell, args: p.args, cwd: p.cwd, cols: p.cols, rows: p.rows, env: p.env, hostRoot: HOST_CONTAIN }, sink);
53486
54436
  send("term", { type: "term:opened", ptyId: p.ptyId, requestId: p.requestId, pid }, route2);
53487
54437
  } catch (err) {
53488
54438
  send("term", { type: "term:openError", ptyId: p.ptyId, requestId: p.requestId, error: err instanceof Error ? err.message : String(err) }, route2);
@@ -53505,6 +54455,15 @@ async function serveInboundTerm(p, driverClientId) {
53505
54455
  }
53506
54456
  function releaseDriver(clientId) {
53507
54457
  unwatchRemoteClient(clientId);
54458
+ releaseFsClient(clientId);
54459
+ const sess = clientToSess.get(clientId);
54460
+ if (sess) {
54461
+ hostSessions?.drop(sess);
54462
+ sessToClient.delete(sess);
54463
+ sessHost.delete(sess);
54464
+ }
54465
+ clientToSess.delete(clientId);
54466
+ sealChains.delete(clientId);
53508
54467
  voiceControls.get(clientId)?.stop("disconnected");
53509
54468
  voiceControls.delete(clientId);
53510
54469
  unwatchSessionClient(clientId);
@@ -53617,7 +54576,7 @@ function registerAsHost() {
53617
54576
  void ensureP2pReady();
53618
54577
  const kind = process.env.CCB_HOST_KIND === "cloud" ? "cloud" : void 0;
53619
54578
  const name = kind === "cloud" ? "Cloud agent" : hostname3();
53620
- send("host", { type: "host:register", name, platform: process.platform, deviceId, caps: ["agent", "fs", "term"], ...kind ? { kind } : {}, ...staticMetrics(hostVersion), ...p2pAdvert ?? {} });
54579
+ send("host", { type: "host:register", name, platform: process.platform, deviceId, caps: hostSessions ? ["agent", "fs", "term", "e2e"] : ["agent", "fs", "term"], ...kind ? { kind } : {}, ...staticMetrics(hostVersion), ...p2pAdvert ?? {} });
53621
54580
  if (!heartbeat)
53622
54581
  heartbeat = setInterval(() => send("host", { type: "host:heartbeat", ...dynamicMetrics() }), 2e4);
53623
54582
  }
@@ -53654,10 +54613,8 @@ function connect() {
53654
54613
  }
53655
54614
  }
53656
54615
  };
53657
- socket.onmessage = (ev) => {
53658
- const data = ev.data;
53659
- if (typeof data !== "string")
53660
- return;
54616
+ let inboundChain = Promise.resolve();
54617
+ const handleInbound = async (data) => {
53661
54618
  let env;
53662
54619
  try {
53663
54620
  env = JSON.parse(data);
@@ -53681,18 +54638,50 @@ function connect() {
53681
54638
  }
53682
54639
  const inbound = typeof env.hostId === "string" && env.hostId.endsWith(`:${deviceId}`);
53683
54640
  const clientId = typeof env.clientId === "string" ? env.clientId : void 0;
54641
+ const driving = inbound && sharingEnabled() && !entitlementDenied;
54642
+ if (hostSessions && inbound && clientId && channel && SEALABLE_CHANNELS.has(channel)) {
54643
+ const pp = env.payload;
54644
+ if (pp && typeof pp.t === "string" && pp.t.startsWith("e2e:")) {
54645
+ const hs = env.payload;
54646
+ if (hs.t === "e2e:hello") {
54647
+ sessToClient.set(hs.sess, clientId);
54648
+ clientToSess.set(clientId, hs.sess);
54649
+ if (typeof env.hostId === "string")
54650
+ sessHost.set(hs.sess, env.hostId);
54651
+ }
54652
+ await hostSessions.onHandshake(hs);
54653
+ return;
54654
+ }
54655
+ if (pp && pp.e2e === 1) {
54656
+ if (!driving)
54657
+ return;
54658
+ const sess = clientToSess.get(clientId);
54659
+ if (!sess || !hostSessions.has(sess))
54660
+ return;
54661
+ try {
54662
+ const plain = await hostSessions.open(sess, e2eRouting(channel, sess, env.browserId), env.payload);
54663
+ await handleInbound(JSON.stringify({ ...env, payload: plain, e2eOpened: true }));
54664
+ } catch {
54665
+ }
54666
+ return;
54667
+ }
54668
+ }
53684
54669
  if (channel === "activelink") {
53685
54670
  handleActiveLinkChannel(env.payload, inbound);
53686
54671
  return;
53687
54672
  }
53688
54673
  if (channel === "voice") {
53689
- if (inbound)
53690
- void handleInboundVoice(env.payload, clientId);
54674
+ if (inbound) {
54675
+ if (driving)
54676
+ void handleInboundVoice(env.payload, clientId);
54677
+ return;
54678
+ }
53691
54679
  return;
53692
54680
  }
53693
54681
  if (channel === "agent") {
53694
54682
  if (inbound) {
53695
- void handleInboundAgent(env.payload, clientId);
54683
+ if (driving)
54684
+ void handleInboundAgent(env.payload, clientId);
53696
54685
  return;
53697
54686
  }
53698
54687
  const p = env.payload;
@@ -53709,7 +54698,13 @@ function connect() {
53709
54698
  if (channel === "fs") {
53710
54699
  const p = env.payload;
53711
54700
  if (inbound && p.type === "fs:req") {
53712
- void serveInboundFs(p.requestId, p.op, p.args, clientId);
54701
+ if (driving) {
54702
+ if (e2eRequired() && !env.e2eOpened) {
54703
+ send("fs", { type: "fs:res", requestId: p.requestId, error: "This machine requires an encrypted connection \u2014 please update your app." }, clientId ? { clientId } : {});
54704
+ return;
54705
+ }
54706
+ void serveInboundFs(p.requestId, p.op, p.args, clientId);
54707
+ }
53713
54708
  return;
53714
54709
  }
53715
54710
  if (p.type === "fs:res") {
@@ -53725,27 +54720,27 @@ function connect() {
53725
54720
  return;
53726
54721
  }
53727
54722
  if (p.type === "fs:transferStart") {
53728
- if (inbound)
54723
+ if (driving)
53729
54724
  void beginOutboundTransfer(p, clientId ?? "");
53730
54725
  return;
53731
54726
  }
53732
54727
  if (p.type === "fs:transferManifest") {
53733
- if (inbound)
54728
+ if (driving)
53734
54729
  void beginInboundTransfer(p, HOST_ROOT, transferSend);
53735
54730
  return;
53736
54731
  }
53737
54732
  if (p.type === "fs:transferChunk") {
53738
- if (inbound)
54733
+ if (driving)
53739
54734
  void writeInboundChunk(p, transferSend);
53740
54735
  return;
53741
54736
  }
53742
54737
  if (p.type === "fs:transferAck") {
53743
- if (inbound)
54738
+ if (driving)
53744
54739
  onTransferAck(p);
53745
54740
  return;
53746
54741
  }
53747
54742
  if (p.type === "fs:transferCancel") {
53748
- if (inbound)
54743
+ if (driving)
53749
54744
  cancelTransfer(p.transferId, transferSend);
53750
54745
  return;
53751
54746
  }
@@ -53758,7 +54753,14 @@ function connect() {
53758
54753
  if (channel === "term") {
53759
54754
  const p = env.payload;
53760
54755
  if (inbound) {
53761
- void serveInboundTerm(p, clientId);
54756
+ if (driving) {
54757
+ if (e2eRequired() && !env.e2eOpened) {
54758
+ if (p.type === "term:open")
54759
+ send("term", { type: "term:openError", ptyId: p.ptyId, requestId: p.requestId, error: "This machine requires an encrypted connection \u2014 please update your app." }, clientId ? { clientId } : {});
54760
+ return;
54761
+ }
54762
+ void serveInboundTerm(p, clientId);
54763
+ }
53762
54764
  return;
53763
54765
  }
53764
54766
  hooks?.pushTerm(p);
@@ -53773,7 +54775,7 @@ function connect() {
53773
54775
  r(p.iceServers);
53774
54776
  return;
53775
54777
  }
53776
- if (inbound && clientId && typeof p?.type === "string" && p.type.startsWith("signal:dc-")) {
54778
+ if (driving && clientId && typeof p?.type === "string" && p.type.startsWith("signal:dc-")) {
53777
54779
  void handleDcSignal(clientId, p, HOST_ROOT, ownHostId(), (payload) => send("signal", payload, { clientId, browserId: "fsdc" }));
53778
54780
  }
53779
54781
  return;
@@ -53790,7 +54792,7 @@ function connect() {
53790
54792
  }
53791
54793
  if (channel === "browser") {
53792
54794
  console.log(`[browser/host] '${String(env.payload?.type)}' inbound=${inbound} client=${clientId ? String(clientId).slice(0, 8) : "none"} bid=${env.browserId ?? "default"}`);
53793
- if (!inbound)
54795
+ if (!driving)
53794
54796
  return;
53795
54797
  const p = env.payload;
53796
54798
  if (!p || typeof p.type !== "string" || !clientId)
@@ -53828,7 +54830,7 @@ function connect() {
53828
54830
  if (channel === "editor") {
53829
54831
  const ep = env.payload;
53830
54832
  console.log(`[editor/host] '${String(ep?.channel)}' inbound=${inbound} client=${clientId ? String(clientId).slice(0, 8) : "none"} bid=${env.browserId ?? "default"}`);
53831
- if (!inbound)
54833
+ if (!driving)
53832
54834
  return;
53833
54835
  if (!ep || typeof ep.channel !== "string" || !clientId)
53834
54836
  return;
@@ -53847,6 +54849,16 @@ function connect() {
53847
54849
  return;
53848
54850
  }
53849
54851
  };
54852
+ socket.onmessage = (ev) => {
54853
+ const data = ev.data;
54854
+ if (typeof data !== "string")
54855
+ return;
54856
+ if (hostSessions)
54857
+ inboundChain = inboundChain.then(() => handleInbound(data)).catch(() => {
54858
+ });
54859
+ else
54860
+ void handleInbound(data);
54861
+ };
53850
54862
  socket.onclose = () => {
53851
54863
  cleanup();
53852
54864
  if (!stopped)
@@ -53862,6 +54874,19 @@ function startControlLink(config2, h) {
53862
54874
  hostVersion = config2.version;
53863
54875
  hooks = h;
53864
54876
  stopped = false;
54877
+ if (config2.e2e && !hostSessions) {
54878
+ const e2e = config2.e2e;
54879
+ hostSessions = new E2ESessions({
54880
+ selfDeviceId: e2e.selfDeviceId,
54881
+ identityPriv: e2e.identityPriv,
54882
+ getPeerIdentityPub: e2e.getPeerIdentityPub,
54883
+ sendHandshake: (msg) => {
54884
+ const cid = sessToClient.get(msg.sess);
54885
+ if (cid)
54886
+ rawSend("agent", msg, { clientId: cid });
54887
+ }
54888
+ });
54889
+ }
53865
54890
  hooks.pushHosts(presentedHosts());
53866
54891
  hooks.ownHostResolved(ownHostId());
53867
54892
  ensureLinkConfigWatch();
@@ -53915,8 +54940,26 @@ function stopControlLink() {
53915
54940
  }
53916
54941
 
53917
54942
  // ../../packages/host-core/dist/local-server.js
54943
+ import { timingSafeEqual as timingSafeEqual3 } from "node:crypto";
53918
54944
  init_dist2();
53919
54945
  var connSeq = 0;
54946
+ function tokenMatches(got, want) {
54947
+ if (!got)
54948
+ return false;
54949
+ const a = Buffer.from(got);
54950
+ const b = Buffer.from(want);
54951
+ return a.length === b.length && timingSafeEqual3(a, b);
54952
+ }
54953
+ function originAllowed(origin) {
54954
+ if (!origin)
54955
+ return true;
54956
+ try {
54957
+ const h = new URL(origin).hostname;
54958
+ return h === "127.0.0.1" || h === "localhost" || h === "::1" || h === "[::1]";
54959
+ } catch {
54960
+ return false;
54961
+ }
54962
+ }
53920
54963
  async function startLocalServer(opts) {
53921
54964
  const log3 = opts.log ?? (() => {
53922
54965
  });
@@ -53930,7 +54973,8 @@ async function startLocalServer(opts) {
53930
54973
  log3(`local listener on 127.0.0.1:${port}`);
53931
54974
  wss.on("connection", (ws, req) => {
53932
54975
  const url2 = new URL(req.url ?? "/", "http://127.0.0.1");
53933
- if (url2.searchParams.get("token") !== opts.token) {
54976
+ const origin = req.headers.origin ?? void 0;
54977
+ if (!originAllowed(origin) || !tokenMatches(url2.searchParams.get("token"), opts.token)) {
53934
54978
  try {
53935
54979
  ws.close(1008, "unauthorized");
53936
54980
  } catch {
@@ -53949,11 +54993,11 @@ async function startLocalServer(opts) {
53949
54993
  };
53950
54994
  const heldSessions = /* @__PURE__ */ new Set();
53951
54995
  let voiceControl = null;
53952
- const unregisterSink = registerAgentSink((msg, origin) => {
53953
- const isCron = origin === "cron" && (MIRRORED_AGENT_EVENTS.has(msg.type) || msg.type === "session:created");
53954
- if (origin !== loopbackOrigin && !isCron)
54996
+ const unregisterSink = registerAgentSink((msg, origin2) => {
54997
+ const isCron = origin2 === "cron" && (MIRRORED_AGENT_EVENTS.has(msg.type) || msg.type === "session:created");
54998
+ if (origin2 !== loopbackOrigin && !isCron)
53955
54999
  return;
53956
- if (origin === loopbackOrigin)
55000
+ if (origin2 === loopbackOrigin)
53957
55001
  voiceControl?.feedAgentEvent(msg);
53958
55002
  if (msg.type === "chat:echoed")
53959
55003
  return;
@@ -54060,6 +55104,8 @@ async function startLocalServer(opts) {
54060
55104
  const newId = await b.forkSession?.(msg.sessionId, { projectDir: msg.projectDir, upToMessageId: msg.upToMessageId, title: msg.title }).catch(() => null);
54061
55105
  if (newId)
54062
55106
  send2("agent", { type: "session:forked", clientRequestId: msg.clientRequestId, sessionId: newId, sourceSessionId: msg.sessionId, title: msg.title });
55107
+ else
55108
+ send2("agent", { type: "session:forkFailed", clientRequestId: msg.clientRequestId, sourceSessionId: msg.sessionId });
54063
55109
  break;
54064
55110
  }
54065
55111
  case "agent:context_usage":
@@ -54162,7 +55208,9 @@ async function startLocalServer(opts) {
54162
55208
  const { pid } = await openPty(
54163
55209
  // `local`: a loopback terminal is the user's OWN machine — never
54164
55210
  // reaped by the host-role teardown that runs in this same process.
54165
- { ptyId: p.ptyId, clientId: termClientId, shell: p.shell, args: p.args, cwd: p.cwd, cols: p.cols, rows: p.rows, env: p.env, hostRoot: opts.hostRoot, local: true },
55211
+ // hostRoot = the containment boundary (undefined when uncontained), so a
55212
+ // loopback terminal on a personal machine isn't clamped to a start dir.
55213
+ { ptyId: p.ptyId, clientId: termClientId, shell: p.shell, args: p.args, cwd: p.cwd, cols: p.cols, rows: p.rows, env: p.env, hostRoot: resolveContainmentRoot() ?? void 0, local: true },
54166
55214
  termSink
54167
55215
  );
54168
55216
  send2("term", { type: "term:opened", ptyId: p.ptyId, requestId: p.requestId, pid });
@@ -54194,13 +55242,94 @@ async function startLocalServer(opts) {
54194
55242
  }
54195
55243
 
54196
55244
  // ../../packages/host-core/dist/daemon-runtime.js
54197
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync12, unlinkSync as unlinkSync2, chmodSync as chmodSync3 } from "node:fs";
54198
- import { join as join30 } from "node:path";
54199
- import { homedir as homedir14 } from "node:os";
54200
- import { randomBytes as randomBytes3 } from "node:crypto";
55245
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync13, existsSync as existsSync16, mkdirSync as mkdirSync13, unlinkSync as unlinkSync2, chmodSync as chmodSync4 } from "node:fs";
55246
+ import { join as join32 } from "node:path";
55247
+ import { randomBytes as randomBytes4 } from "node:crypto";
55248
+ import { hostname as hostname4 } from "node:os";
55249
+
55250
+ // ../../packages/host-core/dist/device-identity.js
55251
+ import { join as join31 } from "node:path";
55252
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync12, renameSync as renameSync7, chmodSync as chmodSync3 } from "node:fs";
55253
+ var cached2 = null;
55254
+ function identityPath() {
55255
+ return join31(daemonDir(), "identity.json");
55256
+ }
55257
+ async function loadOrCreateDeviceIdentity() {
55258
+ if (cached2)
55259
+ return cached2;
55260
+ try {
55261
+ const stored = JSON.parse(readFileSync12(identityPath(), "utf-8"));
55262
+ if (stored?.privateJwk) {
55263
+ cached2 = await importIdentity(stored.privateJwk);
55264
+ return cached2;
55265
+ }
55266
+ } catch {
55267
+ }
55268
+ const kp = await generateIdentityKeyPair(true);
55269
+ const { privateJwk, publicRaw } = await exportIdentity(kp);
55270
+ const dir = daemonDir();
55271
+ if (!existsSync15(dir))
55272
+ mkdirSync12(dir, { recursive: true, mode: 448 });
55273
+ const tmp = `${identityPath()}.tmp`;
55274
+ writeFileSync12(tmp, JSON.stringify({ privateJwk, publicRaw, alg: "P256-ECDSA" }, null, 2), { mode: 384 });
55275
+ renameSync7(tmp, identityPath());
55276
+ try {
55277
+ chmodSync3(identityPath(), 384);
55278
+ } catch {
55279
+ }
55280
+ cached2 = kp;
55281
+ return kp;
55282
+ }
55283
+ var DEVICES_BASE = (apiOrigin) => `${apiOrigin.replace(/\/$/, "")}/api/core/tesseract/devices`;
55284
+ async function registerDeviceKey(opts) {
55285
+ try {
55286
+ const r = await fetch(`${DEVICES_BASE(opts.apiOrigin)}/key`, {
55287
+ method: "POST",
55288
+ headers: { "content-type": "application/json", authorization: `Bearer ${opts.accessToken}` },
55289
+ body: JSON.stringify({
55290
+ deviceId: opts.deviceId,
55291
+ identityPub: b64u(opts.publicRaw),
55292
+ alg: "P256-ECDSA",
55293
+ tlsFingerprint256: opts.tlsFingerprint256,
55294
+ label: opts.label
55295
+ })
55296
+ });
55297
+ return r.ok;
55298
+ } catch {
55299
+ return false;
55300
+ }
55301
+ }
55302
+ var PEER_TTL_MS = 6e4;
55303
+ var peerCache = null;
55304
+ async function refreshPeerKeys(apiOrigin, accessToken) {
55305
+ try {
55306
+ const r = await fetch(`${DEVICES_BASE(apiOrigin)}/keys`, { headers: { authorization: `Bearer ${accessToken}` } });
55307
+ if (!r.ok)
55308
+ return;
55309
+ const list = await r.json();
55310
+ const byDevice = /* @__PURE__ */ new Map();
55311
+ for (const k of list)
55312
+ byDevice.set(k.deviceId, k);
55313
+ peerCache = { at: Date.now(), byDevice };
55314
+ } catch {
55315
+ }
55316
+ }
55317
+ async function getPeerIdentityPub(opts) {
55318
+ const stale = !peerCache || Date.now() - peerCache.at > PEER_TTL_MS;
55319
+ if (stale || !peerCache?.byDevice.has(opts.deviceId))
55320
+ await refreshPeerKeys(opts.apiOrigin, opts.accessToken);
55321
+ const row = peerCache?.byDevice.get(opts.deviceId);
55322
+ if (!row || row.revokedAt)
55323
+ return null;
55324
+ try {
55325
+ return unb64u(row.identityPub);
55326
+ } catch {
55327
+ return null;
55328
+ }
55329
+ }
54201
55330
 
54202
55331
  // ../../packages/host-core/dist/power-node.js
54203
- import { spawn as spawn4 } from "node:child_process";
55332
+ import { spawn as spawn5 } from "node:child_process";
54204
55333
  import { platform } from "node:os";
54205
55334
  function createNodePowerController() {
54206
55335
  const active = /* @__PURE__ */ new Set();
@@ -54208,9 +55337,9 @@ function createNodePowerController() {
54208
55337
  function spawnInhibitor() {
54209
55338
  try {
54210
55339
  if (platform() === "darwin")
54211
- return spawn4("caffeinate", ["-i"], { stdio: "ignore" });
55340
+ return spawn5("caffeinate", ["-i"], { stdio: "ignore" });
54212
55341
  if (platform() === "linux") {
54213
- return spawn4("systemd-inhibit", ["--what=sleep", "--why=tesseract agent running", "--mode=block", "sleep", "infinity"], { stdio: "ignore" });
55342
+ return spawn5("systemd-inhibit", ["--what=sleep", "--why=tesseract agent running", "--mode=block", "sleep", "infinity"], { stdio: "ignore" });
54214
55343
  }
54215
55344
  } catch {
54216
55345
  }
@@ -54262,8 +55391,8 @@ async function runDaemon(opts = {}) {
54262
55391
  const VERSION = opts.version ?? process.env.CCB_DAEMON_VERSION ?? "dev";
54263
55392
  const TAG = opts.tag ?? "host-daemon";
54264
55393
  const log3 = (...args) => console.log(`[${TAG} ${(/* @__PURE__ */ new Date()).toISOString()}]`, ...args);
54265
- const pidfile = () => join30(daemonDir(), "daemon.lock");
54266
- const statusFile = () => join30(daemonDir(), "status.json");
55394
+ const pidfile = () => join32(daemonDir(), "daemon.lock");
55395
+ const statusFile = () => join32(daemonDir(), "status.json");
54267
55396
  const isAlive2 = (pid) => {
54268
55397
  try {
54269
55398
  process.kill(pid, 0);
@@ -54275,17 +55404,17 @@ async function runDaemon(opts = {}) {
54275
55404
  function acquireLock() {
54276
55405
  const f = pidfile();
54277
55406
  try {
54278
- if (existsSync15(f)) {
54279
- const prev = Number(readFileSync12(f, "utf-8").trim());
55407
+ if (existsSync16(f)) {
55408
+ const prev = Number(readFileSync13(f, "utf-8").trim());
54280
55409
  if (prev && prev !== process.pid && isAlive2(prev))
54281
55410
  return false;
54282
55411
  }
54283
55412
  } catch {
54284
55413
  }
54285
55414
  try {
54286
- if (!existsSync15(daemonDir()))
54287
- mkdirSync12(daemonDir(), { recursive: true, mode: 448 });
54288
- writeFileSync12(f, String(process.pid));
55415
+ if (!existsSync16(daemonDir()))
55416
+ mkdirSync13(daemonDir(), { recursive: true, mode: 448 });
55417
+ writeFileSync13(f, String(process.pid));
54289
55418
  } catch {
54290
55419
  }
54291
55420
  return true;
@@ -54293,15 +55422,15 @@ async function runDaemon(opts = {}) {
54293
55422
  function releaseLock() {
54294
55423
  try {
54295
55424
  const f = pidfile();
54296
- if (existsSync15(f) && Number(readFileSync12(f, "utf-8").trim()) === process.pid)
55425
+ if (existsSync16(f) && Number(readFileSync13(f, "utf-8").trim()) === process.pid)
54297
55426
  unlinkSync2(f);
54298
55427
  } catch {
54299
55428
  }
54300
55429
  }
54301
55430
  function writeStatus(s) {
54302
55431
  try {
54303
- writeFileSync12(statusFile(), JSON.stringify({ pid: process.pid, version: VERSION, execPath: process.execPath, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...localInfo ?? {}, ...s }));
54304
- chmodSync3(statusFile(), 384);
55432
+ writeFileSync13(statusFile(), JSON.stringify({ pid: process.pid, version: VERSION, execPath: process.execPath, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...localInfo ?? {}, ...s }));
55433
+ chmodSync4(statusFile(), 384);
54305
55434
  } catch {
54306
55435
  }
54307
55436
  }
@@ -54334,9 +55463,9 @@ async function runDaemon(opts = {}) {
54334
55463
  process.on("SIGTERM", () => shutdown(0));
54335
55464
  process.on("SIGINT", () => shutdown(0));
54336
55465
  process.env.CCB_HOST_DAEMON = "1";
54337
- const localToken = randomBytes3(24).toString("hex");
55466
+ const localToken = randomBytes4(24).toString("hex");
54338
55467
  try {
54339
- localServer = await startLocalServer({ hostRoot: process.env.CCB_HOST_ROOT ?? homedir14(), token: localToken, log: log3 });
55468
+ localServer = await startLocalServer({ hostRoot: resolveHostRoot(), token: localToken, log: log3 });
54340
55469
  localInfo = { localPort: localServer.port, localToken };
54341
55470
  } catch (e) {
54342
55471
  log3("local listener failed to start", e.message);
@@ -54351,6 +55480,15 @@ async function runDaemon(opts = {}) {
54351
55480
  const c = creds2;
54352
55481
  let accessToken = c.accessToken;
54353
55482
  const tokenSource = { getToken: () => accessToken, getUserId: () => c.userId, backendOrigin: c.backendOrigin };
55483
+ void (async () => {
55484
+ try {
55485
+ const id = await loadOrCreateDeviceIdentity();
55486
+ const ok = await registerDeviceKey({ apiOrigin: c.apiOrigin, accessToken, deviceId: c.deviceId, publicRaw: id.publicRaw, label: hostname4() });
55487
+ log3(ok ? "e2e identity registered" : "e2e identity registration not accepted (will retry on restart)");
55488
+ } catch (e) {
55489
+ log3("e2e identity registration failed (non-fatal)", e.message);
55490
+ }
55491
+ })();
54354
55492
  async function refresh() {
54355
55493
  try {
54356
55494
  const r = await fetch(`${c.apiOrigin}/auth/device-token/refresh`, {
@@ -54363,7 +55501,7 @@ async function runDaemon(opts = {}) {
54363
55501
  if (r.status === 401) {
54364
55502
  log3("device token no longer valid \u2014 dropping to local-only on restart");
54365
55503
  try {
54366
- unlinkSync2(join30(daemonDir(), "credentials.json"));
55504
+ unlinkSync2(join32(daemonDir(), "credentials.json"));
54367
55505
  } catch {
54368
55506
  }
54369
55507
  shutdown(0);
@@ -54396,7 +55534,20 @@ async function runDaemon(opts = {}) {
54396
55534
  pushTerm: () => {
54397
55535
  }
54398
55536
  };
54399
- startControlLink({ deviceId: c.deviceId, power: createNodePowerController(), tokenSource, version: VERSION }, hooks2);
55537
+ let e2e;
55538
+ if (process.env.CCB_E2E === "1") {
55539
+ try {
55540
+ const id = await loadOrCreateDeviceIdentity();
55541
+ e2e = {
55542
+ selfDeviceId: c.deviceId,
55543
+ identityPriv: id.privateKey,
55544
+ getPeerIdentityPub: (d) => getPeerIdentityPub({ apiOrigin: c.apiOrigin, accessToken, deviceId: d })
55545
+ };
55546
+ } catch (err) {
55547
+ log3("e2e responder init failed (staying plaintext)", err.message);
55548
+ }
55549
+ }
55550
+ startControlLink({ deviceId: c.deviceId, power: createNodePowerController(), tokenSource, version: VERSION, e2e }, hooks2);
54400
55551
  log3(`started \u2014 device ${c.deviceId}, user ${c.userId}, relay ${c.backendOrigin}, local :${localInfo?.localPort ?? "off"}, version ${VERSION}`);
54401
55552
  await refresh();
54402
55553
  timer = setInterval(() => {
@@ -54408,17 +55559,17 @@ async function runDaemon(opts = {}) {
54408
55559
  import { platform as platform2 } from "node:os";
54409
55560
 
54410
55561
  // ../../packages/host-core/dist/service/launchd.js
54411
- import { writeFileSync as writeFileSync13, existsSync as existsSync16, mkdirSync as mkdirSync13, unlinkSync as unlinkSync3 } from "node:fs";
54412
- import { dirname as dirname15, join as join31 } from "node:path";
54413
- import { homedir as homedir15, userInfo } from "node:os";
55562
+ import { writeFileSync as writeFileSync14, existsSync as existsSync17, mkdirSync as mkdirSync14, unlinkSync as unlinkSync3 } from "node:fs";
55563
+ import { dirname as dirname15, join as join33 } from "node:path";
55564
+ import { homedir as homedir14, userInfo } from "node:os";
54414
55565
 
54415
55566
  // ../../packages/host-core/dist/service/exec.js
54416
- import { execFile as execFile5, spawn as spawn5 } from "node:child_process";
55567
+ import { execFile as execFile5, spawn as spawn6 } from "node:child_process";
54417
55568
  import { promisify as promisify4 } from "node:util";
54418
55569
  var run = promisify4(execFile5);
54419
55570
  function runInherit(cmd, args) {
54420
55571
  return new Promise((resolve10) => {
54421
- const p = spawn5(cmd, args, { stdio: "inherit" });
55572
+ const p = spawn6(cmd, args, { stdio: "inherit" });
54422
55573
  p.on("error", () => resolve10());
54423
55574
  p.on("close", () => resolve10());
54424
55575
  });
@@ -54433,10 +55584,10 @@ function xml(s) {
54433
55584
  }
54434
55585
  function makeLaunchd(instanceId) {
54435
55586
  const label = SERVICE_LABEL_BASE + (instanceId ? `.${instanceId}` : "");
54436
- const plistPath = () => join31(homedir15(), "Library", "LaunchAgents", `${label}.plist`);
55587
+ const plistPath = () => join33(homedir14(), "Library", "LaunchAgents", `${label}.plist`);
54437
55588
  const domainTarget = () => `gui/${userInfo().uid}`;
54438
55589
  const serviceTarget = () => `${domainTarget()}/${label}`;
54439
- const logFile = () => join31(daemonDir(), "daemon.log");
55590
+ const logFile = () => join33(daemonDir(), "daemon.log");
54440
55591
  function buildPlist(spec) {
54441
55592
  const args = [spec.exec, ...spec.args].map((a) => ` <string>${xml(a)}</string>`).join("\n");
54442
55593
  const env = Object.entries(spec.env).map(([k, v]) => ` <key>${xml(k)}</key>
@@ -54476,9 +55627,9 @@ ${env}
54476
55627
  }
54477
55628
  return {
54478
55629
  async install(spec) {
54479
- mkdirSync13(dirname15(plistPath()), { recursive: true });
54480
- mkdirSync13(dirname15(spec.logPath), { recursive: true });
54481
- writeFileSync13(plistPath(), buildPlist(spec));
55630
+ mkdirSync14(dirname15(plistPath()), { recursive: true });
55631
+ mkdirSync14(dirname15(spec.logPath), { recursive: true });
55632
+ writeFileSync14(plistPath(), buildPlist(spec));
54482
55633
  await run("launchctl", ["bootout", serviceTarget()]).catch(() => {
54483
55634
  });
54484
55635
  await run("launchctl", ["bootstrap", domainTarget(), plistPath()]);
@@ -54490,7 +55641,7 @@ ${env}
54490
55641
  async uninstall() {
54491
55642
  await run("launchctl", ["bootout", serviceTarget()]).catch(() => {
54492
55643
  });
54493
- if (existsSync16(plistPath())) {
55644
+ if (existsSync17(plistPath())) {
54494
55645
  try {
54495
55646
  unlinkSync3(plistPath());
54496
55647
  } catch {
@@ -54501,7 +55652,7 @@ ${env}
54501
55652
  await this.install(spec);
54502
55653
  },
54503
55654
  async isInstalled() {
54504
- return existsSync16(plistPath());
55655
+ return existsSync17(plistPath());
54505
55656
  },
54506
55657
  async start() {
54507
55658
  await run("launchctl", ["bootstrap", domainTarget(), plistPath()]).catch(() => {
@@ -54518,7 +55669,7 @@ ${env}
54518
55669
  },
54519
55670
  async logs(follow) {
54520
55671
  const f = logFile();
54521
- if (!existsSync16(f)) {
55672
+ if (!existsSync17(f)) {
54522
55673
  console.log(`No log yet at ${f} \u2014 has the service started?`);
54523
55674
  return;
54524
55675
  }
@@ -54528,17 +55679,17 @@ ${env}
54528
55679
  }
54529
55680
 
54530
55681
  // ../../packages/host-core/dist/service/systemd.js
54531
- import { writeFileSync as writeFileSync14, existsSync as existsSync17, mkdirSync as mkdirSync14, unlinkSync as unlinkSync4 } from "node:fs";
54532
- import { dirname as dirname16, join as join32 } from "node:path";
54533
- import { homedir as homedir16, userInfo as userInfo2 } from "node:os";
55682
+ import { writeFileSync as writeFileSync15, existsSync as existsSync18, mkdirSync as mkdirSync15, unlinkSync as unlinkSync4 } from "node:fs";
55683
+ import { dirname as dirname16, join as join34 } from "node:path";
55684
+ import { homedir as homedir15, userInfo as userInfo2 } from "node:os";
54534
55685
  function shq(s) {
54535
55686
  return `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
54536
55687
  }
54537
55688
  function makeSystemd(instanceId) {
54538
55689
  const unit = `tesseract-host${instanceId ? `-${instanceId}` : ""}.service`;
54539
55690
  const unitPath = () => {
54540
- const base = process.env.XDG_CONFIG_HOME || join32(homedir16(), ".config");
54541
- return join32(base, "systemd", "user", unit);
55691
+ const base = process.env.XDG_CONFIG_HOME || join34(homedir15(), ".config");
55692
+ return join34(base, "systemd", "user", unit);
54542
55693
  };
54543
55694
  function buildUnit(spec) {
54544
55695
  const execStart = [spec.exec, ...spec.args].map(shq).join(" ");
@@ -54563,9 +55714,9 @@ WantedBy=default.target
54563
55714
  }
54564
55715
  return {
54565
55716
  async install(spec) {
54566
- mkdirSync14(dirname16(unitPath()), { recursive: true });
54567
- mkdirSync14(dirname16(spec.logPath), { recursive: true });
54568
- writeFileSync14(unitPath(), buildUnit(spec));
55717
+ mkdirSync15(dirname16(unitPath()), { recursive: true });
55718
+ mkdirSync15(dirname16(spec.logPath), { recursive: true });
55719
+ writeFileSync15(unitPath(), buildUnit(spec));
54569
55720
  await run("loginctl", ["enable-linger", userInfo2().username]).catch(() => {
54570
55721
  });
54571
55722
  await run("systemctl", ["--user", "daemon-reload"]).catch(() => {
@@ -54577,7 +55728,7 @@ WantedBy=default.target
54577
55728
  async uninstall() {
54578
55729
  await run("systemctl", ["--user", "disable", "--now", unit]).catch(() => {
54579
55730
  });
54580
- if (existsSync17(unitPath())) {
55731
+ if (existsSync18(unitPath())) {
54581
55732
  try {
54582
55733
  unlinkSync4(unitPath());
54583
55734
  } catch {
@@ -54590,7 +55741,7 @@ WantedBy=default.target
54590
55741
  await this.install(spec);
54591
55742
  },
54592
55743
  async isInstalled() {
54593
- return existsSync17(unitPath());
55744
+ return existsSync18(unitPath());
54594
55745
  },
54595
55746
  async start() {
54596
55747
  await run("systemctl", ["--user", "start", unit]);
@@ -54608,25 +55759,28 @@ WantedBy=default.target
54608
55759
  }
54609
55760
 
54610
55761
  // ../../packages/host-core/dist/service/windows.js
54611
- import { writeFileSync as writeFileSync15, existsSync as existsSync18, mkdirSync as mkdirSync15, unlinkSync as unlinkSync5 } from "node:fs";
54612
- import { dirname as dirname17, join as join33 } from "node:path";
55762
+ import { writeFileSync as writeFileSync16, existsSync as existsSync19, mkdirSync as mkdirSync16, unlinkSync as unlinkSync5 } from "node:fs";
55763
+ import { dirname as dirname17, join as join35 } from "node:path";
54613
55764
  function makeWindowsTask(instanceId) {
54614
55765
  const task = `Fineguide\\TesseractHost${instanceId ? `-${instanceId}` : ""}`;
54615
- const wrapperPath = () => join33(daemonDir(), `run-host${instanceId ? `-${instanceId}` : ""}.cmd`);
54616
- const logFile = () => join33(daemonDir(), "daemon.log");
55766
+ const wrapperPath = () => join35(daemonDir(), `run-host${instanceId ? `-${instanceId}` : ""}.cmd`);
55767
+ const logFile = () => join35(daemonDir(), "daemon.log");
55768
+ const oneLine2 = (s) => s.replace(/[\r\n]+/g, " ");
55769
+ const envVal = (v) => oneLine2(v).replace(/%/g, "%%").replace(/"/g, "");
55770
+ const cmdArg = (a) => `"${oneLine2(a).replace(/"/g, "")}"`;
54617
55771
  function buildWrapper(spec) {
54618
- const env = Object.entries(spec.env).map(([k, v]) => `set "${k}=${v}"`).join("\r\n");
54619
- const cmd = [spec.exec, ...spec.args].map((a) => `"${a}"`).join(" ");
55772
+ const env = Object.entries(spec.env).map(([k, v]) => `set "${envVal(k)}=${envVal(v)}"`).join("\r\n");
55773
+ const cmd = [spec.exec, ...spec.args].map(cmdArg).join(" ");
54620
55774
  return `@echo off\r
54621
55775
  ${env}\r
54622
- start "" /b ${cmd} >> "${spec.logPath}" 2>&1\r
55776
+ start "" /b ${cmd} >> "${oneLine2(spec.logPath).replace(/"/g, "")}" 2>&1\r
54623
55777
  `;
54624
55778
  }
54625
55779
  return {
54626
55780
  async install(spec) {
54627
- mkdirSync15(dirname17(spec.logPath), { recursive: true });
54628
- mkdirSync15(dirname17(wrapperPath()), { recursive: true });
54629
- writeFileSync15(wrapperPath(), buildWrapper(spec));
55781
+ mkdirSync16(dirname17(spec.logPath), { recursive: true });
55782
+ mkdirSync16(dirname17(wrapperPath()), { recursive: true });
55783
+ writeFileSync16(wrapperPath(), buildWrapper(spec));
54630
55784
  await run("schtasks", ["/Create", "/TN", task, "/SC", "ONLOGON", "/RL", "LIMITED", "/F", "/TR", `"${wrapperPath()}"`]);
54631
55785
  await run("schtasks", ["/Run", "/TN", task]).catch(() => {
54632
55786
  });
@@ -54634,7 +55788,7 @@ start "" /b ${cmd} >> "${spec.logPath}" 2>&1\r
54634
55788
  async uninstall() {
54635
55789
  await run("schtasks", ["/Delete", "/TN", task, "/F"]).catch(() => {
54636
55790
  });
54637
- if (existsSync18(wrapperPath())) {
55791
+ if (existsSync19(wrapperPath())) {
54638
55792
  try {
54639
55793
  unlinkSync5(wrapperPath());
54640
55794
  } catch {
@@ -54664,7 +55818,7 @@ start "" /b ${cmd} >> "${spec.logPath}" 2>&1\r
54664
55818
  },
54665
55819
  async logs(follow) {
54666
55820
  const f = logFile();
54667
- if (!existsSync18(f)) {
55821
+ if (!existsSync19(f)) {
54668
55822
  console.log(`No log yet at ${f} \u2014 has the task run?`);
54669
55823
  return;
54670
55824
  }
@@ -54691,15 +55845,30 @@ function getServiceController(instanceId) {
54691
55845
  // src/auth.ts
54692
55846
  var import_qrcode_terminal = __toESM(require_main(), 1);
54693
55847
  import { randomUUID as randomUUID8 } from "node:crypto";
54694
- import { existsSync as existsSync19, rmSync } from "node:fs";
54695
- import { join as join34 } from "node:path";
55848
+ import { existsSync as existsSync20, rmSync } from "node:fs";
55849
+ import { join as join36 } from "node:path";
54696
55850
 
54697
55851
  // src/config.ts
54698
- import { hostname as hostname4 } from "node:os";
55852
+ import { hostname as hostname5 } from "node:os";
54699
55853
  var DEFAULT_API = (process.env.CCB_TESSERACT_API ?? "https://app.tesseract.dev").replace(/\/$/, "");
54700
55854
  function defaultRelay(api) {
54701
55855
  return (process.env.CCB_BACKEND_ORIGIN ?? api).replace(/\/$/, "");
54702
55856
  }
55857
+ function isAllowedOrigin(origin) {
55858
+ let h, proto;
55859
+ try {
55860
+ const u = new URL(origin);
55861
+ h = u.hostname;
55862
+ proto = u.protocol;
55863
+ } catch {
55864
+ return false;
55865
+ }
55866
+ if (h === "localhost" || h === "127.0.0.1" || h === "::1") return true;
55867
+ if (proto !== "https:" && proto !== "wss:") return false;
55868
+ if (h === "tesseract.dev" || h.endsWith(".tesseract.dev")) return true;
55869
+ const extra = (process.env.CCB_ALLOWED_API_HOSTS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
55870
+ return extra.some((a) => h === a || h.endsWith(`.${a}`));
55871
+ }
54703
55872
  function decodeJwtSub(token) {
54704
55873
  try {
54705
55874
  const part = token.split(".")[1];
@@ -54711,7 +55880,7 @@ function decodeJwtSub(token) {
54711
55880
  }
54712
55881
  }
54713
55882
  function defaultLabel() {
54714
- return hostname4() || "server";
55883
+ return hostname5() || "server";
54715
55884
  }
54716
55885
 
54717
55886
  // src/auth.ts
@@ -54808,6 +55977,15 @@ function persist2(api, relay, deviceId2, dt) {
54808
55977
  async function login(opts) {
54809
55978
  const api = (opts.api ?? DEFAULT_API).replace(/\/$/, "");
54810
55979
  const relay = (opts.relay ?? defaultRelay(api)).replace(/\/$/, "");
55980
+ if (!opts.allowCustomApi) {
55981
+ const bad = [api, relay].find((o) => !isAllowedOrigin(o));
55982
+ if (bad) {
55983
+ console.error(`Refusing to use a non-default server origin: ${bad}`);
55984
+ console.error("If this is intentional (self-hosted), re-run with --allow-custom-api,");
55985
+ console.error("or set CCB_ALLOWED_API_HOSTS=your.host to allowlist it permanently.");
55986
+ process.exit(1);
55987
+ }
55988
+ }
54811
55989
  const deviceId2 = readCredentials()?.deviceId ?? process.env.CCB_DEVICE_ID ?? randomUUID8().slice(0, 8);
54812
55990
  const label = opts.name ?? defaultLabel();
54813
55991
  if (opts.token) {
@@ -54869,8 +56047,8 @@ async function logout() {
54869
56047
  }
54870
56048
  for (const f of ["credentials.json", "status.json", "daemon.lock"]) {
54871
56049
  try {
54872
- const p = join34(daemonDir(), f);
54873
- if (existsSync19(p)) rmSync(p);
56050
+ const p = join36(daemonDir(), f);
56051
+ if (existsSync20(p)) rmSync(p);
54874
56052
  } catch {
54875
56053
  }
54876
56054
  }
@@ -54890,9 +56068,12 @@ function whoami() {
54890
56068
  }
54891
56069
 
54892
56070
  // src/self-update.ts
54893
- import { spawn as spawn6 } from "node:child_process";
56071
+ import { spawn as spawn7 } from "node:child_process";
56072
+ import { createPublicKey, verify as cryptoVerify } from "node:crypto";
54894
56073
  var RESTART_EXIT_CODE = 75;
54895
- var REGISTRY_URL = "https://registry.npmjs.org/tesseract-agent/latest";
56074
+ var REGISTRY_BASE = "https://registry.npmjs.org/tesseract-agent";
56075
+ var REGISTRY_URL = `${REGISTRY_BASE}/latest`;
56076
+ var NPM_SIGNING_KEY_SPKI_B64 = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEY6Ya7W++7aUPzvMTrezH6Ycx3c+HOKYCcNGybJZSCJq/fd7Qa8uuAKtdIkUQtQiEKERhAmE5lMMJhP8OkDOa2g==";
54896
56077
  var FIRST_CHECK_MS = Number(process.env.CCB_UPDATE_FIRST_MS) || 3 * 6e4;
54897
56078
  var INTERVAL_MS = Number(process.env.CCB_UPDATE_INTERVAL_MS) || 6 * 60 * 6e4;
54898
56079
  function disabled() {
@@ -54930,13 +56111,39 @@ async function fetchLatest() {
54930
56111
  clearTimeout(t);
54931
56112
  }
54932
56113
  }
56114
+ async function fetchVersionDist(version2) {
56115
+ const ac = new AbortController();
56116
+ const t = setTimeout(() => ac.abort(), 15e3);
56117
+ try {
56118
+ const r = await fetch(`${REGISTRY_BASE}/${encodeURIComponent(version2)}`, { headers: { accept: "application/json" }, signal: ac.signal });
56119
+ if (!r.ok) return void 0;
56120
+ const j = await r.json();
56121
+ const dist = j.dist;
56122
+ const s = dist?.signatures?.[0];
56123
+ return { integrity: dist?.integrity, sig: s?.sig, keyid: s?.keyid };
56124
+ } catch {
56125
+ return void 0;
56126
+ } finally {
56127
+ clearTimeout(t);
56128
+ }
56129
+ }
56130
+ function verifyNpmSignature(version2, integrity, sig) {
56131
+ if (!integrity || !sig) return false;
56132
+ try {
56133
+ const key2 = createPublicKey({ key: Buffer.from(NPM_SIGNING_KEY_SPKI_B64, "base64"), format: "der", type: "spki" });
56134
+ const message = Buffer.from(`tesseract-agent@${version2}:${integrity}`);
56135
+ return cryptoVerify("sha256", message, { key: key2, dsaEncoding: "der" }, Buffer.from(sig, "base64"));
56136
+ } catch {
56137
+ return false;
56138
+ }
56139
+ }
54933
56140
  function installVersion(target) {
54934
56141
  return new Promise((resolve10) => {
54935
56142
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
54936
56143
  log2(`installing tesseract-agent@${target} \u2026`);
54937
56144
  let child;
54938
56145
  try {
54939
- child = spawn6(npm, ["install", "-g", `tesseract-agent@${target}`], { stdio: ["ignore", "inherit", "inherit"] });
56146
+ child = spawn7(npm, ["install", "-g", `tesseract-agent@${target}`], { stdio: ["ignore", "inherit", "inherit"] });
54940
56147
  } catch (e) {
54941
56148
  log2("npm spawn threw:", e instanceof Error ? e.message : String(e));
54942
56149
  resolve10(false);
@@ -54965,6 +56172,12 @@ async function checkOnce(current) {
54965
56172
  const latest = await fetchLatest();
54966
56173
  if (!latest || !isNewer(latest, current)) return;
54967
56174
  log2(`update available: ${current} \u2192 ${latest}`);
56175
+ const dist = await fetchVersionDist(latest);
56176
+ if (!verifyNpmSignature(latest, dist?.integrity, dist?.sig)) {
56177
+ log2(`REFUSING update to ${latest}: registry signature did not verify \u2014 staying on ${current}`);
56178
+ return;
56179
+ }
56180
+ log2(`registry signature verified for ${latest} (keyid ${dist?.keyid ?? "?"})`);
54968
56181
  if (await installVersion(latest)) {
54969
56182
  log2("restarting on the new version (supervisor re-spawns) \u2026");
54970
56183
  process.exit(RESTART_EXIT_CODE);
@@ -54993,9 +56206,9 @@ function startSelfUpdater(current) {
54993
56206
  }
54994
56207
 
54995
56208
  // src/service.ts
54996
- import { join as join35 } from "node:path";
56209
+ import { join as join37 } from "node:path";
54997
56210
  import { platform as platform3 } from "node:os";
54998
- import { existsSync as existsSync20, readFileSync as readFileSync13 } from "node:fs";
56211
+ import { existsSync as existsSync21, readFileSync as readFileSync14 } from "node:fs";
54999
56212
  function controller(instanceId) {
55000
56213
  const c = getServiceController(instanceId);
55001
56214
  if (!c) {
@@ -55008,7 +56221,7 @@ function buildSpec(cliEntry) {
55008
56221
  exec: process.execPath,
55009
56222
  args: [cliEntry, "run"],
55010
56223
  env: { CCB_DAEMON_DIR: daemonDir() },
55011
- logPath: join35(daemonDir(), "daemon.log")
56224
+ logPath: join37(daemonDir(), "daemon.log")
55012
56225
  };
55013
56226
  }
55014
56227
  var isAlive = (pid) => {
@@ -55020,10 +56233,10 @@ var isAlive = (pid) => {
55020
56233
  }
55021
56234
  };
55022
56235
  function runningDaemonInfo() {
55023
- const lock = join35(daemonDir(), "daemon.lock");
56236
+ const lock = join37(daemonDir(), "daemon.lock");
55024
56237
  try {
55025
- if (!existsSync20(lock)) return null;
55026
- const pid = Number(readFileSync13(lock, "utf-8").trim());
56238
+ if (!existsSync21(lock)) return null;
56239
+ const pid = Number(readFileSync14(lock, "utf-8").trim());
55027
56240
  if (!pid || !isAlive(pid)) return null;
55028
56241
  return { pid, device: readCredentials()?.deviceId };
55029
56242
  } catch {
@@ -55059,8 +56272,8 @@ async function logs(instanceId, follow) {
55059
56272
  // src/cli.ts
55060
56273
  function pkgVersion() {
55061
56274
  try {
55062
- const p = join36(dirname18(fileURLToPath2(import.meta.url)), "..", "package.json");
55063
- return JSON.parse(readFileSync14(p, "utf8")).version ?? "0.0.0";
56275
+ const p = join38(dirname18(fileURLToPath2(import.meta.url)), "..", "package.json");
56276
+ return JSON.parse(readFileSync15(p, "utf8")).version ?? "0.0.0";
55064
56277
  } catch {
55065
56278
  return "0.0.0";
55066
56279
  }
@@ -55116,7 +56329,7 @@ async function superviseDaemon(cliEntry) {
55116
56329
  let stopping = false;
55117
56330
  let child = null;
55118
56331
  const spawnWorker = () => {
55119
- const c = spawn7(process.execPath, [cliEntry, "__worker"], { stdio: "inherit", env: process.env });
56332
+ const c = spawn8(process.execPath, [cliEntry, "__worker"], { stdio: "inherit", env: process.env });
55120
56333
  child = c;
55121
56334
  let settled = false;
55122
56335
  const settle = (fn) => {
@@ -55322,7 +56535,7 @@ async function main() {
55322
56535
  process.exit(1);
55323
56536
  }
55324
56537
  instance = flags.instance;
55325
- process.env.CCB_DAEMON_DIR = join36(homedir17(), ".tesseract", `daemon-${instance}`);
56538
+ process.env.CCB_DAEMON_DIR = join38(homedir16(), ".tesseract", `daemon-${instance}`);
55326
56539
  }
55327
56540
  const force = flags.yes === true || flags.force === true;
55328
56541
  const cliEntry = fileURLToPath2(import.meta.url);
@@ -55332,7 +56545,8 @@ async function main() {
55332
56545
  api: typeof flags.api === "string" ? flags.api : void 0,
55333
56546
  relay: typeof flags.relay === "string" ? flags.relay : void 0,
55334
56547
  token: typeof flags.token === "string" ? flags.token : void 0,
55335
- name: typeof flags.name === "string" ? flags.name : void 0
56548
+ name: typeof flags.name === "string" ? flags.name : void 0,
56549
+ allowCustomApi: flags["allow-custom-api"] === true
55336
56550
  });
55337
56551
  break;
55338
56552
  case "logout":