pwragent 1.3.10 → 1.3.11

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.
@@ -2274,7 +2274,7 @@ var require_websocket = __commonJS({
2274
2274
  var http2 = __require("http");
2275
2275
  var net2 = __require("net");
2276
2276
  var tls = __require("tls");
2277
- var { randomBytes: randomBytes2, createHash: createHash7 } = __require("crypto");
2277
+ var { randomBytes: randomBytes2, createHash: createHash9 } = __require("crypto");
2278
2278
  var { Duplex, Readable } = __require("stream");
2279
2279
  var { URL: URL2 } = __require("url");
2280
2280
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -2955,7 +2955,7 @@ var require_websocket = __commonJS({
2955
2955
  abortHandshake(websocket, socket, "Invalid Upgrade header");
2956
2956
  return;
2957
2957
  }
2958
- const digest = createHash7("sha1").update(key + GUID).digest("base64");
2958
+ const digest = createHash9("sha1").update(key + GUID).digest("base64");
2959
2959
  if (res.headers["sec-websocket-accept"] !== digest) {
2960
2960
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
2961
2961
  return;
@@ -3324,7 +3324,7 @@ var require_websocket_server = __commonJS({
3324
3324
  var EventEmitter = __require("events");
3325
3325
  var http2 = __require("http");
3326
3326
  var { Duplex } = __require("stream");
3327
- var { createHash: createHash7 } = __require("crypto");
3327
+ var { createHash: createHash9 } = __require("crypto");
3328
3328
  var extension2 = require_extension();
3329
3329
  var PerMessageDeflate2 = require_permessage_deflate();
3330
3330
  var subprotocol2 = require_subprotocol();
@@ -3631,7 +3631,7 @@ var require_websocket_server = __commonJS({
3631
3631
  );
3632
3632
  }
3633
3633
  if (this._state > RUNNING) return abortHandshake(socket, 503);
3634
- const digest = createHash7("sha1").update(key + GUID).digest("base64");
3634
+ const digest = createHash9("sha1").update(key + GUID).digest("base64");
3635
3635
  const headers = [
3636
3636
  "HTTP/1.1 101 Switching Protocols",
3637
3637
  "Upgrade: websocket",
@@ -9349,6 +9349,15 @@ function isFederationInstanceId(value) {
9349
9349
  }
9350
9350
  return true;
9351
9351
  }
9352
+ function isRemoteFederationTarget(target) {
9353
+ return target.scope === "remote";
9354
+ }
9355
+ function federationTargetKey(target) {
9356
+ return isRemoteFederationTarget(target) ? `remote:${target.instanceId}` : "local";
9357
+ }
9358
+ function federatedThreadIdentityKey(ref) {
9359
+ return `${federationTargetKey(ref.target)}:${ref.backend}:${ref.threadId}`;
9360
+ }
9352
9361
 
9353
9362
  // ../shared/src/contracts/messaging-tools.ts
9354
9363
  var PWRAGENT_MESSAGING_TOOL_NAMESPACE = "pwragent_messaging";
@@ -9372,6 +9381,56 @@ var PWRAGENT_MESSAGING_CALLABLE_OPERATION_NAMES = [
9372
9381
  ...PWRAGENT_MESSAGING_LEGACY_OPERATION_NAMES
9373
9382
  ];
9374
9383
 
9384
+ // ../shared/src/contracts/navigation.ts
9385
+ var WORKTREE_IMAGE_MAX_BYTES = 16 * 1024 * 1024;
9386
+ function buildThreadIdentityKey(backend, threadId) {
9387
+ return `${backend}:${threadId}`;
9388
+ }
9389
+ var NAVIGATION_QUERY_PROTOCOL_VERSION = 2;
9390
+ var NAVIGATION_QUERY_MAX_PAGE_ROWS = 100;
9391
+ var NAVIGATION_QUERY_MAX_RESULT_BYTES = 252 * 1024;
9392
+
9393
+ // ../shared/src/navigation-star-map-filters.ts
9394
+ var NAVIGATION_STAR_MAP_FACETS = {
9395
+ attention: ["attention", "approval", "pr", "unpushed"],
9396
+ pinned: ["pinned"],
9397
+ agent: ["agent"]
9398
+ };
9399
+ function passesNavigationStarMapFilters(signals, selection, exceptFacet) {
9400
+ for (const [facet, keys] of Object.entries(NAVIGATION_STAR_MAP_FACETS)) {
9401
+ if (facet === exceptFacet) continue;
9402
+ let hasInclude = false;
9403
+ let matchedInclude = false;
9404
+ for (const key of keys) {
9405
+ if (selection[key] === "exclude" && signals[key]) return false;
9406
+ if (selection[key] === "include") {
9407
+ hasInclude = true;
9408
+ matchedInclude ||= signals[key];
9409
+ }
9410
+ }
9411
+ if (hasInclude && !matchedInclude) return false;
9412
+ }
9413
+ return true;
9414
+ }
9415
+ function countNavigationStarMapFacets(signals, selection) {
9416
+ const counts = {
9417
+ matches: { attention: 0, approval: 0, pr: 0, unpushed: 0, pinned: 0, agent: 0 },
9418
+ active: 0,
9419
+ unread: 0
9420
+ };
9421
+ for (const signal of signals) {
9422
+ for (const [facet, keys] of Object.entries(NAVIGATION_STAR_MAP_FACETS)) {
9423
+ if (!passesNavigationStarMapFilters(signal, selection, facet)) continue;
9424
+ for (const key of keys) if (signal[key]) counts.matches[key] += 1;
9425
+ if (facet === "attention") {
9426
+ if (signal.active) counts.active += 1;
9427
+ if (signal.unread) counts.unread += 1;
9428
+ }
9429
+ }
9430
+ }
9431
+ return counts;
9432
+ }
9433
+
9375
9434
  // ../shared/src/contracts/thread-todos.ts
9376
9435
  var THREAD_TODO_TITLE_MAX_LENGTH = 120;
9377
9436
  var THREAD_TODO_ORIGIN_ID_MAX_LENGTH = 200;
@@ -9385,7 +9444,40 @@ function threadTodoMessageOriginFromUnknown(value) {
9385
9444
  return { id, ...title ? { title } : {} };
9386
9445
  }
9387
9446
 
9447
+ // ../shared/src/thread-pins.ts
9448
+ var KEEP_AT_TOP_RANK_BOUNDARY = -(2 ** 39);
9449
+ var KEEP_AT_TOP_RANK_BASE = -(2 ** 40);
9450
+ function comparePinnedThreads(left, right) {
9451
+ const rankComparison = comparePinRanks(left.pinnedRank, right.pinnedRank);
9452
+ if (rankComparison !== 0) return rankComparison;
9453
+ const updatedComparison = (right.updatedAt ?? 0) - (left.updatedAt ?? 0);
9454
+ if (updatedComparison !== 0) return updatedComparison;
9455
+ return left.id.localeCompare(right.id);
9456
+ }
9457
+ function comparePinRanks(left, right) {
9458
+ const leftNumber = parsePinRank(left);
9459
+ const rightNumber = parsePinRank(right);
9460
+ if (leftNumber !== rightNumber) return leftNumber - rightNumber;
9461
+ return (left ?? "").localeCompare(right ?? "");
9462
+ }
9463
+ function parsePinRank(rank) {
9464
+ if (!rank?.trim()) return Number.POSITIVE_INFINITY;
9465
+ const parsed = Number(rank);
9466
+ return Number.isFinite(parsed) ? parsed : Number.POSITIVE_INFINITY;
9467
+ }
9468
+
9388
9469
  // ../shared/src/directory-navigation.ts
9470
+ function pathBaseName(value) {
9471
+ if (!value) {
9472
+ return value ?? "";
9473
+ }
9474
+ const normalized = value.replace(/[\\/]+$/, "");
9475
+ const parts = normalized.split(/[\\/]/).filter(Boolean);
9476
+ return parts.at(-1) ?? normalized;
9477
+ }
9478
+ function canonicalizeNavigationPath(value) {
9479
+ return value.replace(/\\/g, "/").replace(/\/+$/, "");
9480
+ }
9389
9481
  function normalizeGitOriginUrl(value) {
9390
9482
  const trimmed = value?.trim();
9391
9483
  if (!trimmed) {
@@ -9403,6 +9495,91 @@ function normalizeGitOriginUrl(value) {
9403
9495
  const normalized = candidate.replace(/\.git$/i, "").replace(/^ssh\//i, "").replace(/^\/+/, "").replace(/\/+$/, "").toLowerCase();
9404
9496
  return normalized || void 0;
9405
9497
  }
9498
+ function isInternalDirectoryLabel(value) {
9499
+ return Boolean(value?.startsWith("directory:") || value?.startsWith("workspace:"));
9500
+ }
9501
+ function displayDirectoryLabel(params) {
9502
+ const label = params.label?.trim();
9503
+ if (label && !isInternalDirectoryLabel(label)) {
9504
+ return label;
9505
+ }
9506
+ if (params.kind === "workspace") {
9507
+ return "Workspaces";
9508
+ }
9509
+ return pathBaseName(params.path) || label || "Directory";
9510
+ }
9511
+ function matchScratchProjectsRoot(value) {
9512
+ const scratchWorkspaceRootMatch = value.match(
9513
+ /^(.*[\\/]\.pwrag(?:ent|nt)(?:[\\/]profiles[\\/][^\\/]+)?[\\/]projects)$/
9514
+ );
9515
+ if (scratchWorkspaceRootMatch) {
9516
+ return scratchWorkspaceRootMatch[1];
9517
+ }
9518
+ const scratchWorkspaceMatch = value.match(
9519
+ /^(.*[\\/]\.pwrag(?:ent|nt)(?:[\\/]profiles[\\/][^\\/]+)?[\\/]projects)[\\/][^\\/]+$/
9520
+ );
9521
+ return scratchWorkspaceMatch?.[1];
9522
+ }
9523
+ function matchCodexChatsRoot(value) {
9524
+ const codexChatsRootMatch = value.match(
9525
+ /^(.*[\\/]Documents[\\/]Codex)(?:[\\/]\d{4}-\d{2}-\d{2}(?:[\\/].*)?)?$/
9526
+ );
9527
+ return codexChatsRootMatch?.[1];
9528
+ }
9529
+ function classifyDirectory(directory) {
9530
+ const path11 = canonicalizeNavigationPath(directory.path);
9531
+ const scratchProjectsRoot = matchScratchProjectsRoot(path11);
9532
+ if (scratchProjectsRoot) {
9533
+ return {
9534
+ key: `workspace:${scratchProjectsRoot}`,
9535
+ kind: "workspace",
9536
+ label: "Workspaces",
9537
+ path: scratchProjectsRoot
9538
+ };
9539
+ }
9540
+ const codexChatsRoot = matchCodexChatsRoot(path11);
9541
+ if (codexChatsRoot) {
9542
+ return {
9543
+ key: `directory:${codexChatsRoot}`,
9544
+ kind: "directory",
9545
+ label: "Codex Chats",
9546
+ path: codexChatsRoot
9547
+ };
9548
+ }
9549
+ const repoWorktreeMatch = path11.match(
9550
+ /^(.*)[\\/]\.worktrees[\\/][^\\/]+(?:[\\/].*)?$/
9551
+ );
9552
+ if (directory.kind === "worktree" && repoWorktreeMatch) {
9553
+ const canonicalPath = repoWorktreeMatch[1];
9554
+ return {
9555
+ key: `directory:${canonicalPath}`,
9556
+ kind: "directory",
9557
+ label: pathBaseName(canonicalPath),
9558
+ path: canonicalPath
9559
+ };
9560
+ }
9561
+ const codexWorktreeMatch = path11.match(
9562
+ /^(?:[A-Za-z]:)?[\\/].*[\\/]\.codex(?:[\\/]profiles[\\/][^\\/]+)?[\\/]worktrees[\\/][^\\/]+[\\/]([^\\/]+)(?:[\\/].*)?$/
9563
+ );
9564
+ if (codexWorktreeMatch) {
9565
+ return {
9566
+ key: `directory:${path11}`,
9567
+ kind: "directory",
9568
+ label: codexWorktreeMatch[1],
9569
+ path: path11
9570
+ };
9571
+ }
9572
+ return {
9573
+ key: `directory:${path11}`,
9574
+ kind: "directory",
9575
+ label: displayDirectoryLabel({
9576
+ kind: "directory",
9577
+ label: directory.label,
9578
+ path: path11
9579
+ }),
9580
+ path: path11
9581
+ };
9582
+ }
9406
9583
 
9407
9584
  // ../shared/src/nested-worktree-pathspec.ts
9408
9585
  var EXCLUDE_MAGIC = ":(exclude,top,literal)";
@@ -9486,6 +9663,186 @@ async function readStatusWithoutNestedWorktrees(options) {
9486
9663
  return await options.git([...options.statusArgs, ...nestedWorktreePathspecArgs(excludes)]);
9487
9664
  }
9488
9665
 
9666
+ // ../shared/src/subthreads.ts
9667
+ function sortSubthreadSummaries(parent, children) {
9668
+ const order = new Map(
9669
+ (parent.subthreadOrder ?? []).map((threadId, index) => [threadId, index])
9670
+ );
9671
+ return [...children].sort((left, right) => {
9672
+ const leftRank = order.get(left.id);
9673
+ const rightRank = order.get(right.id);
9674
+ if (leftRank !== void 0 || rightRank !== void 0) {
9675
+ return (leftRank ?? Number.MAX_SAFE_INTEGER) - (rightRank ?? Number.MAX_SAFE_INTEGER);
9676
+ }
9677
+ return (right.createdAt ?? 0) - (left.createdAt ?? 0);
9678
+ });
9679
+ }
9680
+
9681
+ // ../shared/src/jump-search-text.ts
9682
+ function wordPrefixesMatch(words, needle) {
9683
+ let offsets = /* @__PURE__ */ new Set([0]);
9684
+ for (let index = 0; index < words.length; index += 1) {
9685
+ const next = /* @__PURE__ */ new Set();
9686
+ const word = words[index].toLowerCase();
9687
+ for (const offset of offsets) {
9688
+ for (let length = 1; length <= word.length && offset + length <= needle.length; length += 1) {
9689
+ if (word[length - 1] !== needle[offset + length - 1]) break;
9690
+ if (offset + length === needle.length && index > 0) return true;
9691
+ next.add(offset + length);
9692
+ }
9693
+ }
9694
+ if (!next.size) return false;
9695
+ offsets = next;
9696
+ }
9697
+ return false;
9698
+ }
9699
+ function splitJumpWords(text) {
9700
+ return text.replace(new RegExp("([\\p{Ll}\\p{N}])(\\p{Lu})", "gu"), "$1 $2").replace(new RegExp("(\\p{Lu})(\\p{Lu}\\p{Ll})", "gu"), "$1 $2").match(/[\p{L}\p{N}]+/gu) ?? [];
9701
+ }
9702
+ function textMatchesJumpQuery(text, query) {
9703
+ const needle = query.trim().toLowerCase();
9704
+ if (!text || !needle) return false;
9705
+ if (text.toLowerCase().includes(needle)) return true;
9706
+ if (!/^[\p{L}\p{N}]{2,32}$/u.test(needle)) return false;
9707
+ return text.split(/[\\/]/).some((component) => {
9708
+ const compounds = component.match(/[\p{L}\p{N}]+(?:[-_.:][\p{L}\p{N}]+)*/gu) ?? [];
9709
+ return compounds.some((compound) => wordPrefixesMatch(splitJumpWords(compound), needle)) || wordPrefixesMatch(splitJumpWords(component), needle);
9710
+ });
9711
+ }
9712
+
9713
+ // ../shared/src/thread-search-query.ts
9714
+ function parseThreadSearchQuery(input) {
9715
+ const projects = [];
9716
+ const tokens = input.match(/(?:in:)?@"[^"]+"(?=\s|$)|"[^"]*"(?=\s|$)|\S+/gi) ?? [];
9717
+ const text = tokens.filter((token) => {
9718
+ const mention = /^(?:in:)?@(?:"([^"]+)"|([^\s"@]+))$/i.exec(token);
9719
+ if (!mention) return true;
9720
+ projects.push((mention[1] ?? mention[2]).toLowerCase());
9721
+ return false;
9722
+ });
9723
+ return { query: projects.length ? text.join(" ") : input.trim(), projects: [...new Set(projects)] };
9724
+ }
9725
+ function matchesThreadSearchProjects(thread, projects) {
9726
+ if (!projects.length) return true;
9727
+ const values = [thread.projectKey, ...(thread.linkedDirectories ?? []).flatMap(
9728
+ (directory) => [directory.label, directory.path, directory.worktreePath]
9729
+ )].filter((value) => Boolean(value));
9730
+ return projects.some((project) => values.some((value) => {
9731
+ const path11 = value.replaceAll("\\", "/").replace(/\/+$/, "").toLowerCase();
9732
+ const name = path11.slice(path11.lastIndexOf("/") + 1);
9733
+ return name.startsWith(project) || path11.startsWith(project.replaceAll("\\", "/"));
9734
+ }));
9735
+ }
9736
+ function threadSearchTextTerms(query) {
9737
+ return (query.match(/"[^"]*"|[^\s"]+/g) ?? []).map((token) => ({
9738
+ text: token.startsWith('"') ? token.slice(1, -1) : token,
9739
+ quoted: token.startsWith('"')
9740
+ })).filter((term) => term.text.length > 0);
9741
+ }
9742
+
9743
+ // ../shared/src/thread-jump-match.ts
9744
+ var MIN_LONG_THREAD_ID_QUERY_LENGTH = 10;
9745
+ var MIN_UUID_FRAGMENT_HEX_CHARS = 8;
9746
+ function threadPrNumbers(thread) {
9747
+ return (thread.prs ?? []).map((pr2) => pr2.number);
9748
+ }
9749
+ function threadHasExactPrNumberMatch(thread, query) {
9750
+ const match = query.trim().match(/^#?(\d{1,7})$/);
9751
+ if (!match) {
9752
+ return false;
9753
+ }
9754
+ const number = Number(match[1]);
9755
+ return Number.isInteger(number) && number > 0 && threadPrNumbers(thread).includes(number);
9756
+ }
9757
+ function threadIdMatchesQuery(threadId, query) {
9758
+ const id = threadId.toLowerCase();
9759
+ if (!id.includes(query)) {
9760
+ return false;
9761
+ }
9762
+ const hexChars = query.match(/[0-9a-f]/g)?.length ?? 0;
9763
+ const uuidFragment = /^[0-9a-f][0-9a-f-]{7,}$/i.test(query) && hexChars >= MIN_UUID_FRAGMENT_HEX_CHARS;
9764
+ if (uuidFragment) {
9765
+ return true;
9766
+ }
9767
+ return query.length >= MIN_LONG_THREAD_ID_QUERY_LENGTH;
9768
+ }
9769
+ function agentMetadataMatchesQuery(thread, query) {
9770
+ if (!thread.agent) {
9771
+ return false;
9772
+ }
9773
+ const tokens = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
9774
+ if (tokens.length === 0) {
9775
+ return false;
9776
+ }
9777
+ const haystack = [
9778
+ "agent",
9779
+ "agent thread",
9780
+ thread.agent.name,
9781
+ thread.agent.instructions
9782
+ ].filter((value) => Boolean(value)).join(" ").toLowerCase();
9783
+ return tokens.every((token) => haystack.includes(token));
9784
+ }
9785
+ function parseThreadJumpQuery(query) {
9786
+ const parsed = parseThreadSearchQuery(query);
9787
+ const terms = threadSearchTextTerms(parsed.query).map((term) => term.text);
9788
+ if (!parsed.projects.length && !/"(?:in:)?@[^"]*"/i.test(parsed.query)) {
9789
+ return { text: parsed.query, projects: [], terms };
9790
+ }
9791
+ return { text: terms.join(" "), projects: parsed.projects, terms };
9792
+ }
9793
+ function createThreadJumpMatcher(query) {
9794
+ const { text, projects, terms } = parseThreadJumpQuery(query);
9795
+ const needle = text.trim().toLowerCase();
9796
+ const words = terms.map((term) => term.toLowerCase());
9797
+ const wordsDiffer = words.join(" ") !== needle;
9798
+ return (thread) => {
9799
+ if (projects.length && !matchesThreadSearchProjects(thread, projects)) {
9800
+ return false;
9801
+ }
9802
+ if (!needle) {
9803
+ return projects.length > 0;
9804
+ }
9805
+ if (threadMatchesNeedle(thread, needle)) {
9806
+ return true;
9807
+ }
9808
+ return words.length > 0 && (words.length > 1 || wordsDiffer) && words.every((word) => threadMatchesNeedle(thread, word));
9809
+ };
9810
+ }
9811
+ function threadMatchesNeedle(thread, needle) {
9812
+ if (textMatchesJumpQuery(thread.title, needle)) {
9813
+ return true;
9814
+ }
9815
+ if (agentMetadataMatchesQuery(thread, needle)) {
9816
+ return true;
9817
+ }
9818
+ if (threadIdMatchesQuery(thread.id, needle)) {
9819
+ return true;
9820
+ }
9821
+ if (textMatchesJumpQuery(thread.gitBranch, needle)) {
9822
+ return true;
9823
+ }
9824
+ const bareNeedle = needle.replace(/^#/, "");
9825
+ if (bareNeedle.length > 0 && threadPrNumbers(thread).some((number) => String(number).includes(bareNeedle))) {
9826
+ return true;
9827
+ }
9828
+ return (thread.linkedDirectories ?? []).some(
9829
+ (directory) => [directory.label, directory.path, directory.worktreePath].some((value) => textMatchesJumpQuery(value, needle))
9830
+ );
9831
+ }
9832
+ function rankThreadJumpMatches(threads, query) {
9833
+ return sortThreadJumpMatches(threads.filter(createThreadJumpMatcher(query)), query);
9834
+ }
9835
+ function sortThreadJumpMatches(threads, query) {
9836
+ const { text } = parseThreadJumpQuery(query);
9837
+ return [...threads].sort((left, right) => {
9838
+ const exactPrPriority = Number(threadHasExactPrNumberMatch(right, text)) - Number(threadHasExactPrNumberMatch(left, text));
9839
+ if (exactPrPriority !== 0) {
9840
+ return exactPrPriority;
9841
+ }
9842
+ return (right.updatedAt ?? right.createdAt ?? 0) - (left.updatedAt ?? left.createdAt ?? 0);
9843
+ });
9844
+ }
9845
+
9489
9846
  // ../shared/src/thread-titles.ts
9490
9847
  var MAX_DERIVED_THREAD_TITLE_LENGTH = 72;
9491
9848
  var MIN_PREFERRED_BREAK_INDEX = 36;
@@ -13088,10 +13445,10 @@ var FederationPtyService = class {
13088
13445
  async list(peerId, request) {
13089
13446
  await this.ready;
13090
13447
  if (this.options.allowOpen?.() === false) throw new Error("Remote shells are disabled.");
13091
- const threadKey = `${request.backend}:${request.threadId}`;
13448
+ const threadKey2 = `${request.backend}:${request.threadId}`;
13092
13449
  return [
13093
13450
  ...[...this.sessionsById.values()].filter((session) => session.peerId === peerId && session.reapReason !== "close" && session.backend === request.backend && session.threadId === request.threadId).map((session) => this.summary(session)),
13094
- ...[...this.archived.values()].filter((record) => record.peerId === peerId && record.summary.threadKey === threadKey).map((record) => record.summary)
13451
+ ...[...this.archived.values()].filter((record) => record.peerId === peerId && record.summary.threadKey === threadKey2).map((record) => record.summary)
13095
13452
  ];
13096
13453
  }
13097
13454
  async snapshot(peerId, request) {
@@ -18790,7 +19147,9 @@ var ThreadInstanceHandoffService = class {
18790
19147
  // ../federation/src/git-instance-handoff.ts
18791
19148
  import { randomUUID as randomUUID6 } from "node:crypto";
18792
19149
  import { realpath as realpath3, stat as stat2 } from "node:fs/promises";
19150
+ import os2 from "node:os";
18793
19151
  import path6 from "node:path";
19152
+ var DISABLE_HOOKS = ["-c", `core.hooksPath=${os2.devNull}`];
18794
19153
  function createGitHandoffService(runGitCommand) {
18795
19154
  function git(cwd, args) {
18796
19155
  return runGitCommand(cwd, args, { maxBuffer: 1024 * 1024 });
@@ -18887,7 +19246,7 @@ function createGitHandoffService(runGitCommand) {
18887
19246
  if (!remote) throw new Error("The receiving repository has no remote matching the sender. Clone the same repository or fix its remote before handing off.");
18888
19247
  const ref = `refs/pwragent/handoffs/${randomUUID6()}`;
18889
19248
  try {
18890
- await git(repository, ["-c", "core.hooksPath=", "fetch", "--no-tags", "--no-write-fetch-head", "--", remote, `${snapshot.ref}:${ref}`]).catch((error) => {
19249
+ await git(repository, [...DISABLE_HOOKS, "fetch", "--no-tags", "--no-write-fetch-head", "--", remote, `${snapshot.ref}:${ref}`]).catch((error) => {
18891
19250
  throw new Error(`The receiving machine could not fetch ${snapshot.ref}. Check its Git access and push the source branch before handing off.`, { cause: error });
18892
19251
  });
18893
19252
  const fetchedHead = (await git(repository, ["rev-parse", `${ref}^{commit}`])).stdout.trim();
@@ -18929,11 +19288,11 @@ function createGitHandoffService(runGitCommand) {
18929
19288
  switchBranch = branchHead ? ["--no-guess", branch] : ["-c", branch, snapshot.head];
18930
19289
  }
18931
19290
  }
18932
- await git(repository, ["-c", "core.hooksPath=", "worktree", "add", "--detach", worktree, snapshot.head]);
19291
+ await git(repository, [...DISABLE_HOOKS, "worktree", "add", "--detach", worktree, snapshot.head]);
18933
19292
  created = true;
18934
19293
  if (switchBranch && branch) {
18935
19294
  try {
18936
- await git(worktree, ["-c", "core.hooksPath=", "switch", ...switchBranch]);
19295
+ await git(worktree, [...DISABLE_HOOKS, "switch", ...switchBranch]);
18937
19296
  if (switchBranch[0] === "-c") createdBranch = branch;
18938
19297
  } catch (error) {
18939
19298
  const ref = `refs/heads/${branch}`;
@@ -19283,7 +19642,7 @@ import path10 from "node:path";
19283
19642
 
19284
19643
  // ../agent-tools/src/local-file-access.ts
19285
19644
  import { realpath as realpath4 } from "node:fs/promises";
19286
- import os2 from "node:os";
19645
+ import os3 from "node:os";
19287
19646
  import path8 from "node:path";
19288
19647
  var PRIVATE_STORAGE_DIRECTORIES = /* @__PURE__ */ new Set([
19289
19648
  "archived_sessions",
@@ -19346,7 +19705,7 @@ async function canonicalizeRoots(roots) {
19346
19705
  }
19347
19706
  function configuredPrivateStorageRoots(privateStorageRoots) {
19348
19707
  return [
19349
- path8.join(os2.homedir(), ".codex"),
19708
+ path8.join(os3.homedir(), ".codex"),
19350
19709
  process.env.CODEX_HOME?.trim(),
19351
19710
  ...privateStorageRoots
19352
19711
  ].filter((root) => Boolean(root));
@@ -20103,6 +20462,1414 @@ var FEDERATION_BACKEND_METHOD_CAPABILITIES = {
20103
20462
  // instance — the same trust the materialize-launchpad path carries.
20104
20463
  [FEDERATION_BACKEND_METHODS.starMapIntake]: "environment_actions"
20105
20464
  };
20465
+
20466
+ // ../owner-core/src/navigation-query-store.ts
20467
+ import { createHash as createHash8, randomUUID as randomUUID8 } from "node:crypto";
20468
+
20469
+ // ../owner-core/src/navigation-query-projection.ts
20470
+ import { createHash as createHash7 } from "node:crypto";
20471
+
20472
+ // ../owner-core/src/navigation-attention-order.ts
20473
+ function observeNavigationAttentionTurn(params) {
20474
+ const previous = params.previous.members.get(params.key);
20475
+ const observedTurnIds = previous?.observedTurnIds ?? (previous?.eventTurnId ? [previous.eventTurnId] : []);
20476
+ if (params.turnId && params.turnId !== previous?.eventTurnId && observedTurnIds.includes(params.turnId)) return params.previous;
20477
+ if (previous?.active === params.active && (!params.turnId || previous.eventTurnId === params.turnId)) return params.previous;
20478
+ if (!params.active && previous?.active && previous.eventTurnId && params.turnId && previous.eventTurnId !== params.turnId) return params.previous;
20479
+ const newTurn = Boolean(params.turnId && previous?.eventTurnId !== params.turnId);
20480
+ const move = !previous || (params.active ? !previous.active || newTurn && Boolean(previous.eventTurnId) : !previous.active && newTurn || params.promoteOnTurnEnd);
20481
+ const rank = move ? params.previous.nextRank : previous.rank;
20482
+ const members = new Map(params.previous.members);
20483
+ members.set(params.key, {
20484
+ active: params.active,
20485
+ rank,
20486
+ updatedAt: previous?.updatedAt ?? 0,
20487
+ observedTurnIds: params.turnId && !observedTurnIds.includes(params.turnId) ? [...observedTurnIds, params.turnId] : observedTurnIds,
20488
+ eventTurnId: params.active ? params.turnId : params.turnId ?? previous?.eventTurnId,
20489
+ eventTracked: true,
20490
+ awaitingBaseline: true,
20491
+ ...params.observedAt !== void 0 ? { observedAt: params.observedAt } : {}
20492
+ });
20493
+ let removed = params.previous.removed;
20494
+ if (removed?.has(params.key)) {
20495
+ removed = new Map(removed);
20496
+ removed.delete(params.key);
20497
+ }
20498
+ return {
20499
+ members,
20500
+ nextRank: params.previous.nextRank + Number(move),
20501
+ ...removed?.size ? { removed } : {},
20502
+ ...params.previous.listedAt !== void 0 ? { listedAt: params.previous.listedAt } : {}
20503
+ };
20504
+ }
20505
+ function removeNavigationAttentionMember(params) {
20506
+ const members = new Map(params.previous.members);
20507
+ members.delete(params.key);
20508
+ const removed = new Map(params.previous.removed);
20509
+ removed.set(params.key, params.observedAt);
20510
+ return { ...params.previous, members, removed };
20511
+ }
20512
+ function navigationAttentionOrderFollows(order, observedAt) {
20513
+ if ((order.listedAt ?? 0) > observedAt) return true;
20514
+ for (const member of order.members.values()) if ((member.observedAt ?? 0) > observedAt) return true;
20515
+ for (const at2 of order.removed?.values() ?? []) if (at2 > observedAt) return true;
20516
+ return false;
20517
+ }
20518
+ function navigationAttentionIdentity(thread) {
20519
+ return JSON.stringify([
20520
+ thread.federation?.ref.target.scope === "remote" ? thread.federation.ref.target.instanceId : null,
20521
+ thread.source,
20522
+ thread.id
20523
+ ]);
20524
+ }
20525
+ function isLive(thread) {
20526
+ return thread.threadStatus === "active" || thread.hasActiveSubAgent === true || thread.hasBackgroundTasks === true;
20527
+ }
20528
+ function reconcileNavigationAttentionOrder(params) {
20529
+ const { observedAt } = params;
20530
+ const follows = (at2) => observedAt !== void 0 && at2 !== void 0 && at2 > observedAt;
20531
+ const members = params.complete === false ? new Map(params.previous?.members) : /* @__PURE__ */ new Map();
20532
+ for (const [key, member] of params.previous?.members ?? []) {
20533
+ if (member.awaitingBaseline || follows(member.observedAt)) members.set(key, member);
20534
+ }
20535
+ const removed = /* @__PURE__ */ new Map();
20536
+ for (const [key, at2] of params.previous?.removed ?? []) {
20537
+ if (follows(at2)) removed.set(key, at2);
20538
+ }
20539
+ const decides = (key) => {
20540
+ const member = params.previous?.members.get(key);
20541
+ return !follows(member?.observedAt) && !removed.has(key) && (member !== void 0 || !follows(params.previous?.listedAt));
20542
+ };
20543
+ for (const thread of params.threads) {
20544
+ const key = navigationAttentionIdentity(thread);
20545
+ const previous = params.previous?.members.get(key);
20546
+ if (decides(key) && (thread.codexNativeSubAgent || !isLive(thread) && !thread.inbox.inInbox && (!previous?.awaitingBaseline || !previous.active))) members.delete(key);
20547
+ }
20548
+ let nextRank = params.previous?.nextRank ?? 1;
20549
+ const eligible = params.threads.filter((thread) => !thread.codexNativeSubAgent && decides(navigationAttentionIdentity(thread)) && (isLive(thread) || thread.inbox.inInbox)).sort((left, right) => (left.updatedAt ?? 0) - (right.updatedAt ?? 0) || navigationAttentionIdentity(right).localeCompare(navigationAttentionIdentity(left)));
20550
+ for (const thread of eligible) {
20551
+ const key = navigationAttentionIdentity(thread);
20552
+ const previous = params.previous?.members.get(key);
20553
+ const observedActive = thread.threadStatus === "active";
20554
+ const active = previous?.awaitingBaseline ? previous.active : observedActive;
20555
+ const updatedAt = thread.updatedAt ?? 0;
20556
+ const started = active && previous?.active === false;
20557
+ const finished = !active && (previous?.active === true || previous?.active === false && !previous.eventTracked && updatedAt > previous.updatedAt);
20558
+ const rank = !previous || started || finished && params.promoteOnTurnEnd ? nextRank++ : previous.rank;
20559
+ const awaitingBaseline = previous?.awaitingBaseline && observedActive !== active;
20560
+ const memberObservedAt = awaitingBaseline ? previous?.observedAt : observedAt ?? previous?.observedAt;
20561
+ members.set(key, {
20562
+ active,
20563
+ rank,
20564
+ updatedAt,
20565
+ ...previous?.eventTurnId ? { eventTurnId: previous.eventTurnId } : {},
20566
+ ...previous?.observedTurnIds ? { observedTurnIds: previous.observedTurnIds } : {},
20567
+ ...previous?.eventTracked ? { eventTracked: true } : {},
20568
+ ...awaitingBaseline ? { awaitingBaseline: true } : {},
20569
+ ...memberObservedAt !== void 0 ? { observedAt: memberObservedAt } : {}
20570
+ });
20571
+ }
20572
+ if (observedAt !== void 0) {
20573
+ for (const key of params.previous?.members.keys() ?? []) {
20574
+ if (!members.has(key)) removed.set(key, observedAt);
20575
+ }
20576
+ }
20577
+ const listedAt = params.complete !== false && observedAt !== void 0 && !follows(params.previous?.listedAt) ? observedAt : params.previous?.listedAt;
20578
+ return { members, nextRank, ...removed.size ? { removed } : {}, ...listedAt !== void 0 ? { listedAt } : {} };
20579
+ }
20580
+ function navigationAttentionOrderBytes(order) {
20581
+ return Buffer.byteLength(JSON.stringify({
20582
+ members: [...order.members],
20583
+ nextRank: order.nextRank,
20584
+ removed: [...order.removed ?? []],
20585
+ listedAt: order.listedAt
20586
+ }), "utf8");
20587
+ }
20588
+
20589
+ // ../owner-core/src/navigation-query-projection.ts
20590
+ var MAX_ROW_NESTED_RECORDS = 16;
20591
+ function hashValue(value) {
20592
+ return createHash7("sha256").update(JSON.stringify(value)).digest("base64url");
20593
+ }
20594
+ function navigationIdentity(thread) {
20595
+ return {
20596
+ backend: thread.source,
20597
+ threadId: thread.id,
20598
+ ...thread.federation?.ref.target.scope === "remote" ? { ownerInstanceId: thread.federation.ref.target.instanceId } : {}
20599
+ };
20600
+ }
20601
+ function identityKey(identity) {
20602
+ return [
20603
+ identity.ownerInstanceId ?? "local",
20604
+ identity.backend,
20605
+ identity.threadId
20606
+ ].join("\0");
20607
+ }
20608
+ function threadKey(thread) {
20609
+ return identityKey(navigationIdentity(thread));
20610
+ }
20611
+ function parentIdentity(thread, candidatesByOwner) {
20612
+ if (!thread.parentThreadId) {
20613
+ return void 0;
20614
+ }
20615
+ let ownerInstanceId = thread.parentThreadInstanceId ?? (thread.federation?.ref.target.scope === "remote" ? thread.federation.ref.target.instanceId : void 0);
20616
+ if (!ownerInstanceId && thread.handoffOrigin?.groupingMode === "subthread" && !(candidatesByOwner.get(JSON.stringify([null, thread.parentThreadId])) ?? []).some((candidate) => candidate.source === (thread.parentThreadBackend ?? thread.source))) {
20617
+ const origin = thread.handoffOrigin;
20618
+ const launcher = (candidatesByOwner.get(JSON.stringify([null, origin.sourceThreadId])) ?? []).find((candidate) => candidate.source === origin.sourceBackend);
20619
+ if (launcher?.parentThreadId === thread.parentThreadId && (launcher.parentThreadBackend ?? launcher.source) === (thread.parentThreadBackend ?? thread.source)) {
20620
+ ownerInstanceId = launcher.parentThreadInstanceId;
20621
+ }
20622
+ }
20623
+ const candidates = candidatesByOwner.get(JSON.stringify([ownerInstanceId ?? null, thread.parentThreadId])) ?? [];
20624
+ const parent = !thread.parentThreadBackend ? candidates.find((candidate) => candidate.source === thread.source) ?? (candidates.length === 1 ? candidates[0] : void 0) : void 0;
20625
+ const backend = thread.parentThreadBackend ?? parent?.source ?? thread.source;
20626
+ const resolvedParent = candidates.find((candidate) => candidate.source === backend);
20627
+ if (resolvedParent) ownerInstanceId = navigationIdentity(resolvedParent).ownerInstanceId;
20628
+ return {
20629
+ backend,
20630
+ threadId: thread.parentThreadId,
20631
+ ...ownerInstanceId ? { ownerInstanceId } : {}
20632
+ };
20633
+ }
20634
+ function isOrdinaryThread(thread) {
20635
+ return thread.codexNativeSubAgent === void 0;
20636
+ }
20637
+ function lensRootResolver(params) {
20638
+ const roots = /* @__PURE__ */ new Map();
20639
+ return (thread) => {
20640
+ const known = roots.get(thread);
20641
+ if (known) return known;
20642
+ let root = thread;
20643
+ const visited = /* @__PURE__ */ new Set([thread]);
20644
+ for (let depth = 0; depth < 32; depth += 1) {
20645
+ const parent = parentIdentity(root, params.parentCandidates);
20646
+ const parentThread = parent ? params.threadsByIdentity.get(identityKey(parent)) : void 0;
20647
+ if (!parentThread || visited.has(parentThread)) break;
20648
+ visited.add(parentThread);
20649
+ root = parentThread;
20650
+ }
20651
+ roots.set(thread, root);
20652
+ return root;
20653
+ };
20654
+ }
20655
+ function availableParentIdentity(thread, candidatesByOwner, coverage) {
20656
+ const parent = parentIdentity(thread, candidatesByOwner);
20657
+ if (!parent) return void 0;
20658
+ if (coverage && coverage.state !== "complete") return parent;
20659
+ const candidates = candidatesByOwner.get(JSON.stringify([parent.ownerInstanceId ?? null, parent.threadId])) ?? [];
20660
+ return candidates.some((candidate) => candidate.source === parent.backend) ? parent : void 0;
20661
+ }
20662
+ function directoryParentResolver(members, parentCandidates) {
20663
+ const memberKeys = new Set(members.map(threadKey));
20664
+ return (thread) => {
20665
+ const parent = parentIdentity(thread, parentCandidates);
20666
+ return parent && memberKeys.has(identityKey(parent)) ? parent : void 0;
20667
+ };
20668
+ }
20669
+ function directoryMemberThreads(directory, threadsByLegacyKey) {
20670
+ return directory?.threadKeys.map((key) => threadsByLegacyKey.get(key)).filter((thread) => Boolean(thread)) ?? [];
20671
+ }
20672
+ function isActive(thread) {
20673
+ return thread.threadStatus === "active" || thread.hasActiveSubAgent === true || thread.hasBackgroundTasks === true;
20674
+ }
20675
+ function countsForThreads(threads, options = {}) {
20676
+ const distinct = /* @__PURE__ */ new Map();
20677
+ for (const thread of threads) {
20678
+ if (!isOrdinaryThread(thread)) {
20679
+ continue;
20680
+ }
20681
+ distinct.set(threadKey(thread), thread);
20682
+ }
20683
+ let active = 0;
20684
+ let activeRemote = 0;
20685
+ let pinned = 0;
20686
+ let unread = 0;
20687
+ let review = 0;
20688
+ for (const thread of distinct.values()) {
20689
+ const threadActive = isActive(thread);
20690
+ const threadUnread = thread.inbox.inInbox;
20691
+ if (threadActive) active += 1;
20692
+ if (threadActive && thread.federation?.ref.target.scope === "remote") activeRemote += 1;
20693
+ if (thread.pinnedRank) pinned += 1;
20694
+ if (threadUnread) unread += 1;
20695
+ if (threadUnread && !threadActive) review += 1;
20696
+ }
20697
+ const backends = options.backends ? [...new Set(
20698
+ [...distinct.values()].map((thread) => thread.source)
20699
+ )].sort() : void 0;
20700
+ return {
20701
+ total: distinct.size,
20702
+ active,
20703
+ ...activeRemote ? { activeRemote } : {},
20704
+ ...pinned ? { pinned } : {},
20705
+ unread,
20706
+ review,
20707
+ ...backends ? { backends } : {}
20708
+ };
20709
+ }
20710
+ function limitRecords(items) {
20711
+ if (!items || items.length === 0) {
20712
+ return { truncated: false };
20713
+ }
20714
+ return {
20715
+ items: items.slice(0, MAX_ROW_NESTED_RECORDS),
20716
+ truncated: items.length > MAX_ROW_NESTED_RECORDS
20717
+ };
20718
+ }
20719
+ function projectNavigationRow(params) {
20720
+ const { thread } = params;
20721
+ const linkedDirectories = limitRecords(thread.federation ? thread.linkedDirectories : withRepositoryIdentities(thread.linkedDirectories, params.repositories));
20722
+ const reactions = limitRecords(thread.reactions);
20723
+ const prs = limitRecords(thread.prs);
20724
+ const messagingBindings = limitRecords(thread.messagingBindings);
20725
+ const prCiWatches = limitRecords(thread.prCiWatches);
20726
+ const nativeSubAgentCount = thread.nativeSubAgentCount ?? thread.codexNativeSubAgents?.length ?? 0;
20727
+ const projected = {
20728
+ ref: navigationIdentity(thread),
20729
+ id: thread.id,
20730
+ source: thread.source,
20731
+ title: thread.title,
20732
+ titleSource: thread.titleSource,
20733
+ ...thread.createdAt !== void 0 ? { createdAt: thread.createdAt } : {},
20734
+ ...thread.updatedAt !== void 0 ? { updatedAt: thread.updatedAt } : {},
20735
+ ...thread.archivedAt !== void 0 ? { archivedAt: thread.archivedAt } : {},
20736
+ ...thread.threadStatus ? { threadStatus: thread.threadStatus } : {},
20737
+ ...thread.hasActiveSubAgent ? { hasActiveSubAgent: true } : {},
20738
+ ...thread.hasBackgroundTasks ? { hasBackgroundTasks: true } : {},
20739
+ ...thread.hasAgentCommands ? { hasAgentCommands: true } : {},
20740
+ inbox: thread.inbox,
20741
+ ...thread.projectKey ? { projectKey: thread.projectKey } : {},
20742
+ linkedDirectories: linkedDirectories.items ?? [],
20743
+ ...linkedDirectories.truncated ? { linkedDirectoriesTruncated: true } : {},
20744
+ ...thread.gitBranch ? { gitBranch: thread.gitBranch } : {},
20745
+ ...thread.gitOriginUrl ? { gitOriginUrl: thread.gitOriginUrl } : {},
20746
+ ...thread.observedGitBranch ? { observedGitBranch: thread.observedGitBranch } : {},
20747
+ ...thread.gitWorkingState ? { gitWorkingState: thread.gitWorkingState } : {},
20748
+ ...thread.gitWorkingStateFetchedAt !== void 0 ? { gitWorkingStateFetchedAt: thread.gitWorkingStateFetchedAt } : {},
20749
+ ...thread.primaryGitRepository ? { primaryGitRepository: thread.primaryGitRepository } : {},
20750
+ ...thread.federation ? {
20751
+ federation: {
20752
+ ref: thread.federation.ref,
20753
+ instanceLabel: thread.federation.instanceLabel,
20754
+ ...thread.federation.peerStatus ? { peerStatus: thread.federation.peerStatus } : {},
20755
+ ...thread.federation.capabilities ? { capabilities: thread.federation.capabilities } : {},
20756
+ ...thread.federation.derivedFromMountedParent ? { derivedFromMountedParent: true } : {},
20757
+ ...thread.federation.celestialIcon ? { celestialIcon: thread.federation.celestialIcon } : {}
20758
+ }
20759
+ } : {},
20760
+ ...thread.pinnedRank ? { pinnedRank: thread.pinnedRank } : {},
20761
+ ...thread.parentThreadId ? { parentThreadId: thread.parentThreadId } : {},
20762
+ ...thread.parentThreadBackend ? { parentThreadBackend: thread.parentThreadBackend } : {},
20763
+ ...thread.parentThreadInstanceId ? { parentThreadInstanceId: thread.parentThreadInstanceId } : {},
20764
+ ordinaryChildCount: params.childCount,
20765
+ ...params.viewerChildCount !== void 0 ? { viewerChildCount: params.viewerChildCount } : {},
20766
+ nativeSubAgentGroupPresent: nativeSubAgentCount > 0,
20767
+ ...nativeSubAgentCount > 0 ? { nativeSubAgentCount } : {},
20768
+ ...thread.subthreadsCollapsed !== void 0 ? { subthreadsCollapsed: thread.subthreadsCollapsed } : {},
20769
+ ...reactions.items ? { reactions: reactions.items } : {},
20770
+ ...reactions.truncated ? { reactionsTruncated: true } : {},
20771
+ ...thread.lock ? { lock: thread.lock } : {},
20772
+ ...prs.items ? { prs: prs.items } : {},
20773
+ ...prs.truncated ? { prsTruncated: true } : {},
20774
+ ...messagingBindings.items ? { messagingBindings: messagingBindings.items } : {},
20775
+ ...messagingBindings.truncated ? { messagingBindingsTruncated: true } : {},
20776
+ ...thread.automationSummary ? { automationSummary: thread.automationSummary } : {},
20777
+ ...thread.agentChange ? { agentChange: thread.agentChange } : {},
20778
+ ...thread.agent ? {
20779
+ agent: {
20780
+ name: thread.agent.name,
20781
+ instructionLineCount: thread.agent.instructionLineCount,
20782
+ instructionsTooLong: thread.agent.instructionsTooLong,
20783
+ updatedAt: thread.agent.updatedAt
20784
+ }
20785
+ } : {},
20786
+ ...thread.executionMode ? { executionMode: thread.executionMode } : {},
20787
+ ...thread.model ? { model: thread.model } : {},
20788
+ ...thread.serviceTier ? { serviceTier: thread.serviceTier } : {},
20789
+ ...thread.reasoningEffort ? { reasoningEffort: thread.reasoningEffort } : {},
20790
+ ...thread.fastMode !== void 0 ? { fastMode: thread.fastMode } : {},
20791
+ ...thread.workspaceHandoff ? { workspaceHandoff: thread.workspaceHandoff } : {},
20792
+ needsInput: params.needsInput,
20793
+ queueCount: thread.queuedTurns?.length ?? 0,
20794
+ queueState: thread.queuedTurns ? "ready" : "unknown",
20795
+ ...thread.queuedExecutionMode ? { queuedExecutionMode: thread.queuedExecutionMode } : {},
20796
+ ...thread.prAutoDispatchEnabled !== void 0 ? { prAutoDispatchEnabled: thread.prAutoDispatchEnabled } : {},
20797
+ ...prCiWatches.items ? { prCiWatches: prCiWatches.items } : {},
20798
+ ...thread.scheduledStart ? { scheduledStart: thread.scheduledStart } : {},
20799
+ ...params.homeProject ? { homeProject: params.homeProject } : {}
20800
+ };
20801
+ return {
20802
+ ...projected,
20803
+ rowRevision: hashValue(projected)
20804
+ };
20805
+ }
20806
+ function projectDirectoryGitStatus(directory) {
20807
+ const status = directory.gitStatus;
20808
+ if (!status) {
20809
+ return void 0;
20810
+ }
20811
+ return {
20812
+ ...status.currentBranch ? { currentBranch: status.currentBranch } : {},
20813
+ ...status.defaultBranch ? { defaultBranch: status.defaultBranch } : {},
20814
+ ...status.upstreamBranch ? { upstreamBranch: status.upstreamBranch } : {},
20815
+ ...status.ahead !== void 0 ? { ahead: status.ahead } : {},
20816
+ ...status.behind !== void 0 ? { behind: status.behind } : {},
20817
+ ...status.syncState ? { syncState: status.syncState } : {},
20818
+ ...status.statusUnavailableReason ? { statusUnavailableReason: status.statusUnavailableReason } : {},
20819
+ ...status.worktreeCreationAvailable !== void 0 ? { worktreeCreationAvailable: status.worktreeCreationAvailable } : {},
20820
+ ...status.worktreeCreationUnavailableReason ? {
20821
+ worktreeCreationUnavailableReason: status.worktreeCreationUnavailableReason
20822
+ } : {}
20823
+ };
20824
+ }
20825
+ function buildDirectoryRows(params) {
20826
+ return params.snapshot.directories.map((directory) => {
20827
+ const memberThreads = directoryMemberThreads(directory, params.threadsByLegacyKey);
20828
+ const parentOf = directoryParentResolver(memberThreads, params.parentCandidates);
20829
+ const rootThreads = memberThreads.filter((thread) => !parentOf(thread));
20830
+ const pinnedRootCount = rootThreads.filter((thread) => thread.pinnedRank).length;
20831
+ const gitStatus = projectDirectoryGitStatus(directory);
20832
+ const repositoryKey = directory.gitStatus?.originRepository ?? directory.repositoryKey;
20833
+ const repositoryKeys = directory.gitStatus?.remoteRepositories?.length ? directory.gitStatus.remoteRepositories : directory.repositoryKeys;
20834
+ return {
20835
+ key: directory.key,
20836
+ kind: directory.kind,
20837
+ label: directory.label,
20838
+ ...directory.path ? { path: directory.path } : {},
20839
+ ...repositoryKey ? { repositoryKey } : {},
20840
+ ...repositoryKeys?.length ? { repositoryKeys } : {},
20841
+ ...directory.localAvailability ? { localAvailability: directory.localAvailability } : {},
20842
+ counts: countsForThreads(memberThreads),
20843
+ pinnedRootCount,
20844
+ unpinnedRootCount: rootThreads.length - pinnedRootCount,
20845
+ ...directory.latestUpdatedAt !== void 0 ? { latestUpdatedAt: directory.latestUpdatedAt } : {},
20846
+ ...directory.pinnedRank ? { pinnedRank: directory.pinnedRank } : {},
20847
+ ...directory.directoryThreadsCollapsed !== void 0 ? { directoryThreadsCollapsed: directory.directoryThreadsCollapsed } : {},
20848
+ // A placeholder row stands for a peer's project. It shows that
20849
+ // project's setting only when its threads name one project on one peer.
20850
+ ...directory.forgeAutomation && (!directory.localAvailability || directory.peerProject) ? { forgeAutomation: directory.forgeAutomation } : {},
20851
+ ...directory.localAvailability && directory.peerProject ? { peerProject: directory.peerProject } : {},
20852
+ ...gitStatus ? { gitStatus } : {},
20853
+ launchpadPresent: Boolean(directory.launchpad),
20854
+ ...directory.launchpad?.backend ? { launchpadBackend: directory.launchpad.backend } : {}
20855
+ };
20856
+ });
20857
+ }
20858
+ function repositoryIdentitiesByPath(index) {
20859
+ const identities = /* @__PURE__ */ new Map();
20860
+ for (const directory of index.directories) {
20861
+ const path11 = directory.path;
20862
+ const repositoryKey = directory.gitStatus?.originRepository;
20863
+ const repositoryKeys = directory.gitStatus?.remoteRepositories;
20864
+ if (!path11 || !repositoryKey && !repositoryKeys?.length) continue;
20865
+ identities.set(path11, {
20866
+ ...repositoryKey ? { repositoryKey } : {},
20867
+ ...repositoryKeys?.length ? { repositoryKeys } : {}
20868
+ });
20869
+ }
20870
+ return identities;
20871
+ }
20872
+ function withRepositoryIdentities(directories, repositories) {
20873
+ if (!repositories.size) return directories;
20874
+ return directories.map((directory) => {
20875
+ const descriptor = classifyDirectory(directory);
20876
+ if (descriptor.kind !== "directory") return directory;
20877
+ const identity = (descriptor.path ? repositories.get(descriptor.path) : void 0) ?? repositories.get(directory.path);
20878
+ return identity ? { ...directory, ...identity } : directory;
20879
+ });
20880
+ }
20881
+ function buildProjectGeometry(index) {
20882
+ const projects = /* @__PURE__ */ new Map();
20883
+ const launchpads = new Set(index.directories.filter((directory) => directory.launchpad).map((directory) => directory.key));
20884
+ const repositories = repositoryIdentitiesByPath(index);
20885
+ const seen = /* @__PURE__ */ new Set();
20886
+ for (const thread of index.threads) {
20887
+ if (!isStarMapOwnerThread(thread) || seen.has(threadKey(thread))) continue;
20888
+ seen.add(threadKey(thread));
20889
+ const primary = thread.linkedDirectories[0];
20890
+ const descriptor = primary ? classifyDirectory(primary) : void 0;
20891
+ const key = descriptor?.key ?? "__no-project__";
20892
+ let project = projects.get(key);
20893
+ if (!project) {
20894
+ const segments = (descriptor?.path ?? primary?.path)?.split(/[\\/]/).filter(Boolean);
20895
+ const repositoryKey = (descriptor?.path ? repositories.get(descriptor.path)?.repositoryKey : void 0) ?? (primary?.path ? repositories.get(primary.path)?.repositoryKey : void 0);
20896
+ project = {
20897
+ key,
20898
+ kind: descriptor?.kind ?? "unlinked",
20899
+ label: descriptor ? descriptor.label !== primary?.label ? descriptor.label : segments?.at(-1) ?? descriptor.label : "No project",
20900
+ path: descriptor?.path,
20901
+ ...repositoryKey ? { repositoryKey } : {},
20902
+ counts: { total: 0, active: 0, unread: 0, review: 0 },
20903
+ pinnedRootCount: 0,
20904
+ unpinnedRootCount: 0,
20905
+ launchpadPresent: launchpads.has(key)
20906
+ };
20907
+ projects.set(key, project);
20908
+ }
20909
+ project.counts.total += 1;
20910
+ const active = isActive(thread);
20911
+ if (active) project.counts.active += 1;
20912
+ if (thread.inbox.inInbox) {
20913
+ project.counts.unread += 1;
20914
+ if (!active) project.counts.review += 1;
20915
+ }
20916
+ if (!thread.parentThreadId) {
20917
+ if (thread.pinnedRank !== void 0) project.pinnedRootCount += 1;
20918
+ else project.unpinnedRootCount += 1;
20919
+ }
20920
+ project.latestUpdatedAt = Math.max(project.latestUpdatedAt ?? 0, thread.updatedAt ?? 0);
20921
+ }
20922
+ return [...projects.values()].sort((left, right) => left.key.localeCompare(right.key));
20923
+ }
20924
+ function normalizeQuery(query) {
20925
+ if (query.kind === "group-members") return { ...query, roots: [...query.roots].sort((left, right) => identityKey(left).localeCompare(identityKey(right))) };
20926
+ if (query.kind === "messaging-threads" || query.kind === "messaging-projects") {
20927
+ return {
20928
+ ...query,
20929
+ filter: query.filter?.trim().toLowerCase() || void 0,
20930
+ ...query.allowedBackends ? { allowedBackends: [...new Set(query.allowedBackends)].sort() } : {}
20931
+ };
20932
+ }
20933
+ if (query.kind === "star-map") {
20934
+ return { kind: "star-map", ...query.projectKey !== void 0 ? { projectKey: query.projectKey } : {}, filters: Object.fromEntries(Object.entries(query.filters).filter(([, value]) => value !== "neutral").sort(([left], [right]) => left.localeCompare(right))) };
20935
+ }
20936
+ if (query.kind === "lens" || query.kind === "directory-index") {
20937
+ return {
20938
+ ...query,
20939
+ ...query.kind === "directory-index" && query.keys ? { keys: [...new Set(query.keys)].sort() } : {},
20940
+ ...query.kind === "directory-index" && query.paths ? { paths: [...new Set(query.paths)].sort() } : {},
20941
+ ...query.filter?.trim().toLowerCase() ? { filter: query.filter.trim().toLowerCase() } : { filter: void 0 }
20942
+ };
20943
+ }
20944
+ if (query.kind === "search") {
20945
+ return { ...query, text: query.text.trim().toLowerCase() };
20946
+ }
20947
+ if (query.kind === "directory") {
20948
+ return {
20949
+ ...query,
20950
+ roots: query.roots ?? "all",
20951
+ disclosedParentThreadKeys: [
20952
+ ...new Set(query.disclosedParentThreadKeys ?? [])
20953
+ ].sort()
20954
+ };
20955
+ }
20956
+ if (query.kind === "exact") {
20957
+ return {
20958
+ ...query,
20959
+ identities: [...query.identities].sort((left, right) => identityKey(left).localeCompare(identityKey(right)))
20960
+ };
20961
+ }
20962
+ return query;
20963
+ }
20964
+ function navigationQueryKey(request) {
20965
+ return hashValue({
20966
+ inventory: request.inventory ?? "owner",
20967
+ backend: request.backend ?? "all",
20968
+ attentionView: request.attentionView,
20969
+ query: normalizeQuery(request.query)
20970
+ });
20971
+ }
20972
+ function rowOrderKey(index) {
20973
+ return index.toString(36).padStart(10, "0");
20974
+ }
20975
+ function compareUpdated(left, right) {
20976
+ return (right.updatedAt ?? 0) - (left.updatedAt ?? 0) || threadKey(left).localeCompare(threadKey(right));
20977
+ }
20978
+ function compareCreated(left, right) {
20979
+ return (right.createdAt ?? 0) - (left.createdAt ?? 0) || threadKey(left).localeCompare(threadKey(right));
20980
+ }
20981
+ function compareDirectoryMembers(left, right) {
20982
+ if (Boolean(left.pinnedRank) !== Boolean(right.pinnedRank)) {
20983
+ return left.pinnedRank ? -1 : 1;
20984
+ }
20985
+ if (left.pinnedRank && right.pinnedRank) {
20986
+ const pinned = comparePinnedThreads(left, right);
20987
+ if (pinned !== 0) return pinned;
20988
+ }
20989
+ return compareCreated(left, right);
20990
+ }
20991
+ function isStarMapOwnerThread(thread) {
20992
+ return isOrdinaryThread(thread) && thread.archivedAt === void 0 && thread.federation?.ref.target.scope !== "remote";
20993
+ }
20994
+ function starMapProjectKey(thread) {
20995
+ const primary = thread.linkedDirectories[0];
20996
+ return primary ? classifyDirectory(primary).key : "__no-project__";
20997
+ }
20998
+ function starMapSignals(thread, index) {
20999
+ const active = isActive(thread);
21000
+ const unread = thread.inbox.inInbox && thread.inbox.reason === "updated-since-seen";
21001
+ return {
21002
+ active,
21003
+ unread,
21004
+ attention: active || unread,
21005
+ approval: !thread.federation && (index.inputRequestThreadKeys?.has(buildThreadIdentityKey(thread.source, thread.id)) ?? false),
21006
+ pr: thread.prs?.some((pr2) => pr2.state !== "merged" && pr2.state !== "closed" && pr2.lifecycleState !== "merged" && pr2.lifecycleState !== "closed") ?? false,
21007
+ unpushed: (thread.gitWorkingState?.unpushedCommits ?? 0) > 0,
21008
+ pinned: thread.pinnedRank !== void 0,
21009
+ agent: thread.agent !== void 0
21010
+ };
21011
+ }
21012
+ function selectQueryThreads(params) {
21013
+ const ordinaryThreads = params.index.threads.filter(isOrdinaryThread);
21014
+ const query = params.query;
21015
+ if (query.kind === "messaging-threads" || query.kind === "messaging-projects") {
21016
+ const directory = query.directoryKey ? params.index.directories.find((candidate) => candidate.key === query.directoryKey || candidate.path === query.directoryKey) : void 0;
21017
+ const members = directory ? new Set(directory.threadKeys.flatMap((key) => {
21018
+ const thread = params.threadsByLegacyKey.get(key);
21019
+ return thread ? [threadKey(thread)] : [];
21020
+ })) : void 0;
21021
+ const filter = query.kind === "messaging-threads" ? query.filter?.trim().toLowerCase() : void 0;
21022
+ return ordinaryThreads.filter((thread) => {
21023
+ if (query.agentOnly && !thread.agent) return false;
21024
+ if (query.excludeFullAccess && thread.executionMode === "full-access") return false;
21025
+ if (query.allowedBackends && !query.allowedBackends.includes(thread.source)) return false;
21026
+ if (query.directoryKey && (!members || !members.has(threadKey(thread)))) return false;
21027
+ return !filter || [
21028
+ thread.id,
21029
+ thread.title,
21030
+ thread.summary,
21031
+ thread.agent?.name,
21032
+ thread.agent?.instructions,
21033
+ thread.projectKey,
21034
+ ...thread.linkedDirectories.flatMap((linked) => [linked.label, linked.path, linked.worktreePath])
21035
+ ].some((value) => value?.toLowerCase().includes(filter));
21036
+ }).sort(compareUpdated);
21037
+ }
21038
+ if (query.kind === "directory-index" || query.kind === "star-map-geometry" || query.kind === "model-inventory") {
21039
+ return [];
21040
+ }
21041
+ if (query.kind === "star-map") {
21042
+ return ordinaryThreads.filter(isStarMapOwnerThread).filter((thread) => query.projectKey === void 0 || starMapProjectKey(thread) === query.projectKey).filter((thread) => thread.pinnedRank !== void 0 && query.filters.pinned !== "exclude" || passesNavigationStarMapFilters(starMapSignals(thread, params.index), query.filters)).sort((left, right) => {
21043
+ if (left.pinnedRank !== void 0 !== (right.pinnedRank !== void 0)) return left.pinnedRank !== void 0 ? -1 : 1;
21044
+ return left.pinnedRank !== void 0 && right.pinnedRank !== void 0 ? comparePinnedThreads(left, right) : compareUpdated(left, right);
21045
+ });
21046
+ }
21047
+ if (query.kind === "lens") {
21048
+ const filter = query.filter?.trim().toLowerCase();
21049
+ const rootOf = params.lensRootOf;
21050
+ const threads = ordinaryThreads.filter((thread) => {
21051
+ if (query.lens === "attention") {
21052
+ if (!isActive(thread) && !thread.inbox.inInbox) return false;
21053
+ }
21054
+ if (rootOf && rootOf(thread).pinnedRank !== void 0 !== (query.roots === "pinned")) return false;
21055
+ if (!filter) return true;
21056
+ return thread.title.toLowerCase().includes(filter) || thread.linkedDirectories.some((directory) => directory.path.toLowerCase().includes(filter));
21057
+ });
21058
+ if (rootOf && query.roots === "pinned") {
21059
+ return threads.sort((left, right) => {
21060
+ const leftRoot = rootOf(left);
21061
+ const rightRoot = rootOf(right);
21062
+ if (leftRoot !== rightRoot) return comparePinnedThreads(leftRoot, rightRoot);
21063
+ if (left === leftRoot !== (right === rightRoot)) return left === leftRoot ? -1 : 1;
21064
+ return compareCreated(left, right);
21065
+ });
21066
+ }
21067
+ return threads.sort(query.lens === "recents" ? compareCreated : compareUpdated);
21068
+ }
21069
+ if (query.kind === "directory") {
21070
+ const directory = params.index.directories.find(
21071
+ (candidate) => candidate.key === query.directoryKey
21072
+ );
21073
+ if (!directory) return [];
21074
+ const disclosedParents = new Set(query.disclosedParentThreadKeys ?? []);
21075
+ const members = directoryMemberThreads(directory, params.threadsByLegacyKey);
21076
+ const parentOf = directoryParentResolver(members, params.parentCandidates);
21077
+ return members.filter((thread) => {
21078
+ const parent = parentOf(thread);
21079
+ if (parent) return disclosedParents.has(buildThreadIdentityKey(parent.backend, parent.threadId));
21080
+ return query.roots === "pinned" ? thread.pinnedRank !== void 0 : query.roots === "unpinned" ? thread.pinnedRank === void 0 : true;
21081
+ }).sort(compareDirectoryMembers);
21082
+ }
21083
+ if (query.kind === "group-members") {
21084
+ const ordinary = new Set(ordinaryThreads);
21085
+ const children = /* @__PURE__ */ new Map();
21086
+ for (const thread of ordinaryThreads) {
21087
+ const parent = parentIdentity(thread, params.parentCandidates);
21088
+ if (!parent) continue;
21089
+ const key = identityKey(parent);
21090
+ const members = children.get(key) ?? [];
21091
+ members.push(thread);
21092
+ children.set(key, members);
21093
+ }
21094
+ const result = [];
21095
+ const visited = /* @__PURE__ */ new Set();
21096
+ const visiting2 = /* @__PURE__ */ new Set();
21097
+ const visit = (ref, depth) => {
21098
+ const key = identityKey(ref);
21099
+ if (visited.has(key)) return;
21100
+ if (visiting2.has(key) || depth > 32) throw new Error("Thread group has a cycle or exceeds the 32-level group budget.");
21101
+ visiting2.add(key);
21102
+ for (const child of children.get(key) ?? []) visit(navigationIdentity(child), depth + 1);
21103
+ visiting2.delete(key);
21104
+ visited.add(key);
21105
+ const thread = params.threadsByIdentity.get(key);
21106
+ if (thread && ordinary.has(thread)) result.push(thread);
21107
+ };
21108
+ for (const root of query.roots) visit(root, 0);
21109
+ return result;
21110
+ }
21111
+ if (query.kind === "children") {
21112
+ const parentThread = params.threadsByIdentity.get(identityKey(query.parent));
21113
+ const children = ordinaryThreads.filter((thread) => {
21114
+ const parent = parentIdentity(thread, params.parentCandidates);
21115
+ return parent && identityKey(parent) === identityKey(query.parent);
21116
+ });
21117
+ return sortSubthreadSummaries(parentThread ?? {}, children);
21118
+ }
21119
+ if (query.kind === "search") {
21120
+ const text = query.text.trim().toLowerCase();
21121
+ if (!text) return [];
21122
+ return rankThreadJumpMatches(ordinaryThreads, text);
21123
+ }
21124
+ const selected = /* @__PURE__ */ new Map();
21125
+ const visiting = /* @__PURE__ */ new Set();
21126
+ const addWithAncestry = (thread) => {
21127
+ const key = threadKey(thread);
21128
+ if (selected.has(key) || visiting.has(key)) return;
21129
+ visiting.add(key);
21130
+ const parent = parentIdentity(thread, params.parentCandidates);
21131
+ if (query.includeAncestry && parent) {
21132
+ const parentThread = params.threadsByIdentity.get(identityKey(parent));
21133
+ if (parentThread) addWithAncestry(parentThread);
21134
+ }
21135
+ selected.set(key, thread);
21136
+ visiting.delete(key);
21137
+ };
21138
+ for (const identity of query.identities.slice(0, NAVIGATION_QUERY_MAX_PAGE_ROWS)) {
21139
+ const thread = params.threadsByIdentity.get(identityKey(identity));
21140
+ if (thread) addWithAncestry(thread);
21141
+ }
21142
+ return [...selected.values()];
21143
+ }
21144
+ function buildModelInventory(threads) {
21145
+ const groups = /* @__PURE__ */ new Map();
21146
+ for (const thread of threads) {
21147
+ const model = thread.model?.trim() || void 0;
21148
+ const key = JSON.stringify([thread.source, model, thread.modelMigrationRevision]);
21149
+ const group = groups.get(key) ?? { backend: thread.source, model, modelMigrationRevision: thread.modelMigrationRevision, threadCount: 0, fastThreadCount: 0 };
21150
+ group.threadCount += 1;
21151
+ if (thread.fastMode === true || thread.serviceTier === "ultrafast") group.fastThreadCount += 1;
21152
+ groups.set(key, group);
21153
+ }
21154
+ return [...groups].sort(([left], [right]) => left.localeCompare(right)).map(([, group]) => group);
21155
+ }
21156
+ function buildHomeProjects(directories, threadsByLegacyKey) {
21157
+ const homes = /* @__PURE__ */ new Map();
21158
+ for (const directory of directories) {
21159
+ if (directory.kind !== "directory" || !directory.key.startsWith("directory:") || directory.localAvailability) continue;
21160
+ const home = {
21161
+ directoryKey: directory.key,
21162
+ ...directory.forgeAutomation ? { forgeAutomation: directory.forgeAutomation } : {}
21163
+ };
21164
+ for (const key of directory.threadKeys) {
21165
+ const thread = threadsByLegacyKey.get(key);
21166
+ if (thread && !thread.federation && !homes.has(thread)) homes.set(thread, home);
21167
+ }
21168
+ }
21169
+ return homes;
21170
+ }
21171
+ function buildThreadsByLegacyKey(index) {
21172
+ return new Map(
21173
+ index.threads.map((thread) => [
21174
+ thread.federation?.ref.target.scope === "remote" ? federatedThreadIdentityKey(thread.federation.ref) : buildThreadIdentityKey(thread.source, thread.id),
21175
+ thread
21176
+ ])
21177
+ );
21178
+ }
21179
+ function buildParentCandidates(index) {
21180
+ const parentCandidates = /* @__PURE__ */ new Map();
21181
+ for (const thread of index.threads) {
21182
+ const ref = navigationIdentity(thread);
21183
+ const key = JSON.stringify([ref.ownerInstanceId ?? null, ref.threadId]);
21184
+ const candidates = parentCandidates.get(key) ?? [];
21185
+ candidates.push(thread);
21186
+ parentCandidates.set(key, candidates);
21187
+ if (!ref.ownerInstanceId && index.localInstanceId) {
21188
+ parentCandidates.set(JSON.stringify([index.localInstanceId, ref.threadId]), candidates);
21189
+ }
21190
+ }
21191
+ return parentCandidates;
21192
+ }
21193
+ function explainMissingNavigationAnchor(params) {
21194
+ const { anchor, query } = params.request;
21195
+ if (anchor?.kind !== "thread") {
21196
+ return anchor && !params.index.directories.some((directory2) => directory2.key === anchor.key) ? "directory-not-indexed" : "filtered";
21197
+ }
21198
+ const thread = params.index.threads.find((candidate) => threadKey(candidate) === identityKey(anchor.ref));
21199
+ if (!thread) {
21200
+ const sameId = params.index.threads.filter((candidate) => candidate.source === anchor.ref.backend && candidate.id === anchor.ref.threadId);
21201
+ if (anchor.ref.ownerInstanceId && anchor.ref.ownerInstanceId === params.index.localInstanceId && sameId.some((candidate) => !navigationIdentity(candidate).ownerInstanceId)) return "ref-mismatch";
21202
+ return sameId.length ? "other-owner" : "not-indexed";
21203
+ }
21204
+ if (query.kind !== "directory") return thread.archivedAt !== void 0 ? "archived" : "filtered";
21205
+ const directory = params.index.directories.find((candidate) => candidate.key === query.directoryKey);
21206
+ if (!directory) return "directory-not-indexed";
21207
+ const threadsByLegacyKey = buildThreadsByLegacyKey(params.index);
21208
+ if (!directory.threadKeys.some((key) => threadsByLegacyKey.get(key) === thread)) return "other-directory";
21209
+ const parent = directoryParentResolver(directoryMemberThreads(directory, threadsByLegacyKey), buildParentCandidates(params.index))(thread);
21210
+ if (parent) {
21211
+ return query.disclosedParentThreadKeys?.includes(buildThreadIdentityKey(parent.backend, parent.threadId)) ? "ref-mismatch" : "child";
21212
+ }
21213
+ if (query.roots === "unpinned" && thread.pinnedRank !== void 0) return "pinned";
21214
+ if (query.roots === "pinned" && thread.pinnedRank === void 0) return "unpinned";
21215
+ return "ref-mismatch";
21216
+ }
21217
+ function projectNavigationQuery(params) {
21218
+ const query = params.request.query;
21219
+ if (query.kind === "model-inventory") {
21220
+ const owned = params.index.threads.filter((thread) => isStarMapOwnerThread(thread) && (!params.request.backend || params.request.backend === "all" || params.request.backend === thread.source));
21221
+ return { coverage: params.index.coverage ?? { state: "complete" }, counts: countsForThreads(owned), directories: [], entries: [], modelGroups: buildModelInventory(owned), queryKey: navigationQueryKey(params.request) };
21222
+ }
21223
+ const threadsByIdentity = new Map(
21224
+ params.index.threads.map((thread) => [threadKey(thread), thread])
21225
+ );
21226
+ const threadsByLegacyKey = buildThreadsByLegacyKey(params.index);
21227
+ const parentCandidates = buildParentCandidates(params.index);
21228
+ const repositories = repositoryIdentitiesByPath(params.index);
21229
+ const childCountByParent = /* @__PURE__ */ new Map();
21230
+ const viewerChildCountByParent = /* @__PURE__ */ new Map();
21231
+ for (const thread of params.index.threads) {
21232
+ const parent = parentIdentity(thread, parentCandidates);
21233
+ if (!parent || !isOrdinaryThread(thread)) continue;
21234
+ const key = identityKey(parent);
21235
+ childCountByParent.set(key, (childCountByParent.get(key) ?? 0) + 1);
21236
+ if (parent.ownerInstanceId && navigationIdentity(thread).ownerInstanceId !== parent.ownerInstanceId) {
21237
+ viewerChildCountByParent.set(key, (viewerChildCountByParent.get(key) ?? 0) + 1);
21238
+ }
21239
+ }
21240
+ const lensRootOf = query.kind === "lens" && query.roots ? lensRootResolver({ threadsByIdentity, parentCandidates }) : void 0;
21241
+ const selectedThreads = selectQueryThreads({
21242
+ query,
21243
+ index: params.index,
21244
+ threadsByIdentity,
21245
+ threadsByLegacyKey,
21246
+ parentCandidates,
21247
+ lensRootOf
21248
+ });
21249
+ if ((query.kind === "lens" && query.lens === "attention" || query.kind === "star-map" && query.filters.attention === "include") && params.attentionOrder) {
21250
+ const members = params.attentionOrder.members;
21251
+ selectedThreads.sort((left, right) => {
21252
+ if (query.kind === "star-map") {
21253
+ if (left.pinnedRank !== void 0 !== (right.pinnedRank !== void 0)) return left.pinnedRank !== void 0 ? -1 : 1;
21254
+ if (left.pinnedRank !== void 0 && right.pinnedRank !== void 0) return comparePinnedThreads(left, right);
21255
+ }
21256
+ return (members.get(navigationAttentionIdentity(right))?.rank ?? 0) - (members.get(navigationAttentionIdentity(left))?.rank ?? 0);
21257
+ });
21258
+ }
21259
+ if (query.kind === "messaging-projects") {
21260
+ const eligibleKeys = new Set(selectedThreads.map(threadKey));
21261
+ const eligibleByLegacyKey = new Map([...threadsByLegacyKey].filter(([, thread]) => eligibleKeys.has(threadKey(thread))));
21262
+ let sourceDirectories = params.index.directories;
21263
+ if (query.scratchpadFirst) {
21264
+ const scratchpads = sourceDirectories.filter((directory) => directory.kind === "workspace").sort((left, right) => (right.latestUpdatedAt ?? 0) - (left.latestUpdatedAt ?? 0) || left.key.localeCompare(right.key));
21265
+ if (scratchpads.length > 1) {
21266
+ sourceDirectories = [
21267
+ { ...scratchpads[0], threadKeys: [...new Set(scratchpads.flatMap((directory) => directory.threadKeys))] },
21268
+ ...sourceDirectories.filter((directory) => directory.kind !== "workspace")
21269
+ ];
21270
+ }
21271
+ }
21272
+ const filter = query.filter?.trim().toLowerCase();
21273
+ const directories = buildDirectoryRows({ snapshot: { ...params.index, directories: sourceDirectories }, threadsByLegacyKey: eligibleByLegacyKey, parentCandidates }).filter((directory) => !filter || [
21274
+ directory.key,
21275
+ directory.label,
21276
+ directory.path,
21277
+ query.scratchpadFirst && directory.kind === "workspace" ? "Workspaces Scratchpad" : void 0
21278
+ ].some((value) => value?.toLowerCase().includes(filter))).sort((left, right) => (query.scratchpadFirst ? Number(right.kind === "workspace") - Number(left.kind === "workspace") : 0) || (right.latestUpdatedAt ?? 0) - (left.latestUpdatedAt ?? 0) || left.label.localeCompare(right.label));
21279
+ return {
21280
+ coverage: params.index.coverage ?? { state: "complete" },
21281
+ counts: countsForThreads(selectedThreads),
21282
+ collectionSize: directories.length,
21283
+ collectionKind: "directories",
21284
+ directories,
21285
+ entries: [],
21286
+ queryKey: navigationQueryKey(params.request)
21287
+ };
21288
+ }
21289
+ const attentionMemberKeys = query.kind === "lens" && query.lens === "attention" ? new Set(selectedThreads.map(threadKey)) : void 0;
21290
+ const directoryMembers = query.kind === "directory" ? directoryMemberThreads(params.index.directories.find((directory) => directory.key === query.directoryKey), threadsByLegacyKey) : void 0;
21291
+ const directoryParentOf = directoryMembers ? directoryParentResolver(directoryMembers, parentCandidates) : void 0;
21292
+ const homeProjects = query.kind === "group-members" ? buildHomeProjects(params.index.directories, threadsByLegacyKey) : void 0;
21293
+ const entries = selectedThreads.map((thread, index) => {
21294
+ const parent = parentIdentity(thread, parentCandidates);
21295
+ const placementParent = attentionMemberKeys && parent && !attentionMemberKeys.has(identityKey(parent)) ? void 0 : directoryParentOf ? directoryParentOf(thread) : query.kind === "exact" && query.includeAncestry ? availableParentIdentity(thread, parentCandidates, params.index.coverage) : parent;
21296
+ return {
21297
+ row: projectNavigationRow({
21298
+ childCount: childCountByParent.get(threadKey(thread)) ?? 0,
21299
+ ...params.request.inventory === "viewer" && navigationIdentity(thread).ownerInstanceId ? { viewerChildCount: viewerChildCountByParent.get(threadKey(thread)) ?? 0 } : {},
21300
+ needsInput: starMapSignals(thread, params.index).approval,
21301
+ repositories,
21302
+ thread: parent?.ownerInstanceId && !thread.parentThreadInstanceId ? { ...thread, parentThreadInstanceId: parent.ownerInstanceId } : thread,
21303
+ homeProject: homeProjects?.get(thread)
21304
+ }),
21305
+ orderKey: rowOrderKey(index),
21306
+ ...params.attentionOrder?.members.has(navigationAttentionIdentity(thread)) ? { attentionRank: params.attentionOrder.members.get(navigationAttentionIdentity(thread)).rank } : {},
21307
+ placement: placementParent ? { kind: "child", parent: placementParent } : { kind: "root" }
21308
+ };
21309
+ });
21310
+ const includeDirectories = query.kind === "directory-index" || query.kind === "star-map-geometry";
21311
+ const countsThreads = query.kind === "directory" ? directoryMembers ?? [] : query.kind === "star-map" ? query.projectKey === void 0 ? params.index.threads.filter(isStarMapOwnerThread) : selectedThreads : query.kind === "exact" || query.kind === "search" || query.kind === "messaging-threads" || query.kind === "children" || query.kind === "group-members" || query.kind === "lens" && query.roots !== void 0 ? selectedThreads : params.index.threads;
21312
+ return {
21313
+ coverage: params.index.coverage ?? { state: "complete" },
21314
+ // Both pages count every owner thread, so both can say which providers
21315
+ // the owner uses. Provider chips hide while that is a single provider.
21316
+ counts: countsForThreads(countsThreads, {
21317
+ backends: query.kind === "directory-index" || query.kind === "star-map" && query.projectKey === void 0
21318
+ }),
21319
+ ...query.kind === "directory" ? {
21320
+ selectionDirectory: buildDirectoryRows({ snapshot: {
21321
+ ...params.index,
21322
+ directories: params.index.directories.filter((directory) => directory.key === query.directoryKey)
21323
+ }, threadsByLegacyKey, parentCandidates })[0]
21324
+ } : {},
21325
+ ...query.kind === "exact" && selectedThreads.length ? {
21326
+ // Resolve the canonical home from the complete compact index. A linked
21327
+ // worktree path is not necessarily the directory key shown by the viewer.
21328
+ selectionDirectory: buildDirectoryRows({ snapshot: {
21329
+ ...params.index,
21330
+ directories: params.index.directories.filter((directory) => directory.threadKeys.some((key) => selectedThreads[0] === threadsByLegacyKey.get(key)))
21331
+ }, threadsByLegacyKey, parentCandidates })[0]
21332
+ } : {},
21333
+ // The split's acknowledgment as well as its size: an owner that predates
21334
+ // `roots` sets no collection size on a lens page, so the viewer knows to
21335
+ // filter that page itself.
21336
+ ...lensRootOf ? { collectionSize: selectedThreads.filter((thread) => lensRootOf(thread) === thread).length } : {},
21337
+ ...query.kind === "messaging-threads" ? {
21338
+ collectionSize: entries.length,
21339
+ ...query.directoryKey ? { selectionDirectory: buildDirectoryRows({ snapshot: { ...params.index, directories: params.index.directories.filter((directory) => directory.key === query.directoryKey || directory.path === query.directoryKey) }, threadsByLegacyKey, parentCandidates }).find((directory) => directory.key === query.directoryKey || directory.path === query.directoryKey) } : {}
21340
+ } : {},
21341
+ ...query.kind === "star-map" ? {
21342
+ facets: countNavigationStarMapFacets(
21343
+ [...new Map(params.index.threads.filter(isStarMapOwnerThread).map((thread) => [threadKey(thread), thread])).values()].map((thread) => starMapSignals(thread, params.index)),
21344
+ query.filters
21345
+ )
21346
+ } : {},
21347
+ ...includeDirectories ? { collectionKind: "directories" } : {},
21348
+ directories: includeDirectories ? (query.kind === "star-map-geometry" ? buildProjectGeometry(params.index) : buildDirectoryRows({ snapshot: params.index, threadsByLegacyKey, parentCandidates })).filter((directory) => query.kind !== "directory-index" || (!query.keys && !query.paths || query.keys?.includes(directory.key) || directory.path !== void 0 && query.paths?.includes(directory.path))).filter((directory) => query.kind !== "directory-index" || !query.filter?.trim() || [directory.label, directory.path].some((value) => textMatchesJumpQuery(value, query.filter ?? ""))) : [],
21349
+ entries,
21350
+ queryKey: navigationQueryKey(params.request)
21351
+ };
21352
+ }
21353
+
21354
+ // ../owner-core/src/navigation-observation-clock.ts
21355
+ var observation = 0;
21356
+ function nextNavigationObservation() {
21357
+ return ++observation;
21358
+ }
21359
+ function currentNavigationObservation() {
21360
+ return observation;
21361
+ }
21362
+
21363
+ // ../owner-core/src/navigation-query-store.ts
21364
+ var NAVIGATION_QUERY_CURSOR_IDLE_MS = 6e4;
21365
+ var NAVIGATION_QUERY_MAX_GENERATIONS = 8;
21366
+ var NAVIGATION_QUERY_MAX_RETAINED_BYTES = 32 * 1024 * 1024;
21367
+ var NAVIGATION_ATTENTION_MAX_VIEWS = 64;
21368
+ var NAVIGATION_ATTENTION_MAX_BYTES = 8 * 1024 * 1024;
21369
+ var NavigationQueryError = class extends Error {
21370
+ code;
21371
+ constructor(code, message) {
21372
+ super(`[${code}] ${message}`);
21373
+ this.name = "NavigationQueryError";
21374
+ this.code = code;
21375
+ }
21376
+ };
21377
+ function serializedBytes(value) {
21378
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
21379
+ }
21380
+ function encodeCursor(cursor) {
21381
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
21382
+ }
21383
+ function decodeCursor(value) {
21384
+ try {
21385
+ const parsed = JSON.parse(
21386
+ Buffer.from(value, "base64url").toString("utf8")
21387
+ );
21388
+ if (typeof parsed.generation !== "string" || typeof parsed.offset !== "number" || !Number.isSafeInteger(parsed.offset) || parsed.offset < 0 || typeof parsed.queryKey !== "string" || typeof parsed.scopeKey !== "string") {
21389
+ throw new Error("invalid cursor fields");
21390
+ }
21391
+ return parsed;
21392
+ } catch {
21393
+ throw new NavigationQueryError(
21394
+ "navigation_invalid_request",
21395
+ "Navigation cursor is malformed."
21396
+ );
21397
+ }
21398
+ }
21399
+ function validateRequest(request) {
21400
+ if (!request || request.protocol !== NAVIGATION_QUERY_PROTOCOL_VERSION) {
21401
+ throw new NavigationQueryError(
21402
+ "navigation_invalid_request",
21403
+ `Navigation query protocol ${NAVIGATION_QUERY_PROTOCOL_VERSION} is required.`
21404
+ );
21405
+ }
21406
+ if (request.inventory !== void 0 && request.inventory !== "owner" && request.inventory !== "viewer") {
21407
+ throw new NavigationQueryError("navigation_invalid_request", "Navigation inventory must be owner or viewer.");
21408
+ }
21409
+ if (request.inventory === "viewer" && request.federationTarget?.scope === "remote") {
21410
+ throw new NavigationQueryError("navigation_invalid_request", "Viewer navigation inventory is available only on this machine.");
21411
+ }
21412
+ if (request.attentionView !== void 0 && (typeof request.attentionView.id !== "string" || request.attentionView.id.length < 1 || request.attentionView.id.length > 128 || typeof request.attentionView.promoteOnTurnEnd !== "boolean")) {
21413
+ throw new NavigationQueryError(
21414
+ "navigation_invalid_request",
21415
+ "Navigation Attention requires a bounded view identity and promotion policy."
21416
+ );
21417
+ }
21418
+ if (request.pageSize !== void 0 && (!Number.isSafeInteger(request.pageSize) || request.pageSize < 1 || request.pageSize > NAVIGATION_QUERY_MAX_PAGE_ROWS)) {
21419
+ throw new NavigationQueryError(
21420
+ "navigation_invalid_request",
21421
+ "Navigation page size must be between 1 and 100."
21422
+ );
21423
+ }
21424
+ const range = request.retainedRange;
21425
+ if (range && (request.cursor || request.completeBaselineRevision || typeof range.revision !== "string" || range.revision.length > 128 || typeof range.ownerEpoch !== "string" || range.ownerEpoch.length > 128 || !Number.isSafeInteger(range.start) || range.start < 0 || !Number.isSafeInteger(range.count) || range.count < 1 || !Number.isSafeInteger(range.start + range.count))) {
21426
+ throw new NavigationQueryError("navigation_invalid_request", "Invalid retained navigation range.");
21427
+ }
21428
+ if (request.anchor && (request.cursor || request.completeBaselineRevision || !["thread", "directory"].includes(request.anchor.kind) || request.anchor.kind === "thread" && (!request.anchor.ref?.backend || !request.anchor.ref.threadId) || request.anchor.kind === "directory" && !request.anchor.key)) {
21429
+ throw new NavigationQueryError("navigation_invalid_request", "Navigation rebaseline requires one explicit anchor without a cursor or unchanged baseline.");
21430
+ }
21431
+ if (request.query.kind === "star-map") {
21432
+ if (request.query.projectKey !== void 0 && (typeof request.query.projectKey !== "string" || !request.query.projectKey || request.query.projectKey.length > 4096)) {
21433
+ throw new NavigationQueryError("navigation_invalid_request", "Invalid Star Map project key.");
21434
+ }
21435
+ const keys = /* @__PURE__ */ new Set(["attention", "approval", "pr", "unpushed", "pinned", "agent"]);
21436
+ if (!request.query.filters || Object.entries(request.query.filters).some(([key, value]) => !keys.has(key) || !["neutral", "include", "exclude"].includes(value))) {
21437
+ throw new NavigationQueryError("navigation_invalid_request", "Invalid Star Map filter selection.");
21438
+ }
21439
+ }
21440
+ if (request.query.kind === "messaging-threads" || request.query.kind === "messaging-projects") {
21441
+ const query = request.query;
21442
+ if (query.allowedBackends !== void 0 && (!Array.isArray(query.allowedBackends) || query.allowedBackends.length > 64 || query.allowedBackends.some((backend) => typeof backend !== "string" || !backend)) || query.filter !== void 0 && (typeof query.filter !== "string" || query.filter.length > 256)) {
21443
+ throw new NavigationQueryError("navigation_invalid_request", "Messaging queries accept at most 64 backends and a 256-character filter.");
21444
+ }
21445
+ }
21446
+ if (request.query.kind === "directory-index") {
21447
+ const { keys, paths } = request.query;
21448
+ if (keys !== void 0 && !Array.isArray(keys) || paths !== void 0 && !Array.isArray(paths) || [...keys ?? [], ...paths ?? []].length > 100 || [...keys ?? [], ...paths ?? []].some((key) => typeof key !== "string" || !key)) {
21449
+ throw new NavigationQueryError("navigation_invalid_request", "Exact directory metadata accepts at most 100 keys or paths.");
21450
+ }
21451
+ }
21452
+ if (request.query.kind === "directory" && request.query.roots !== void 0 && !["all", "pinned", "unpinned"].includes(request.query.roots)) {
21453
+ throw new NavigationQueryError("navigation_invalid_request", "Invalid directory root disclosure.");
21454
+ }
21455
+ if (request.query.kind === "lens" && request.query.roots !== void 0 && !["pinned", "unpinned"].includes(request.query.roots)) {
21456
+ throw new NavigationQueryError("navigation_invalid_request", "Invalid lens root split.");
21457
+ }
21458
+ if (request.query.kind === "exact" && request.query.identities.length > 100) {
21459
+ throw new NavigationQueryError(
21460
+ "navigation_invalid_request",
21461
+ "An exact navigation query accepts at most 100 identities."
21462
+ );
21463
+ }
21464
+ if (request.query.kind === "exact" && request.query.resolveFromOwners && (request.inventory !== "viewer" || request.federationTarget?.scope === "remote")) {
21465
+ throw new NavigationQueryError(
21466
+ "navigation_invalid_request",
21467
+ "Owner-resolved navigation is available only to this machine's viewer inventory."
21468
+ );
21469
+ }
21470
+ if (request.query.kind === "group-members" && (!Array.isArray(request.query.roots) || !request.query.roots.length || request.query.roots.length > 100)) {
21471
+ throw new NavigationQueryError("navigation_invalid_request", "Group discovery accepts one to 100 root identities.");
21472
+ }
21473
+ if (request.query.kind === "directory" && (request.query.disclosedParentThreadKeys?.length ?? 0) > 100) {
21474
+ throw new NavigationQueryError(
21475
+ "navigation_invalid_request",
21476
+ "A directory navigation query accepts at most 100 disclosures."
21477
+ );
21478
+ }
21479
+ }
21480
+ function fingerprintNavigationMaterialization(materialization) {
21481
+ const hash = createHash8("sha256");
21482
+ let retainedBytes = 0;
21483
+ const append = (value) => {
21484
+ retainedBytes += Buffer.byteLength(value, "utf8");
21485
+ if (retainedBytes > NAVIGATION_QUERY_MAX_RETAINED_BYTES) {
21486
+ throw new NavigationQueryError("navigation_busy", "Navigation query exceeds the process retained-memory budget.");
21487
+ }
21488
+ hash.update(value);
21489
+ };
21490
+ append("{");
21491
+ let first = true;
21492
+ for (const [key, value] of Object.entries(materialization)) {
21493
+ if (value === void 0) continue;
21494
+ if (!first) append(",");
21495
+ first = false;
21496
+ append(JSON.stringify(key));
21497
+ append(":");
21498
+ if (Array.isArray(value)) {
21499
+ append("[");
21500
+ for (let index = 0; index < value.length; index += 1) {
21501
+ if (index) append(",");
21502
+ append(JSON.stringify(value[index]) ?? "null");
21503
+ }
21504
+ append("]");
21505
+ } else append(JSON.stringify(value));
21506
+ }
21507
+ append("}");
21508
+ return { revision: hash.digest("base64url"), retainedBytes };
21509
+ }
21510
+ function pageBase(params) {
21511
+ const { generation } = params;
21512
+ return {
21513
+ protocol: NAVIGATION_QUERY_PROTOCOL_VERSION,
21514
+ queryKey: generation.materialization.queryKey,
21515
+ generation: generation.generation,
21516
+ ownerEpoch: params.ownerEpoch,
21517
+ countsRevision: generation.completeRevision,
21518
+ coverage: generation.materialization.coverage,
21519
+ counts: generation.materialization.counts,
21520
+ ...generation.materialization.collectionSize !== void 0 ? { collectionSize: generation.materialization.collectionSize } : {},
21521
+ ...generation.materialization.selectionDirectory ? { selectionDirectory: generation.materialization.selectionDirectory } : {},
21522
+ ...generation.materialization.facets ? { facets: generation.materialization.facets } : {}
21523
+ };
21524
+ }
21525
+ var NavigationQueryStore = class {
21526
+ constructor(options = {}) {
21527
+ this.options = options;
21528
+ }
21529
+ options;
21530
+ ownerEpoch = randomUUID8();
21531
+ attentionLifetimes = /* @__PURE__ */ new Map();
21532
+ generations = /* @__PURE__ */ new Map();
21533
+ currentGenerationByScopeAndQuery = /* @__PURE__ */ new Map();
21534
+ attentionViews = /* @__PURE__ */ new Map();
21535
+ async readPage(params) {
21536
+ validateRequest(params.request);
21537
+ const now = this.options.now?.() ?? Date.now();
21538
+ this.expireIdle(now);
21539
+ const attentionKey = params.request.attentionView ? JSON.stringify([params.scopeKey, params.request.attentionView.id]) : void 0;
21540
+ let lifetime = attentionKey ? this.attentionLifetimes.get(attentionKey) : void 0;
21541
+ if (attentionKey && !lifetime) {
21542
+ const bytes = [...this.attentionLifetimes.keys(), attentionKey].reduce((total, key) => total + serializedBytes(key) + 32, 0);
21543
+ if (this.attentionLifetimes.size >= 256 || bytes > 256 * 1024) throw new NavigationQueryError("navigation_busy", "Attention lifetime admission is occupied.");
21544
+ lifetime = {};
21545
+ this.attentionLifetimes.set(attentionKey, lifetime);
21546
+ }
21547
+ if (lifetime?.closedAt !== void 0) throw new NavigationQueryError("navigation_invalid_request", "This Attention view has closed. Open a new view lifetime.");
21548
+ const queryKey = navigationQueryKey(params.request);
21549
+ let generation;
21550
+ let newCurrentKey;
21551
+ let offset = 0;
21552
+ let anchorIndex;
21553
+ if (params.request.cursor) {
21554
+ const cursor = decodeCursor(params.request.cursor);
21555
+ if (cursor.scopeKey !== params.scopeKey || cursor.queryKey !== queryKey) {
21556
+ throw new NavigationQueryError(
21557
+ "navigation_invalid_request",
21558
+ "Navigation cursor does not belong to this requester and query."
21559
+ );
21560
+ }
21561
+ const retained = this.generations.get(cursor.generation);
21562
+ if (!retained) {
21563
+ throw new NavigationQueryError(
21564
+ "navigation_cursor_expired",
21565
+ "Navigation cursor expired; rebaseline around the visible anchor."
21566
+ );
21567
+ }
21568
+ if (retained.scopeKey !== params.scopeKey || retained.materialization.queryKey !== queryKey) {
21569
+ throw new NavigationQueryError(
21570
+ "navigation_invalid_request",
21571
+ "Navigation generation does not belong to this requester and query."
21572
+ );
21573
+ }
21574
+ this.options.onCursorHit?.();
21575
+ generation = retained;
21576
+ offset = cursor.offset;
21577
+ } else {
21578
+ const readStartedAt = currentNavigationObservation();
21579
+ const index = await params.loadIndex();
21580
+ if (lifetime?.closedAt !== void 0) throw new NavigationQueryError("navigation_invalid_request", "This Attention view closed during its owner read.");
21581
+ anchorIndex = index;
21582
+ const attentionOrder = this.reconcileAttentionView(params.scopeKey, params.request, index, index.observedAt ?? readStartedAt);
21583
+ const materialization = projectNavigationQuery({
21584
+ index,
21585
+ request: params.request,
21586
+ attentionOrder
21587
+ });
21588
+ const { revision, retainedBytes } = fingerprintNavigationMaterialization(materialization);
21589
+ const currentKey = `${params.scopeKey}\0${queryKey}`;
21590
+ const currentId = this.currentGenerationByScopeAndQuery.get(currentKey);
21591
+ const current = currentId ? this.generations.get(currentId) : void 0;
21592
+ if (current?.completeRevision === revision) {
21593
+ generation = current;
21594
+ } else {
21595
+ generation = {
21596
+ completeRevision: revision,
21597
+ createdAt: now,
21598
+ generation: randomUUID8(),
21599
+ lastAccessedAt: now,
21600
+ materialization,
21601
+ retainedBytes,
21602
+ scopeKey: params.scopeKey,
21603
+ attentionViewId: params.request.attentionView?.id
21604
+ };
21605
+ if (generation.retainedBytes > NAVIGATION_QUERY_MAX_RETAINED_BYTES) {
21606
+ throw new NavigationQueryError("navigation_busy", "Navigation query exceeds the process retained-memory budget.");
21607
+ }
21608
+ newCurrentKey = currentKey;
21609
+ }
21610
+ if (params.request.completeBaselineRevision && params.request.completeBaselineRevision === generation.completeRevision) {
21611
+ const unchanged = {
21612
+ ...pageBase({ generation, ownerEpoch: this.ownerEpoch }),
21613
+ entries: [],
21614
+ directories: [],
21615
+ complete: true,
21616
+ unchanged: true
21617
+ };
21618
+ this.assertPageBudget(unchanged);
21619
+ generation.lastAccessedAt = now;
21620
+ return unchanged;
21621
+ }
21622
+ }
21623
+ const range = params.request.retainedRange;
21624
+ if (range && range.revision === generation.completeRevision && range.ownerEpoch === this.ownerEpoch) {
21625
+ const size = generation.materialization.modelGroups?.length ?? (generation.materialization.directories.length || generation.materialization.entries.length);
21626
+ if (range.start + range.count > size) {
21627
+ throw new NavigationQueryError("navigation_invalid_request", "Retained range exceeds its matching generation.");
21628
+ }
21629
+ const page2 = this.buildPage({
21630
+ generation,
21631
+ offset: range.start + range.count,
21632
+ ownerEpoch: this.ownerEpoch,
21633
+ pageSize: 0
21634
+ });
21635
+ page2.rangeStart = range.start;
21636
+ page2.rangeUnchanged = { start: range.start, count: range.count };
21637
+ this.assertPageBudget(page2);
21638
+ generation.lastAccessedAt = now;
21639
+ if (newCurrentKey && page2.nextCursor) {
21640
+ this.retainGeneration(generation);
21641
+ this.currentGenerationByScopeAndQuery.set(newCurrentKey, generation.generation);
21642
+ }
21643
+ return page2;
21644
+ }
21645
+ if (params.request.anchor) {
21646
+ const anchor = params.request.anchor;
21647
+ offset = anchor.kind === "directory" ? generation.materialization.directories.findIndex((directory) => directory.key === anchor.key) : generation.materialization.entries.findIndex(({ row }) => row.ref.backend === anchor.ref.backend && row.ref.threadId === anchor.ref.threadId && row.ref.ownerInstanceId === anchor.ref.ownerInstanceId);
21648
+ if (offset < 0) {
21649
+ const reason = anchorIndex ? explainMissingNavigationAnchor({ index: anchorIndex, request: params.request }) : "filtered";
21650
+ throw new NavigationQueryError("navigation_anchor_missing", `The visible navigation anchor is no longer in this query (reason: ${reason}). Choose another item or restart this list explicitly.`);
21651
+ }
21652
+ }
21653
+ generation.lastAccessedAt = now;
21654
+ const page = this.buildPage({
21655
+ generation,
21656
+ offset,
21657
+ ownerEpoch: this.ownerEpoch,
21658
+ pageSize: params.request.pageSize ?? NAVIGATION_QUERY_MAX_PAGE_ROWS
21659
+ });
21660
+ if (newCurrentKey && page.nextCursor) {
21661
+ this.retainGeneration(generation);
21662
+ this.currentGenerationByScopeAndQuery.set(newCurrentKey, generation.generation);
21663
+ }
21664
+ return page;
21665
+ }
21666
+ /** Window teardown releases order lifetime; page expiry deliberately does not. */
21667
+ releaseAttentionView(scopeKey, viewId) {
21668
+ const lifetime = this.attentionLifetimes.get(JSON.stringify([scopeKey, viewId]));
21669
+ if (lifetime) lifetime.closedAt = this.options.now?.() ?? Date.now();
21670
+ for (const [key, generation] of this.generations) {
21671
+ if (generation.scopeKey === scopeKey && generation.attentionViewId === viewId) this.generations.delete(key);
21672
+ }
21673
+ for (const [key, generationId] of this.currentGenerationByScopeAndQuery) {
21674
+ if (!this.generations.has(generationId)) this.currentGenerationByScopeAndQuery.delete(key);
21675
+ }
21676
+ this.attentionViews.delete(JSON.stringify([scopeKey, viewId]));
21677
+ }
21678
+ /**
21679
+ * Reads for one view overlap, and a read can be served an index whose
21680
+ * listing began before a boundary the store already applied. The index is
21681
+ * served and reconciled, never reread: the reducer leaves alone whatever
21682
+ * the order observed after that listing began (see the observation clock).
21683
+ */
21684
+ reconcileAttentionView(scopeKey, request, index, observedAt) {
21685
+ if (!request.attentionView) return void 0;
21686
+ const { id, promoteOnTurnEnd } = request.attentionView;
21687
+ const key = JSON.stringify([scopeKey, id]);
21688
+ const previous = this.attentionViews.get(key);
21689
+ if (previous?.failed) throw new NavigationQueryError("navigation_busy", "Attention metadata exceeded its budget. Close this view and open a new one.");
21690
+ if (!previous && this.attentionViews.size >= NAVIGATION_ATTENTION_MAX_VIEWS) {
21691
+ throw new NavigationQueryError("navigation_busy", "Attention view budget is occupied.");
21692
+ }
21693
+ const order = reconcileNavigationAttentionOrder({
21694
+ previous: previous?.order,
21695
+ threads: index.threads,
21696
+ complete: !index.coverage || index.coverage.state === "complete",
21697
+ promoteOnTurnEnd,
21698
+ observedAt
21699
+ });
21700
+ if (previous && navigationAttentionOrderFollows(previous.order, observedAt)) {
21701
+ this.options.onOvertakenRead?.();
21702
+ }
21703
+ const remoteMembers = index.coverage && index.coverage.state !== "complete" ? new Set(previous?.remoteMembers) : /* @__PURE__ */ new Set();
21704
+ for (const thread of index.threads) if (thread.federation?.ref.target.scope === "remote") remoteMembers.add(navigationAttentionIdentity(thread));
21705
+ const bytes = navigationAttentionOrderBytes(order) + serializedBytes([...remoteMembers]);
21706
+ let retainedBytes = bytes;
21707
+ for (const [otherKey, view] of this.attentionViews) {
21708
+ if (otherKey !== key) retainedBytes += view.bytes;
21709
+ }
21710
+ if (retainedBytes > NAVIGATION_ATTENTION_MAX_BYTES) {
21711
+ throw new NavigationQueryError("navigation_busy", "Attention metadata exceeds its retained budget.");
21712
+ }
21713
+ this.attentionViews.set(key, { order, bytes, promoteOnTurnEnd, backend: request.backend, remoteMembers });
21714
+ return order;
21715
+ }
21716
+ /** Boundary events maintain session ranks even while the viewer browses another lens. */
21717
+ observeAttentionEvent(event) {
21718
+ const method = event.notification.method;
21719
+ const status = "status" in event.notification.params ? event.notification.params.status : void 0;
21720
+ const active = method === "turn/started" ? true : ["turn/completed", "turn/failed", "turn/cancelled"].includes(method) ? false : method === "thread/status/changed" ? status?.type === "active" ? true : status?.type === "idle" ? false : void 0 : void 0;
21721
+ const remove = method === "thread/archived";
21722
+ const seen = method === "navigation/thread/seen";
21723
+ if (active === void 0 && !remove && !seen) return;
21724
+ const params = event.notification.params;
21725
+ if (typeof params.threadId !== "string") return;
21726
+ const owner = event.federationTarget?.scope === "remote" ? event.federationTarget.instanceId : null;
21727
+ const key = JSON.stringify([owner, event.backend, params.threadId]);
21728
+ if (owner && ![...this.attentionViews.values()].some((view) => view.remoteMembers.has(key))) return;
21729
+ const observedAt = nextNavigationObservation();
21730
+ let retainedBytes = [...this.attentionViews.values()].reduce((sum, view) => sum + view.bytes, 0);
21731
+ for (const view of this.attentionViews.values()) {
21732
+ if (view.failed || view.backend && view.backend !== "all" && view.backend !== event.backend || owner && !view.remoteMembers.has(key)) continue;
21733
+ const member = view.order.members.get(key);
21734
+ let order = view.order;
21735
+ if (remove || seen && !member?.active) {
21736
+ order = removeNavigationAttentionMember({ previous: order, key, observedAt });
21737
+ } else if (active !== void 0) {
21738
+ order = observeNavigationAttentionTurn({
21739
+ previous: order,
21740
+ key,
21741
+ active,
21742
+ turnId: params.turnId ?? params.turn?.id,
21743
+ promoteOnTurnEnd: view.promoteOnTurnEnd,
21744
+ observedAt
21745
+ });
21746
+ }
21747
+ if (order === view.order) continue;
21748
+ const bytes = navigationAttentionOrderBytes(order) + serializedBytes([...view.remoteMembers]);
21749
+ if (retainedBytes - view.bytes + bytes > NAVIGATION_ATTENTION_MAX_BYTES) {
21750
+ view.failed = true;
21751
+ continue;
21752
+ }
21753
+ retainedBytes += bytes - view.bytes;
21754
+ view.order = order;
21755
+ view.bytes = bytes;
21756
+ }
21757
+ }
21758
+ buildPage(params) {
21759
+ const materialization = params.generation.materialization;
21760
+ const collectionKind = materialization.modelGroups ? "models" : materialization.collectionKind === "directories" || materialization.directories.length > 0 ? "directories" : "entries";
21761
+ const collection = materialization.modelGroups ?? (collectionKind === "directories" ? materialization.directories : materialization.entries);
21762
+ if (params.offset > collection.length) {
21763
+ throw new NavigationQueryError(
21764
+ "navigation_invalid_request",
21765
+ "Navigation cursor offset is outside its generation."
21766
+ );
21767
+ }
21768
+ const page = {
21769
+ ...pageBase({
21770
+ generation: params.generation,
21771
+ ownerEpoch: params.ownerEpoch
21772
+ }),
21773
+ entries: [],
21774
+ ...params.offset ? { rangeStart: params.offset } : {},
21775
+ ...collectionKind === "models" ? { modelGroups: [] } : collectionKind === "directories" ? { directories: [] } : {},
21776
+ complete: false
21777
+ };
21778
+ let nextOffset = params.offset;
21779
+ for (; nextOffset < collection.length && nextOffset - params.offset < params.pageSize; nextOffset += 1) {
21780
+ const item = collection[nextOffset];
21781
+ const candidate = collectionKind === "models" ? { ...page, modelGroups: [...page.modelGroups ?? [], item] } : collectionKind === "directories" ? {
21782
+ ...page,
21783
+ directories: [...page.directories ?? [], item]
21784
+ } : {
21785
+ ...page,
21786
+ entries: [...page.entries, item]
21787
+ };
21788
+ const candidateNextOffset = nextOffset + 1;
21789
+ if (candidateNextOffset < collection.length) {
21790
+ candidate.nextCursor = encodeCursor({
21791
+ generation: params.generation.generation,
21792
+ offset: candidateNextOffset,
21793
+ queryKey: params.generation.materialization.queryKey,
21794
+ scopeKey: params.generation.scopeKey
21795
+ });
21796
+ }
21797
+ if (serializedBytes(candidate) > NAVIGATION_QUERY_MAX_RESULT_BYTES) {
21798
+ if (nextOffset === params.offset) {
21799
+ throw new NavigationQueryError(
21800
+ "navigation_item_too_large",
21801
+ "One navigation row exceeds the result budget; read its exact detail."
21802
+ );
21803
+ }
21804
+ break;
21805
+ }
21806
+ if (collectionKind === "models") {
21807
+ page.modelGroups.push(item);
21808
+ } else if (collectionKind === "directories") {
21809
+ page.directories.push(item);
21810
+ } else {
21811
+ page.entries.push(item);
21812
+ }
21813
+ }
21814
+ page.complete = nextOffset >= collection.length;
21815
+ if (!page.complete) {
21816
+ page.nextCursor = encodeCursor({
21817
+ generation: params.generation.generation,
21818
+ offset: nextOffset,
21819
+ queryKey: params.generation.materialization.queryKey,
21820
+ scopeKey: params.generation.scopeKey
21821
+ });
21822
+ }
21823
+ this.assertPageBudget(page);
21824
+ return page;
21825
+ }
21826
+ retainGeneration(generation) {
21827
+ if (generation.retainedBytes > NAVIGATION_QUERY_MAX_RETAINED_BYTES) {
21828
+ throw new NavigationQueryError(
21829
+ "navigation_busy",
21830
+ "Navigation query exceeds the process retained-memory budget."
21831
+ );
21832
+ }
21833
+ let retainedBytes = [...this.generations.values()].reduce((total, item) => total + item.retainedBytes, 0);
21834
+ const currentIds = new Set(this.currentGenerationByScopeAndQuery.values());
21835
+ const evictable = [...this.generations.values()].sort((left, right) => Number(currentIds.has(left.generation)) - Number(currentIds.has(right.generation)) || left.lastAccessedAt - right.lastAccessedAt);
21836
+ while (this.generations.size >= NAVIGATION_QUERY_MAX_GENERATIONS || retainedBytes + generation.retainedBytes > NAVIGATION_QUERY_MAX_RETAINED_BYTES) {
21837
+ const oldest = evictable.shift();
21838
+ if (!oldest) break;
21839
+ this.generations.delete(oldest.generation);
21840
+ retainedBytes -= oldest.retainedBytes;
21841
+ for (const [key, id] of this.currentGenerationByScopeAndQuery) {
21842
+ if (id === oldest.generation) this.currentGenerationByScopeAndQuery.delete(key);
21843
+ }
21844
+ }
21845
+ this.generations.set(generation.generation, generation);
21846
+ return generation;
21847
+ }
21848
+ expireIdle(now) {
21849
+ for (const [key, lifetime] of this.attentionLifetimes) {
21850
+ if (lifetime.closedAt !== void 0 && now - lifetime.closedAt > NAVIGATION_QUERY_CURSOR_IDLE_MS) this.attentionLifetimes.delete(key);
21851
+ }
21852
+ for (const generation of this.generations.values()) {
21853
+ if (now - generation.lastAccessedAt <= NAVIGATION_QUERY_CURSOR_IDLE_MS) {
21854
+ continue;
21855
+ }
21856
+ this.generations.delete(generation.generation);
21857
+ }
21858
+ for (const [key, generationId] of this.currentGenerationByScopeAndQuery) {
21859
+ if (!this.generations.has(generationId)) {
21860
+ this.currentGenerationByScopeAndQuery.delete(key);
21861
+ }
21862
+ }
21863
+ }
21864
+ assertPageBudget(page) {
21865
+ if (serializedBytes(page) > NAVIGATION_QUERY_MAX_RESULT_BYTES) {
21866
+ throw new NavigationQueryError(
21867
+ "navigation_item_too_large",
21868
+ "Navigation response exceeds the result budget."
21869
+ );
21870
+ }
21871
+ }
21872
+ };
20106
21873
  export {
20107
21874
  AgentToolRouter,
20108
21875
  FEDERATION_BACKEND_METHOD_CAPABILITIES,
@@ -20140,6 +21907,8 @@ export {
20140
21907
  FederationRouter,
20141
21908
  FederationRpcEndpoint,
20142
21909
  FederationSocketKeepalive,
21910
+ NAVIGATION_QUERY_MAX_RESULT_BYTES,
21911
+ NavigationQueryStore,
20143
21912
  NoiseIKHandshake,
20144
21913
  PWRAGENT_MESSAGING_UNAVAILABLE_MESSAGE,
20145
21914
  PWRAGENT_MODEL_DIRECTED_PDF_OPERATION_NAMES,
@@ -20158,6 +21927,7 @@ export {
20158
21927
  buildPwrAgentMessagingPdfToolRouter,
20159
21928
  buildPwrAgentMessagingToolDefinitions,
20160
21929
  buildPwrAgentMessagingToolRouter,
21930
+ buildThreadIdentityKey,
20161
21931
  completeFederationEnrollment,
20162
21932
  connectFederationClient,
20163
21933
  createFederationEnrollmentInvite,